SaltProphet commited on
Commit
8b9b3f3
·
verified ·
1 Parent(s): 1858216

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +66 -73
app.py CHANGED
@@ -15,69 +15,68 @@ matplotlib.use('Agg')
15
 
16
  def update_output_visibility(choice):
17
  if "2 Stems" in choice:
18
- return {
19
- vocals_output: gr.update(visible=True),
20
- drums_output: gr.update(visible=False),
21
- bass_output: gr.update(visible=False),
22
- other_output: gr.update(visible=True, label="Instrumental (No Vocals)")
23
  }
24
  elif "4 Stems" in choice:
25
- return {
26
- vocals_output: gr.update(visible=True),
27
- drums_output: gr.update(visible=True),
28
- bass_output: gr.update(visible=True),
29
- other_output: gr.update(visible=True, label="Other")
30
  }
31
 
32
  async def separate_stems(audio_file_path, stem_choice, progress=gr.Progress(track_tqdm=True)):
33
  if audio_file_path is None: raise gr.Error("No audio file uploaded!")
34
  log_history = "Starting separation...\n"
35
- # Make progress bar visible at the start
36
- yield { status_log: log_history, progress_bar: progress(0, desc="Starting...", visible=True) }
37
  try:
38
  progress(0.05, desc="Preparing audio file...")
39
  log_history += "Preparing audio file...\n"; yield { status_log: log_history, progress_bar: progress(0.05, desc="Preparing...") }
40
  original_filename_base = os.path.basename(audio_file_path).rsplit('.', 1)[0]
41
  stable_input_path = f"stable_input_{original_filename_base}.wav"
42
  shutil.copy(audio_file_path, stable_input_path)
43
-
44
  model_arg = "--two-stems=vocals" if "2 Stems" in stem_choice else ""
45
  output_dir = "separated"
46
  if os.path.exists(output_dir): shutil.rmtree(output_dir)
47
-
48
  command = f"python3 -m demucs {model_arg} -o \"{output_dir}\" \"{stable_input_path}\""
49
- log_history += f"Running Demucs command: {command}\n(This may take a minute or more depending on track length)\n";
50
  yield { status_log: log_history, progress_bar: progress(0.2, desc="Running Demucs...") }
51
 
