SaltProphet commited on
Commit
488381a
·
verified ·
1 Parent(s): 2e1de7d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +108 -32
app.py CHANGED
@@ -1,3 +1,6 @@
 
 
 
1
 
2
  # 2. Import libraries
3
  import gradio as gr
@@ -19,65 +22,128 @@ matplotlib.use('Agg') # Use a non-interactive backend for plotting
19
 
20
  def update_output_visibility(choice):
21
  if "2 Stems" in choice:
22
- return { vocals_output: gr.update(visible=True), drums_output: gr.update(visible=False), bass_output: gr.update(visible=False), other_output: gr.update(visible=True, label="Instrumental (No Vocals)") }
 
 
 
 
 
23
  elif "4 Stems" in choice:
24
- return { vocals_output: gr.update(visible=True), drums_output: gr.update(visible=True), bass_output: gr.update(visible=True), other_output: gr.update(visible=True, label="Other") }
 
 
 
 
 
25
 
26
  async def separate_stems(audio_file_path, stem_choice, progress=gr.Progress(track_tqdm=True)):
27
  if audio_file_path is None: raise gr.Error("No audio file uploaded!")
28
- progress(0, desc="Starting..."); await asyncio.sleep(0.1)
29
  try:
30
- progress(0.05, desc="Preparing audio file..."); original_filename_base = os.path.basename(audio_file_path).rsplit('.', 1)[0]; stable_input_path = f"stable_input_{original_filename_base}.wav"; shutil.copy(audio_file_path, stable_input_path)
31
- model_arg = "--two-stems=vocals" if "2 Stems" in stem_choice else ""; output_dir = "separated"
 
 
 
 
 
32
  if os.path.exists(output_dir): shutil.rmtree(output_dir)
 
33
  command = f"python3 -m demucs {model_arg} -o \"{output_dir}\" \"{stable_input_path}\""
34
- progress(0.2, desc="Running Demucs..."); process = await asyncio.create_subprocess_shell(command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE); await process.communicate()
35
- if process.returncode != 0: raise gr.Error(f"Demucs failed to run.")
36
- progress(0.8, desc="Locating separated stem files..."); stable_filename_base = os.path.basename(stable_input_path).rsplit('.', 1)[0]; model_folder_name = next(os.walk(output_dir))[1][0]; stems_path = os.path.join(output_dir, model_folder_name, stable_filename_base)
37
- if not os.path.exists(stems_path): raise gr.Error(f"Demucs finished, but the output directory was not found!")
38
- vocals_path = os.path.join(stems_path, "vocals.wav") if os.path.exists(os.path.join(stems_path, "vocals.wav")) else None; drums_path = os.path.join(stems_path, "drums.wav") if os.path.exists(os.path.join(stems_path, "drums.wav")) else None; bass_path = os.path.join(stems_path, "bass.wav") if os.path.exists(os.path.join(stems_path, "bass.wav")) else None
39
- other_filename = "no_vocals.wav" if "2 Stems" in stem_choice else "other.wav"; other_path = os.path.join(stems_path, other_filename) if os.path.exists(os.path.join(stems_path, other_filename)) else None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  os.remove(stable_input_path)
41
  return vocals_path, drums_path, bass_path, other_path
42
  except Exception as e:
43
- print(f"An error occurred: {e}"); raise gr.Error(str(e))
 
44
 
45
  def visualize_slices(stem_audio_data, progress=gr.Progress(track_tqdm=True)):
46
  if stem_audio_data is None:
47
- gr.Warning("This stem is empty. Cannot visualize."); return None, None, None
48
- sample_rate, y_int = stem_audio_data; y = librosa.util.buf_to_float(y_int);
49
- progress(0.3, desc="Finding transients..."); onset_frames = librosa.onset.onset_detect(y=librosa.to_mono(y.T) if y.ndim > 1 else y, sr=sample_rate, wait=1, pre_avg=1, post_avg=1, post_max=1, delta=0.05)
 
 
 
 
 
50
  onset_times = librosa.frames_to_time(onset_frames, sr=sample_rate)
 
51
  progress(0.7, desc="Generating waveform plot...")
52
- fig, ax = plt.subplots(figsize=(10, 3)); fig.patch.set_facecolor('#1f2937'); ax.set_facecolor('#111827')
 
 
 
53
  librosa.display.waveshow(y, sr=sample_rate, ax=ax, color='#32f6ff', alpha=0.7)
 
54
  for t in onset_times:
55
  ax.axvline(x=t, color='#ff3b3b', linestyle='--', linewidth=1)
56
- ax.tick_params(colors='gray'); ax.xaxis.label.set_color('gray'); ax.yaxis.label.set_color('gray'); ax.set_xlabel("Time (s)"); ax.set_ylabel("Amplitude"); ax.set_title("Detected Slices", color='white'); plt.tight_layout()
 
 
 
 
 
 
 
 
57
  progress(1, desc="Done!")
