SaltProphet commited on
Commit
6c1c4c8
·
verified ·
1 Parent(s): 0bcb76d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +63 -93
app.py CHANGED
@@ -1,4 +1,3 @@
1
- # Import libraries
2
  import gradio as gr
3
  import os
4
  import shutil
@@ -12,12 +11,9 @@ import zipfile
12
  import tempfile
13
  import matplotlib.pyplot as plt
14
  import matplotlib
15
- matplotlib.use('Agg') # Use a non-interactive backend for plotting
16
-
17
- # --- Helper/Processing Functions ---
18
 
19
  def update_output_visibility(choice):
20
- # Shows/hides stem players based on 2 or 4 stem selection
21
  if "2 Stems" in choice:
22
  return {
23
  vocals_output: gr.update(visible=True),
@@ -34,13 +30,10 @@ def update_output_visibility(choice):
34
  }
35
 
36
  async def separate_stems(audio_file_path, stem_choice, progress=gr.Progress(track_tqdm=True)):
37
- # Separates the uploaded audio using Demucs
38
  if audio_file_path is None: raise gr.Error("No audio file uploaded!")
39
-
40
  log_history = "Starting separation...\n"
41
- # Initial yield to update UI immediately
42
  yield { status_log: log_history, progress_bar: progress(0, desc="Starting...", visible=True) }
43
-
44
  try:
45
  progress(0.05, desc="Preparing audio file...")
46
  log_history += "Preparing audio file...\n"; yield { status_log: log_history, progress_bar: progress(0.05, desc="Preparing...") }