52
  process = await asyncio.create_subprocess_shell(
53
  command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
54
  stdout, stderr = await process.communicate()
55
 
56
- if process.returncode != 0:
57
  raise gr.Error(f"Demucs failed. Error: {stderr.decode()[:500]}")
58
 
59
  log_history += "Demucs finished. Locating stem files...\n"; yield { status_log: log_history, progress_bar: progress(0.8, desc="Locating stems...") }
60
  stable_filename_base = os.path.basename(stable_input_path).rsplit('.', 1)[0]
61
-
62
  subfolders = [f.name for f in os.scandir(output_dir) if f.is_dir()]
63
  if not subfolders: raise gr.Error("Demucs output folder structure not found!")
64
- model_folder_name = subfolders[0]
65
  stems_path = os.path.join(output_dir, model_folder_name, stable_filename_base)
66
-
67
- if not os.path.exists(stems_path):
68
  raise gr.Error(f"Demucs output directory was not found! Looked for: {stems_path}")
69
-
70
  vocals_path = os.path.join(stems_path, "vocals.wav") if os.path.exists(os.path.join(stems_path, "vocals.wav")) else None
71
  drums_path = os.path.join(stems_path, "drums.wav") if os.path.exists(os.path.join(stems_path, "drums.wav")) else None
72
  bass_path = os.path.join(stems_path, "bass.wav") if os.path.exists(os.path.join(stems_path, "bass.wav")) else None
73
  other_filename = "no_vocals.wav" if "2 Stems" in stem_choice else "other.wav"
74
  other_path = os.path.join(stems_path, other_filename) if os.path.exists(os.path.join(stems_path, other_filename)) else None
75
-
76
  os.remove(stable_input_path)
77
-
78
- log_history += "✅ Stem separation complete!\n";
79
- yield {
80
- status_log: log_history,
81
  progress_bar: progress(1, desc="Complete!", visible=False), # Hide progress bar when done
82
  vocals_output: gr.update(value=vocals_path),
83
  drums_output: gr.update(value=drums_path),
@@ -89,7 +88,6 @@ async def separate_stems(audio_file_path, stem_choice, progress=gr.Progress(trac
89
  yield { status_log: log_history + f"❌ ERROR: {e}", progress_bar: gr.update(visible=False) } # Hide progress bar on error
90
 
91
  def slice_stem_real(stem_audio_data, loop_choice, sensitivity, stem_name, progress_fn=None):
92
- # This function remains the same internally
93
  if stem_audio_data is None: return None, None
94
  sample_rate, y_int = stem_audio_data; y = librosa.util.buf_to_float(y_int)
95
  y_mono = librosa.to_mono(y.T) if y.ndim > 1 else y
@@ -133,9 +131,8 @@ def slice_stem_real(stem_audio_data, loop_choice, sensitivity, stem_name, progre
133
 
134
  async def slice_all_and_zip_real(vocals, drums, bass, other, loop_choice, sensitivity, progress=gr.Progress(track_tqdm=True)):
135
  log_history = "Starting batch slice...\n"
136
- # Make progress bar visible at the start
137
- yield { status_log: log_history, progress_bar: progress(0, desc="Starting...", visible=True) }
138
- await asyncio.sleep(0.1)
139
  stems_to_process = {"vocals": vocals, "drums": drums, "bass": bass, "other": other}
140
  zip_path = "Loop_Architect_Pack.zip"
141
  num_stems = sum(1 for data in stems_to_process.values() if data is not None)
@@ -147,38 +144,34 @@ async def slice_all_and_zip_real(vocals, drums, bass, other, loop_choice, sensit
147
  for name, data in stems_to_process.items():
148
  if data is not None:
149
  log_history += f"--- Slicing {name} stem ---\n"; yield { status_log: log_history }
150
-
151
  def update_main_progress(p, desc=""):
152
  overall_progress = (processed_count + p) / num_stems
153
- # Ensure progress updates visibility
154
- progress(overall_progress, desc=f"Slicing {name}: {desc}", visible=True)
155
-
156
  sliced_files, temp_dir = slice_stem_real((data[0], data[1]), loop_choice, sensitivity, name, progress_fn=update_main_progress)
157
-
158
  if sliced_files:
159
  log_history += f"Generated {len(sliced_files)} slices for {name}.\n"; yield { status_log: log_history }
160
- all_temp_dirs.append(temp_dir)
161
  for loop_file in sliced_files:
162
- arcname = os.path.join(name, os.path.basename(loop_file))
163
  zf.write(loop_file, arcname)
164
  else:
165
  log_history += f"No slices generated for {name}.\n"; yield { status_log: log_history }
166
 
167
  processed_count += 1
168
- # Yield intermediate progress after each stem is fully processed
169
  yield { status_log: log_history, progress_bar: progress(processed_count / num_stems, desc=f"Finished {name}", visible=True) }
170
 
171
- log_history += "Packaging complete!\n";
172
- yield {
173
- status_log: log_history + "✅ Pack ready for download!",
174
- progress_bar: progress(1, desc="Pack Ready!", visible=False), # Hide when done
175
- download_zip_file: gr.update(value=zip_path, visible=True)
176
  }
177
  except Exception as e:
178
  print(f"An error occurred during slice all: {e}")
179
- yield { status_log: log_history + f"❌ ERROR: {e}", progress_bar: gr.update(visible=False) } # Hide on error
180
  finally:
181
- # Ensure cleanup happens even if there's an error during zipping
182
  for d in all_temp_dirs:
183
  if d and os.path.exists(d):
184
  shutil.rmtree(d)
@@ -192,32 +185,32 @@ with gr.Blocks(theme=gr.themes.Default(primary_hue="blue", secondary_hue="red"))
192
  audio_input = gr.Audio(type="filepath", label="Upload a Track")
193
  stem_options = gr.Radio(["4 Stems (Vocals, Drums, Bass, Other)", "2 Stems (Vocals + Instrumental)"], label="Separation Type", value="4 Stems (Vocals, Drums, Bass, Other)")
194
  submit_button = gr.Button("Separate Stems")
195
-
196
  with gr.Accordion("Slicing Options", open=True):
197
  loop_options_radio = gr.Radio(["One-Shots (All Transients)", "4 Bar Loops", "8 Bar Loops"], label="Slice Type", value="One-Shots (All Transients)")
198
  sensitivity_slider = gr.Slider(minimum=0.01, maximum=0.5, value=0.05, step=0.01, label="One-Shot Sensitivity", info="Lower values = more slices")
199
-
200
  gr.Markdown("### 3. Create Pack")
201
  slice_all_button = gr.Button("Slice All Stems & Create Pack", variant="primary")
202
  download_zip_file = gr.File(label="Download Your Loop Pack", visible=False)
203
 
204
  gr.Markdown("### Status")
205
- # --- THIS IS THE FIX --- Initialize without arguments
206
- progress_bar = gr.Progress(visible=False)
207
  status_log = gr.Textbox(label="Status Log", lines=10, interactive=False)
208
 
209
  with gr.Column(scale=2):
210
  with gr.Accordion("Separated Stems", open=True):
211
- with gr.Row():
212
  vocals_output = gr.Audio(label="Vocals", scale=4)
213
  slice_vocals_btn = gr.Button("Slice Vocals", scale=1)
214
- with gr.Row():
215
  drums_output = gr.Audio(label="Drums", scale=4)
216
  slice_drums_btn = gr.Button("Slice Drums", scale=1)
217
- with gr.Row():
218
  bass_output = gr.Audio(label="Bass", scale=4)
219
  slice_bass_btn = gr.Button("Slice Bass", scale=1)
220
- with gr.Row():
221
  other_output = gr.Audio(label="Other / Instrumental", scale=4)
222
  slice_other_btn = gr.Button("Slice Other", scale=1)
223
  gr.Markdown("### Sliced Loops / Samples (Preview)")
@@ -227,44 +220,44 @@ with gr.Blocks(theme=gr.themes.Default(primary_hue="blue", secondary_hue="red"))
227
  def slice_and_display(stem_data, loop_choice, sensitivity, stem_name):
228
  log_history = f"Slicing {stem_name}...\n"
229
  # Make progress bar visible when starting
230
- yield { status_log: log_history, progress_bar: gr.update(value=0, visible=True, label=f"Slicing {stem_name}...") }
231
-
 
 
 
232
  # Define how slice_stem_real updates the progress bar using gr.Progress directly
233
  def update_single_progress(p, desc=""):
234
- gr.Progress(track_tqdm=True)(p, desc=desc) # Directly update the bar
235
 
236
  files, temp_dir = slice_stem_real(stem_data, loop_choice, sensitivity, stem_name, progress_fn=update_single_progress)
237
-
238
  if temp_dir and os.path.exists(temp_dir):
239
- shutil.rmtree(temp_dir)
240
-
241
- yield {
242
- loop_gallery: gr.update(value=files),
243
  status_log: log_history + f"✅ Sliced {stem_name} into {len(files) if files else 0} pieces.",
244
  progress_bar: gr.update(visible=False) # Hide progress bar when done
245
  }
246
 
247
  submit_event = submit_button.click(
248
- fn=separate_stems,
249
- inputs=[audio_input, stem_options],
250
- # Corrected: progress_bar is not an output here
251
- outputs=[vocals_output, drums_output, bass_output, other_output, status_log]
252
  )
253
 
254
  stem_options.change(fn=update_output_visibility, inputs=stem_options, outputs=[vocals_output, drums_output, bass_output, other_output])
255
-
256
- # Use the wrapper function for individual slice buttons
257
  slice_vocals_btn.click(fn=slice_and_display, inputs=[vocals_output, loop_options_radio, sensitivity_slider, gr.Textbox("vocals", visible=False)], outputs=[loop_gallery, status_log, progress_bar])
258
  slice_drums_btn.click(fn=slice_and_display, inputs=[drums_output, loop_options_radio, sensitivity_slider, gr.Textbox("drums", visible=False)], outputs=[loop_gallery, status_log, progress_bar])
259
  slice_bass_btn.click(fn=slice_and_display, inputs=[bass_output, loop_options_radio, sensitivity_slider, gr.Textbox("bass", visible=False)], outputs=[loop_gallery, status_log, progress_bar])
260
  slice_other_btn.click(fn=slice_and_display, inputs=[other_output, loop_options_radio, sensitivity_slider, gr.Textbox("other", visible=False)], outputs=[loop_gallery, status_log, progress_bar])
261
-
262
  slice_all_event = slice_all_button.click(
263
- fn=slice_all_and_zip_real,
264
- inputs=[vocals_output, drums_output, bass_output, other_output, loop_options_radio, sensitivity_slider],
265
- # Corrected: progress_bar is not an output here
266
- outputs=[download_zip_file, download_zip_file, status_log]
267
  )
268
 
269
  # --- Launch the UI ---
270
- demo.launch()
 
15
 
16
  def update_output_visibility(choice):
17
  if "2 Stems" in choice:
18
+ return {
19
+ vocals_output: gr.update(visible=True),
20
+ drums_output: gr.update(visible=False),
21
+ bass_output: gr.update(visible=False),
22
+ other_output: gr.update(visible=True, label="Instrumental (No Vocals)")
23
  }
24
  elif "4 Stems" in choice:
25
+ return {
26
+ vocals_output: gr.update(visible=True),
27
+ drums_output: gr.update(visible=True),
28
+ bass_output: gr.update(visible=True),
29
+ other_output: gr.update(visible=True, label="Other")
30
  }
31
 
32
  async def separate_stems(audio_file_path, stem_choice, progress=gr.Progress(track_tqdm=True)):
33
  if audio_file_path is None: raise gr.Error("No audio file uploaded!")
34
  log_history = "Starting separation...\n"
35
+ yield { status_log: log_history, progress_bar: progress(0, desc="Starting...", visible=True) }
 
36
  try:
37
  progress(0.05, desc="Preparing audio file...")
38
  log_history += "Preparing audio file...\n"; yield { status_log: log_history, progress_bar: progress(0.05, desc="Preparing...") }
39
  original_filename_base = os.path.basename(audio_file_path).rsplit('.', 1)[0]
40
  stable_input_path = f"stable_input_{original_filename_base}.wav"
41
  shutil.copy(audio_file_path, stable_input_path)
42
+
43
  model_arg = "--two-stems=vocals" if "2 Stems" in stem_choice else ""
44
  output_dir = "separated"
45
  if os.path.exists(output_dir): shutil.rmtree(output_dir)
46
+
47
  command = f"python3 -m demucs {model_arg} -o \"{output_dir}\" \"{stable_input_path}\""
48
+ log_history += f"Running Demucs command: {command}\n(This may take a minute or more depending on track length)\n";
49
  yield { status_log: log_history, progress_bar: progress(0.2, desc="Running Demucs...") }
50
 
51
  process = await asyncio.create_subprocess_shell(
52
  command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
53
  stdout, stderr = await process.communicate()
54
 
55
+ if process.returncode != 0:
56
  raise gr.Error(f"Demucs failed. Error: {stderr.decode()[:500]}")
57
 
58
  log_history += "Demucs finished. Locating stem files...\n"; yield { status_log: log_history, progress_bar: progress(0.8, desc="Locating stems...") }
59
  stable_filename_base = os.path.basename(stable_input_path).rsplit('.', 1)[0]
60
+
61
  subfolders = [f.name for f in os.scandir(output_dir) if f.is_dir()]
62
  if not subfolders: raise gr.Error("Demucs output folder structure not found!")
63
+ model_folder_name = subfolders[0]
64
  stems_path = os.path.join(output_dir, model_folder_name, stable_filename_base)
65
+
66
+ if not os.path.exists(stems_path):
67
  raise gr.Error(f"Demucs output directory was not found! Looked for: {stems_path}")
68
+
69
  vocals_path = os.path.join(stems_path, "vocals.wav") if os.path.exists(os.path.join(stems_path, "vocals.wav")) else None
70
  drums_path = os.path.join(stems_path, "drums.wav") if os.path.exists(os.path.join(stems_path, "drums.wav")) else None
71
  bass_path = os.path.join(stems_path, "bass.wav") if os.path.exists(os.path.join(stems_path, "bass.wav")) else None
72
  other_filename = "no_vocals.wav" if "2 Stems" in stem_choice else "other.wav"
73
  other_path = os.path.join(stems_path, other_filename) if os.path.exists(os.path.join(stems_path, other_filename)) else None
74
+
75
  os.remove(stable_input_path)
76
+
77
+ log_history += "✅ Stem separation complete!\n";
78
+ yield {
79
+ status_log: log_history,
80
  progress_bar: progress(1, desc="Complete!", visible=False), # Hide progress bar when done
81
  vocals_output: gr.update(value=vocals_path),
82
  drums_output: gr.update(value=drums_path),
 
88
  yield { status_log: log_history + f"❌ ERROR: {e}", progress_bar: gr.update(visible=False) } # Hide progress bar on error
89
 
90
  def slice_stem_real(stem_audio_data, loop_choice, sensitivity, stem_name, progress_fn=None):
 
91
  if stem_audio_data is None: return None, None
92
  sample_rate, y_int = stem_audio_data; y = librosa.util.buf_to_float(y_int)
93
  y_mono = librosa.to_mono(y.T) if y.ndim > 1 else y
 
131
 
132
  async def slice_all_and_zip_real(vocals, drums, bass, other, loop_choice, sensitivity, progress=gr.Progress(track_tqdm=True)):
133
  log_history = "Starting batch slice...\n"
134
+ yield { status_log: log_history, progress_bar: progress(0, desc="Starting...", visible=True) }
135
+ await asyncio.sleep(0.1)
 
136
  stems_to_process = {"vocals": vocals, "drums": drums, "bass": bass, "other": other}
137
  zip_path = "Loop_Architect_Pack.zip"
138
  num_stems = sum(1 for data in stems_to_process.values() if data is not None)
 
144
  for name, data in stems_to_process.items():
145
  if data is not None:
146
  log_history += f"--- Slicing {name} stem ---\n"; yield { status_log: log_history }
 
147
  def update_main_progress(p, desc=""):
148
  overall_progress = (processed_count + p) / num_stems
149
+ progress(overall_progress, desc=f"Slicing {name}: {desc}", visible=True)
150
+
 
151
  sliced_files, temp_dir = slice_stem_real((data[0], data[1]), loop_choice, sensitivity, name, progress_fn=update_main_progress)
152
+
153
  if sliced_files:
154
  log_history += f"Generated {len(sliced_files)} slices for {name}.\n"; yield { status_log: log_history }
155
+ all_temp_dirs.append(temp_dir)
156
  for loop_file in sliced_files:
157
+ arcname = os.path.join(name, os.path.basename(loop_file))
158
  zf.write(loop_file, arcname)
159
  else:
160
  log_history += f"No slices generated for {name}.\n"; yield { status_log: log_history }
161
 
162
  processed_count += 1
 
163
  yield { status_log: log_history, progress_bar: progress(processed_count / num_stems, desc=f"Finished {name}", visible=True) }
164
 
165
+ log_history += "Packaging complete!\n";
166
+ yield {
167
+ status_log: log_history + "✅ Pack ready for download!",
168
+ progress_bar: progress(1, desc="Pack Ready!", visible=False),
169
+ download_zip_file: gr.update(value=zip_path, visible=True)
170
  }
171
  except Exception as e:
172
  print(f"An error occurred during slice all: {e}")
173
+ yield { status_log: log_history + f"❌ ERROR: {e}", progress_bar: gr.update(visible=False) }
174
  finally:
 
175
  for d in all_temp_dirs:
176
  if d and os.path.exists(d):
177
  shutil.rmtree(d)
 
185
  audio_input = gr.Audio(type="filepath", label="Upload a Track")
186
  stem_options = gr.Radio(["4 Stems (Vocals, Drums, Bass, Other)", "2 Stems (Vocals + Instrumental)"], label="Separation Type", value="4 Stems (Vocals, Drums, Bass, Other)")
187
  submit_button = gr.Button("Separate Stems")
188
+
189
  with gr.Accordion("Slicing Options", open=True):
190
  loop_options_radio = gr.Radio(["One-Shots (All Transients)", "4 Bar Loops", "8 Bar Loops"], label="Slice Type", value="One-Shots (All Transients)")
191
  sensitivity_slider = gr.Slider(minimum=0.01, maximum=0.5, value=0.05, step=0.01, label="One-Shot Sensitivity", info="Lower values = more slices")
192
+
193
  gr.Markdown("### 3. Create Pack")
194
  slice_all_button = gr.Button("Slice All Stems & Create Pack", variant="primary")
195
  download_zip_file = gr.File(label="Download Your Loop Pack", visible=False)
196
 
197
  gr.Markdown("### Status")
198
+ # --- THIS IS THE FIX --- Initialize without any arguments
199
+ progress_bar = gr.Progress()
200
  status_log = gr.Textbox(label="Status Log", lines=10, interactive=False)
201
 
202
  with gr.Column(scale=2):
203
  with gr.Accordion("Separated Stems", open=True):
204
+ with gr.Row():
205
  vocals_output = gr.Audio(label="Vocals", scale=4)
206
  slice_vocals_btn = gr.Button("Slice Vocals", scale=1)
207
+ with gr.Row():
208
  drums_output = gr.Audio(label="Drums", scale=4)
209
  slice_drums_btn = gr.Button("Slice Drums", scale=1)
210
+ with gr.Row():
211
  bass_output = gr.Audio(label="Bass", scale=4)
212
  slice_bass_btn = gr.Button("Slice Bass", scale=1)
213
+ with gr.Row():
214
  other_output = gr.Audio(label="Other / Instrumental", scale=4)
215
  slice_other_btn = gr.Button("Slice Other", scale=1)
216
  gr.Markdown("### Sliced Loops / Samples (Preview)")
 
220
  def slice_and_display(stem_data, loop_choice, sensitivity, stem_name):
221
  log_history = f"Slicing {stem_name}...\n"
222
  # Make progress bar visible when starting
223
+ yield {
224
+ status_log: log_history,
225
+ progress_bar: gr.update(value=0, visible=True, label=f"Slicing {stem_name}...")
226
+ }
227
+
228
  # Define how slice_stem_real updates the progress bar using gr.Progress directly
229
  def update_single_progress(p, desc=""):
230
+ gr.Progress(track_tqdm=True)(p, desc=desc) # Pass progress updates
231
 
232
  files, temp_dir = slice_stem_real(stem_data, loop_choice, sensitivity, stem_name, progress_fn=update_single_progress)
233
+
234
  if temp_dir and os.path.exists(temp_dir):
235
+ shutil.rmtree(temp_dir)
236
+
237
+ yield {
238
+ loop_gallery: gr.update(value=files),
239
  status_log: log_history + f"✅ Sliced {stem_name} into {len(files) if files else 0} pieces.",
240
  progress_bar: gr.update(visible=False) # Hide progress bar when done
241
  }
242
 
243
  submit_event = submit_button.click(
244
+ fn=separate_stems,
245
+ inputs=[audio_input, stem_options],
246
+ outputs=[vocals_output, drums_output, bass_output, other_output, status_log]
 
247
  )
248
 
249
  stem_options.change(fn=update_output_visibility, inputs=stem_options, outputs=[vocals_output, drums_output, bass_output, other_output])
250
+
 
251
  slice_vocals_btn.click(fn=slice_and_display, inputs=[vocals_output, loop_options_radio, sensitivity_slider, gr.Textbox("vocals", visible=False)], outputs=[loop_gallery, status_log, progress_bar])
252
  slice_drums_btn.click(fn=slice_and_display, inputs=[drums_output, loop_options_radio, sensitivity_slider, gr.Textbox("drums", visible=False)], outputs=[loop_gallery, status_log, progress_bar])
253
  slice_bass_btn.click(fn=slice_and_display, inputs=[bass_output, loop_options_radio, sensitivity_slider, gr.Textbox("bass", visible=False)], outputs=[loop_gallery, status_log, progress_bar])
254
  slice_other_btn.click(fn=slice_and_display, inputs=[other_output, loop_options_radio, sensitivity_slider, gr.Textbox("other", visible=False)], outputs=[loop_gallery, status_log, progress_bar])
255
+
256
  slice_all_event = slice_all_button.click(
257
+ fn=slice_all_and_zip_real,
258
+ inputs=[vocals_output, drums_output, bass_output, other_output, loop_options_radio, sensitivity_slider],
259
+ outputs=[download_zip_file, download_zip_file, status_log]
 
260
  )
261
 
262
  # --- Launch the UI ---
263
+ demo.launch()