58
  return fig, onset_times, stem_audio_data
59
 
60
  def preview_slice(active_stem_audio, onset_times, evt: gr.SelectData):
61
  if active_stem_audio is None or onset_times is None:
62
  return None
63
- sample_rate, y = active_stem_audio; clicked_time = evt.index[0]
64
- start_time = 0; end_time = len(y) / sample_rate
 
 
 
 
 
65
  for i, t in enumerate(onset_times):
66
  if t > clicked_time:
67
- end_time = t; break
 
68
  start_time = t
69
- start_sample = librosa.time_to_samples(start_time, sr=sample_rate); end_sample = librosa.time_to_samples(end_time, sr=sample_rate)
 
 
 
70
  sliced_audio = y[start_sample:end_sample]
71
  return (sample_rate, sliced_audio)
72
 
73
- # --- NEW FUNCTIONS FOR MANAGING THE PACK ---
74
  def add_slice_to_pack(current_preview, selection_list):
75
  if current_preview is None:
76
  gr.Warning("No slice is being previewed to add.")
77
- return selection_list
78
  selection_list.append(current_preview)
79
  gr.Info(f"Slice added! You now have {len(selection_list)} slices in your pack.")
80
- return selection_list
81
 
82
  def clear_selection():
83
  gr.Info("Selection cleared.")
@@ -103,6 +169,7 @@ def create_final_pack(selection_list, progress=gr.Progress(track_tqdm=True)):
103
  progress(1, desc="Pack Ready!")
104
  return zip_path, gr.update(visible=True)
105
 
 
106
  # --- Create the full Gradio Interface ---
107
  with gr.Blocks(theme=gr.themes.Default(primary_hue="blue", secondary_hue="red")) as demo:
108
  gr.Markdown("# 🎵 Loop Architect")
@@ -114,7 +181,8 @@ with gr.Blocks(theme=gr.themes.Default(primary_hue="blue", secondary_hue="red"))
114
 
115
  with gr.Row():
116
  with gr.Column(scale=1):
117
- gr.Markdown("### 1. Separate Stems"); audio_input = gr.Audio(type="filepath", label="Upload a Track")
 
118
  stem_options = gr.Radio(["4 Stems (Vocals, Drums, Bass, Other)", "2 Stems (Vocals + Instrumental)"], label="Separation Type", value="4 Stems (Vocals, Drums, Bass, Other)")
119
  submit_button = gr.Button("Separate Stems")
120
  with gr.Accordion("Slicing Options", open=True):
@@ -124,10 +192,18 @@ with gr.Blocks(theme=gr.themes.Default(primary_hue="blue", secondary_hue="red"))
124
 
125
  with gr.Column(scale=2):
126
  with gr.Accordion("Separated Stems", open=True):
127
- with gr.Row(): vocals_output = gr.Audio(label="Vocals", scale=4); slice_vocals_btn = gr.Button("Visualize Slices", scale=1)
128
- with gr.Row(): drums_output = gr.Audio(label="Drums", scale=4); slice_drums_btn = gr.Button("Visualize Slices", scale=1)
129
- with gr.Row(): bass_output = gr.Audio(label="Bass", scale=4); slice_bass_btn = gr.Button("Visualize Slices", scale=1)
130
- with gr.Row(): other_output = gr.Audio(label="Other / Instrumental", scale=4); slice_other_btn = gr.Button("Visualize Slices", scale=1)
 
 
 
 
 
 
 
 
131
 
132
  gr.Markdown("### Slice Editor")
133
  slice_plot = gr.Plot(label="Click a region on the waveform to preview a slice")
@@ -151,12 +227,12 @@ with gr.Blocks(theme=gr.themes.Default(primary_hue="blue", secondary_hue="red"))
151
  slice_bass_btn.click(fn=visualize_slices, inputs=bass_output, outputs=[slice_plot, onset_times_state, active_stem_state])
152
  slice_other_btn.click(fn=visualize_slices, inputs=other_output, outputs=[slice_plot, onset_times_state, active_stem_state])
153
 
 
154
  slice_plot.select(fn=preview_slice, inputs=[active_stem_state, onset_times_state], outputs=preview_player)
155
 
156
- add_to_pack_btn.click(fn=add_slice_to_pack, inputs=[preview_player, selected_slices_state], outputs=selected_slices_state)
157
- selected_slices_state.change(fn=lambda x: x, inputs=selected_slices_state, outputs=selected_gallery) # Update gallery when state changes
158
  clear_selection_btn.click(fn=clear_selection, outputs=selected_slices_state)