@@ -57,10 +50,7 @@ async def separate_stems(audio_file_path, stem_choice, progress=gr.Progress(trac
57
  yield { status_log: log_history, progress_bar: progress(0.2, desc="Running Demucs...") }
58
 
59
  process = await asyncio.create_subprocess_shell(
60
- command,
61
- stdout=asyncio.subprocess.PIPE,
62
- stderr=asyncio.subprocess.PIPE)
63
-
64
  stdout, stderr = await process.communicate()
65
 
66
  if process.returncode != 0:
@@ -69,7 +59,6 @@ async def separate_stems(audio_file_path, stem_choice, progress=gr.Progress(trac
69
  log_history += "Demucs finished. Locating stem files...\n"; yield { status_log: log_history, progress_bar: progress(0.8, desc="Locating stems...") }
70
  stable_filename_base = os.path.basename(stable_input_path).rsplit('.', 1)[0]
71
 
72
- # Find the model-specific subfolder created by Demucs
73
  subfolders = [f.name for f in os.scandir(output_dir) if f.is_dir()]
74
  if not subfolders: raise gr.Error("Demucs output folder structure not found!")
75
  model_folder_name = subfolders[0]
@@ -97,32 +86,23 @@ async def separate_stems(audio_file_path, stem_choice, progress=gr.Progress(trac
97
  }
98
  except Exception as e:
99
  print(f"An error occurred during separation: {e}")
100
- yield { status_log: log_history + f"❌ ERROR: {e}", progress_bar: gr.update(visible=False) }
101
 
102
  def slice_stem_real(stem_audio_data, loop_choice, sensitivity, stem_name, progress_fn=None):
103
- # Slices a single stem into loops or one-shots
104
- if stem_audio_data is None:
105
- return None, None
106
-
107
- sample_rate, y_int = stem_audio_data
108
- y = librosa.util.buf_to_float(y_int)
109
  y_mono = librosa.to_mono(y.T) if y.ndim > 1 else y
110
-
111
  if progress_fn: progress_fn(0.1, desc="Detecting BPM...")
112
  tempo, beats = librosa.beat.beat_track(y=y_mono, sr=sample_rate)
113
- bpm = 120 if tempo is None else np.round(tempo)
114
- bpm_int = int(bpm.item())
115
  if bpm_int == 0: bpm_int = 120; print("BPM detection failed, defaulting to 120 BPM.")
116
  print(f"Detected BPM for {stem_name}: {bpm_int}")
117
-
118
- output_files = []
119
- loops_dir = tempfile.mkdtemp()
120
-
121
  if "One-Shots" in loop_choice:
122
  if progress_fn: progress_fn(0.3, desc="Finding transients...")
123
  onset_frames = librosa.onset.onset_detect(y=y_mono, sr=sample_rate, delta=sensitivity, wait=1, pre_avg=1, post_avg=1, post_max=1)
124
  onset_samples = librosa.frames_to_samples(onset_frames)
125
-
126
  if progress_fn: progress_fn(0.5, desc="Slicing one-shots...")
127
  if len(onset_samples) > 0:
128
  num_onsets = len(onset_samples)
@@ -134,79 +114,74 @@ def slice_stem_real(stem_audio_data, loop_choice, sensitivity, stem_name, progre
134
  output_files.append(filename)
135
  if progress_fn and num_onsets > 1:
136
  progress_fn(0.5 + (i / (num_onsets - 1) * 0.5), desc=f"Exporting slice {i+1}/{num_onsets}...")
137
- else: # Handle bar loops
138
- bars = int(loop_choice.split(" ")[0])
139
- seconds_per_beat = 60.0 / bpm_int
140
- seconds_per_bar = seconds_per_beat * 4
141
- loop_duration_seconds = seconds_per_bar * bars
142
- loop_duration_samples = int(loop_duration_seconds * sample_rate)
143
-
144
  if progress_fn: progress_fn(0.4, desc=f"Slicing into {bars}-bar loops...")
145
  num_loops = len(y) // loop_duration_samples
146
- if num_loops == 0:
147
- print(f"Audio for {stem_name} is too short for {bars}-bar loops at {bpm_int} BPM.")
148
- return None, None
149
  for i in range(num_loops):
150
- start_sample = i * loop_duration_samples
151
- end_sample = start_sample + loop_duration_samples
152
  slice_data = y[start_sample:end_sample]
153
  filename = os.path.join(loops_dir, f"{stem_name}_{bars}bar_loop_{i+1:03d}_{bpm_int}bpm.wav")
154
  sf.write(filename, slice_data, sample_rate, subtype='PCM_16')
155
  output_files.append(filename)
156
  if progress_fn and num_loops > 1:
157
  progress_fn(0.4 + (i / (num_loops - 1) * 0.6), desc=f"Exporting loop {i+1}/{num_loops}...")
158
-
159
- if not output_files:
160
- return None, None
161
-
162
  return output_files, loops_dir
163
 
164
  async def slice_all_and_zip_real(vocals, drums, bass, other, loop_choice, sensitivity, progress=gr.Progress(track_tqdm=True)):
165
- # Slices all available stems and creates a zip pack
166
  log_history = "Starting batch slice...\n"
167
- yield { status_log: log_history, progress_bar: progress(0, desc="Starting...", visible=True) }
 
168
  await asyncio.sleep(0.1)
169
-
170
  stems_to_process = {"vocals": vocals, "drums": drums, "bass": bass, "other": other}
171
  zip_path = "Loop_Architect_Pack.zip"
172
-
173
  num_stems = sum(1 for data in stems_to_process.values() if data is not None)
174
  if num_stems == 0: raise gr.Error("No stems to process! Please separate stems first.")
175
-
176
  all_temp_dirs = []
177
-
178
- with zipfile.ZipFile(zip_path, 'w') as zf:
179
- processed_count = 0
180
- for name, data in stems_to_process.items():
181
- if data is not None:
182
- log_history += f"--- Slicing {name} stem ---\n"; yield { status_log: log_history }
183
-
184
- def update_main_progress(p, desc=""):
185
- overall_progress = (processed_count + p) / num_stems
186
- progress(overall_progress, desc=f"Slicing {name}: {desc}")
187
-
188
- sliced_files, temp_dir = slice_stem_real((data[0], data[1]), loop_choice, sensitivity, name, progress_fn=update_main_progress)
189
-
190
- if sliced_files:
191
- log_history += f"Generated {len(sliced_files)} slices for {name}.\n"; yield { status_log: log_history }
192
- all_temp_dirs.append(temp_dir)
193
- for loop_file in sliced_files:
194
- arcname = os.path.join(name, os.path.basename(loop_file))
195
- zf.write(loop_file, arcname)
196
- else:
197
- log_history += f"No slices generated for {name}.\n"; yield { status_log: log_history }
198
-
199
- processed_count += 1
200
- yield { status_log: log_history, progress_bar: progress(processed_count / num_stems, desc=f"Finished {name}") }
201
 
202
- log_history += "Packaging complete!\n"; yield { status_log: log_history, progress_bar: progress(1, desc="Pack Ready!", visible=False) }
203
-
204
- for d in all_temp_dirs:
205
- if d and os.path.exists(d):
206
- shutil.rmtree(d)
207
-
208
- yield { download_zip_file: gr.update(value=zip_path, visible=True), status_log: log_history + "✅ Pack ready for download!"}
209
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
 
211
  # --- Create the full Gradio Interface ---
212
  with gr.Blocks(theme=gr.themes.Default(primary_hue="blue", secondary_hue="red")) as demo:
@@ -227,7 +202,7 @@ with gr.Blocks(theme=gr.themes.Default(primary_hue="blue", secondary_hue="red"))
227
  download_zip_file = gr.File(label="Download Your Loop Pack", visible=False)
228
 
229
  gr.Markdown("### Status")
230
- # Removed label from gr.Progress
231
  progress_bar = gr.Progress(visible=False)
232
  status_log = gr.Textbox(label="Status Log", lines=10, interactive=False)
233
 
@@ -250,17 +225,13 @@ with gr.Blocks(theme=gr.themes.Default(primary_hue="blue", secondary_hue="red"))
250
 
251
  # --- Define Event Listeners ---
252
  def slice_and_display(stem_data, loop_choice, sensitivity, stem_name):
253
- # Wrapper to handle progress display for single slice buttons
254
  log_history = f"Slicing {stem_name}...\n"
255
- # Use a dictionary to update multiple components
256
- yield {
257
- status_log: log_history,
258
- progress_bar: gr.update(value=0, visible=True, label=f"Slicing {stem_name}...")
259
- }
260
 
261
- # Define how slice_stem_real updates the progress bar
262
  def update_single_progress(p, desc=""):
263
- progress_bar.update(value=p, label=desc, visible=True)
264
 
265
  files, temp_dir = slice_stem_real(stem_data, loop_choice, sensitivity, stem_name, progress_fn=update_single_progress)
266
 
@@ -273,11 +244,10 @@ with gr.Blocks(theme=gr.themes.Default(primary_hue="blue", secondary_hue="red"))
273
  progress_bar: gr.update(visible=False) # Hide progress bar when done
274
  }
275
 
276
-
277
  submit_event = submit_button.click(
278
  fn=separate_stems,
279
  inputs=[audio_input, stem_options],
280
- # Corrected outputs: progress_bar removed from here
281
  outputs=[vocals_output, drums_output, bass_output, other_output, status_log]
282
  )
283
 
@@ -292,7 +262,7 @@ with gr.Blocks(theme=gr.themes.Default(primary_hue="blue", secondary_hue="red"))
292
  slice_all_event = slice_all_button.click(
293
  fn=slice_all_and_zip_real,
294
  inputs=[vocals_output, drums_output, bass_output, other_output, loop_options_radio, sensitivity_slider],
295
- # Corrected outputs: progress_bar removed from here
296
  outputs=[download_zip_file, download_zip_file, status_log]
297
  )
298
 
 
 
1
  import gradio as gr
2
  import os
3
  import shutil
 
11
  import tempfile
12
  import matplotlib.pyplot as plt
13
  import matplotlib
14
+ 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),
 
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...") }
 
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:
 
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]
 
86
  }
87
  except Exception as e:
88
  print(f"An error occurred during separation: {e}")
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
 
96
  if progress_fn: progress_fn(0.1, desc="Detecting BPM...")
97
  tempo, beats = librosa.beat.beat_track(y=y_mono, sr=sample_rate)
98
+ bpm = 120 if tempo is None else np.round(tempo); bpm_int = int(bpm.item())
 
99
  if bpm_int == 0: bpm_int = 120; print("BPM detection failed, defaulting to 120 BPM.")
100
  print(f"Detected BPM for {stem_name}: {bpm_int}")
101
+ output_files = []; loops_dir = tempfile.mkdtemp()
 
 
 
102
  if "One-Shots" in loop_choice:
103
  if progress_fn: progress_fn(0.3, desc="Finding transients...")
104
  onset_frames = librosa.onset.onset_detect(y=y_mono, sr=sample_rate, delta=sensitivity, wait=1, pre_avg=1, post_avg=1, post_max=1)
105
  onset_samples = librosa.frames_to_samples(onset_frames)
 
106
  if progress_fn: progress_fn(0.5, desc="Slicing one-shots...")
107
  if len(onset_samples) > 0:
108
  num_onsets = len(onset_samples)
 
114
  output_files.append(filename)
115
  if progress_fn and num_onsets > 1:
116
  progress_fn(0.5 + (i / (num_onsets - 1) * 0.5), desc=f"Exporting slice {i+1}/{num_onsets}...")
117
+ else:
118
+ bars = int(loop_choice.split(" ")[0]); seconds_per_beat = 60.0 / bpm_int; seconds_per_bar = seconds_per_beat * 4
119
+ loop_duration_seconds = seconds_per_bar * bars; loop_duration_samples = int(loop_duration_seconds * sample_rate)
 
 
 
 
120
  if progress_fn: progress_fn(0.4, desc=f"Slicing into {bars}-bar loops...")
121
  num_loops = len(y) // loop_duration_samples
122
+ if num_loops == 0: print(f"Audio for {stem_name} is too short for {bars}-bar loops at {bpm_int} BPM."); return None, None
 
 
123
  for i in range(num_loops):
124
+ start_sample = i * loop_duration_samples; end_sample = start_sample + loop_duration_samples
 
125
  slice_data = y[start_sample:end_sample]
126
  filename = os.path.join(loops_dir, f"{stem_name}_{bars}bar_loop_{i+1:03d}_{bpm_int}bpm.wav")
127
  sf.write(filename, slice_data, sample_rate, subtype='PCM_16')
128
  output_files.append(filename)
129
  if progress_fn and num_loops > 1:
130
  progress_fn(0.4 + (i / (num_loops - 1) * 0.6), desc=f"Exporting loop {i+1}/{num_loops}...")
131
+ if not output_files: return None, None
 
 
 
132
  return output_files, loops_dir
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)
142
  if num_stems == 0: raise gr.Error("No stems to process! Please separate stems first.")
 
143
  all_temp_dirs = []
144
+ try:
145
+ with zipfile.ZipFile(zip_path, 'w') as zf:
146
+ processed_count = 0
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)
185
 
186
  # --- Create the full Gradio Interface ---
187
  with gr.Blocks(theme=gr.themes.Default(primary_hue="blue", secondary_hue="red")) as demo:
 
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
 
 
225
 
226
  # --- Define Event Listeners ---
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
 
 
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
 
 
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