159
  create_pack_btn.click(fn=create_final_pack, inputs=selected_slices_state, outputs=[download_zip_file, download_zip_file])
160
 
161
  # --- Launch the UI ---
162
- demo.launch(debug=True)
 
1
+ # 1. Install all necessary libraries for the full application
2
+ # This line is for Colab. On Hugging Face, these should be in your requirements.txt
3
+ # !pip install gradio "demucs>=4.0.0" librosa soundfile matplotlib
4
 
5
  # 2. Import libraries
6
  import gradio as gr
 
22
 
23
  def update_output_visibility(choice):
24
  if "2 Stems" in choice:
25
+ return {
26
+ vocals_output: gr.update(visible=True),
27
+ drums_output: gr.update(visible=False),
28
+ bass_output: gr.update(visible=False),
29
+ other_output: gr.update(visible=True, label="Instrumental (No Vocals)")
30
+ }
31
  elif "4 Stems" in choice:
32
+ return {
33
+ vocals_output: gr.update(visible=True),
34
+ drums_output: gr.update(visible=True),
35
+ bass_output: gr.update(visible=True),
36
+ other_output: gr.update(visible=True, label="Other")
37
+ }
38
 
39
  async def separate_stems(audio_file_path, stem_choice, progress=gr.Progress(track_tqdm=True)):
40
  if audio_file_path is None: raise gr.Error("No audio file uploaded!")
41
+ progress(0, desc="Starting...")
42
  try:
43
+ progress(0.05, desc="Preparing audio file...")
44
+ original_filename_base = os.path.basename(audio_file_path).rsplit('.', 1)[0]
45
+ stable_input_path = f"stable_input_{original_filename_base}.wav"
46
+ shutil.copy(audio_file_path, stable_input_path)
47
+
48
+ model_arg = "--two-stems=vocals" if "2 Stems" in stem_choice else ""
49
+ output_dir = "separated"
50
  if os.path.exists(output_dir): shutil.rmtree(output_dir)
51
+
52
  command = f"python3 -m demucs {model_arg} -o \"{output_dir}\" \"{stable_input_path}\""
53
+ progress(0.2, desc="Running Demucs (this may take a minute)...")
54
+
55
+ process = await asyncio.create_subprocess_shell(
56
+ command,
57
+ stdout=asyncio.subprocess.PIPE,
58
+ stderr=asyncio.subprocess.PIPE)
59
+
60
+ stdout, stderr = await process.communicate()
61
+
62
+ if process.returncode != 0:
63
+ raise gr.Error(f"Demucs failed to run. Error: {stderr.decode()[:500]}")
64
+
65
+ progress(0.8, desc="Locating separated stem files...")
66
+ stable_filename_base = os.path.basename(stable_input_path).rsplit('.', 1)[0]
67
+ model_folder_name = next(os.walk(output_dir))[1][0]
68
+ stems_path = os.path.join(output_dir, model_folder_name, stable_filename_base)
69
+
70
+ if not os.path.exists(stems_path):
71
+ raise gr.Error(f"Demucs finished, but the output directory was not found!")
72
+
73
+ vocals_path = os.path.join(stems_path, "vocals.wav") if os.path.exists(os.path.join(stems_path, "vocals.wav")) else None
74
+ drums_path = os.path.join(stems_path, "drums.wav") if os.path.exists(os.path.join(stems_path, "drums.wav")) else None
75
+ bass_path = os.path.join(stems_path, "bass.wav") if os.path.exists(os.path.join(stems_path, "bass.wav")) else None
76
+ other_filename = "no_vocals.wav" if "2 Stems" in stem_choice else "other.wav"
77
+ other_path = os.path.join(stems_path, other_filename) if os.path.exists(os.path.join(stems_path, other_filename)) else None
78
+
79
  os.remove(stable_input_path)
80
  return vocals_path, drums_path, bass_path, other_path
81
  except Exception as e:
82
+ print(f"An error occurred: {e}")
83
+ raise gr.Error(str(e))
84
 
85
  def visualize_slices(stem_audio_data, progress=gr.Progress(track_tqdm=True)):
86
  if stem_audio_data is None:
87
+ gr.Warning("This stem is empty. Cannot visualize.")
88
+ return None, None, None
89
+
90
+ sample_rate, y_int = stem_audio_data
91
+ y = librosa.util.buf_to_float(y_int)
92
+
93
+ progress(0.3, desc="Finding transients...")
94
+ onset_frames = librosa.onset.onset_detect(y=librosa.to_mono(y.T) if y.ndim > 1 else y, sr=sample_rate, wait=1, pre_avg=1, post_avg=1, post_max=1, delta=0.05)
95
  onset_times = librosa.frames_to_time(onset_frames, sr=sample_rate)
96
+
97
  progress(0.7, desc="Generating waveform plot...")
98
+ fig, ax = plt.subplots(figsize=(10, 3))
99
+ fig.patch.set_facecolor('#1f2937')
100
+ ax.set_facecolor('#111827')
101
+
102
  librosa.display.waveshow(y, sr=sample_rate, ax=ax, color='#32f6ff', alpha=0.7)
103
+
104
  for t in onset_times:
105
  ax.axvline(x=t, color='#ff3b3b', linestyle='--', linewidth=1)
106
+
107
+ ax.tick_params(colors='gray')
108
+ ax.xaxis.label.set_color('gray')
109
+ ax.yaxis.label.set_color('gray')
110
+ ax.set_xlabel("Time (s)")
111
+ ax.set_ylabel("Amplitude")
112
+ ax.set_title("Detected Slices", color='white')
113
+ plt.tight_layout()
114
+
115
  progress(1, desc="Done!")
116
  return fig, onset_times, stem_audio_data
117
 
118
  def preview_slice(active_stem_audio, onset_times, evt: gr.SelectData):
119
  if active_stem_audio is None or onset_times is None:
120
  return None
121
+
122
+ sample_rate, y = active_stem_audio
123
+ clicked_time = evt.index[0]
124
+
125
+ start_time = 0
126
+ end_time = len(y) / sample_rate
127
+
128
  for i, t in enumerate(onset_times):
129
  if t > clicked_time:
130
+ end_time = t
131
+ break
132
  start_time = t
133
+
134
+ start_sample = librosa.time_to_samples(start_time, sr=sample_rate)
135
+ end_sample = librosa.time_to_samples(end_time, sr=sample_rate)
136
+
137
  sliced_audio = y[start_sample:end_sample]
138
  return (sample_rate, sliced_audio)
139
 
 
140
  def add_slice_to_pack(current_preview, selection_list):
141
  if current_preview is None:
142
  gr.Warning("No slice is being previewed to add.")
143
+ return selection_list, selection_list
144
  selection_list.append(current_preview)
145
  gr.Info(f"Slice added! You now have {len(selection_list)} slices in your pack.")
146
+ return selection_list, selection_list
147
 
148
  def clear_selection():
149
  gr.Info("Selection cleared.")
 
169
  progress(1, desc="Pack Ready!")
170
  return zip_path, gr.update(visible=True)
171
 
172
+
173
  # --- Create the full Gradio Interface ---
174
  with gr.Blocks(theme=gr.themes.Default(primary_hue="blue", secondary_hue="red")) as demo:
175
  gr.Markdown("# 🎵 Loop Architect")
 
181
 
182
  with gr.Row():
183
  with gr.Column(scale=1):
184
+ gr.Markdown("### 1. Separate Stems")
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
  with gr.Accordion("Slicing Options", open=True):
 
192
 
193
  with gr.Column(scale=2):
194
  with gr.Accordion("Separated Stems", open=True):
195
+ with gr.Row():
196
+ vocals_output = gr.Audio(label="Vocals", scale=4)
197
+ slice_vocals_btn = gr.Button("Visualize Slices", scale=1)
198
+ with gr.Row():
199
+ drums_output = gr.Audio(label="Drums", scale=4)
200
+ slice_drums_btn = gr.Button("Visualize Slices", scale=1)
201
+ with gr.Row():
202
+ bass_output = gr.Audio(label="Bass", scale=4)
203
+ slice_bass_btn = gr.Button("Visualize Slices", scale=1)
204
+ with gr.Row():
205
+ other_output = gr.Audio(label="Other / Instrumental", scale=4)
206
+ slice_other_btn = gr.Button("Visualize Slices", scale=1)
207
 
208
  gr.Markdown("### Slice Editor")
209
  slice_plot = gr.Plot(label="Click a region on the waveform to preview a slice")
 
227
  slice_bass_btn.click(fn=visualize_slices, inputs=bass_output, outputs=[slice_plot, onset_times_state, active_stem_state])
228
  slice_other_btn.click(fn=visualize_slices, inputs=other_output, outputs=[slice_plot, onset_times_state, active_stem_state])
229
 
230
+ # Correct event listener for plot interaction
231
  slice_plot.select(fn=preview_slice, inputs=[active_stem_state, onset_times_state], outputs=preview_player)
232
 
233
+ add_to_pack_btn.click(fn=add_slice_to_pack, inputs=[preview_player, selected_slices_state], outputs=[selected_slices_state, selected_gallery])
 
234
  clear_selection_btn.click(fn=clear_selection, outputs=selected_slices_state)
235
  create_pack_btn.click(fn=create_final_pack, inputs=selected_slices_state, outputs=[download_zip_file, download_zip_file])
236
 
237
  # --- Launch the UI ---
238
+ demo.launch()