SaltProphet commited on
Commit
ac29ba7
·
verified ·
1 Parent(s): 20b72e8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +70 -158
app.py CHANGED
@@ -1,6 +1,5 @@
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
@@ -16,25 +15,24 @@ import zipfile
16
  import tempfile
17
  import matplotlib.pyplot as plt
18
  import matplotlib
19
- matplotlib.use('Agg') # Use a non-interactive backend for plotting
20
 
21
  # --- Helper/Processing Functions ---
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!")
@@ -45,185 +43,99 @@ async def separate_stems(audio_file_path, stem_choice, progress=gr.Progress(trac
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 slice_stem_real(stem_audio_data, loop_choice, sensitivity, progress=gr.Progress(track_tqdm=True)):
86
- if stem_audio_data is None:
87
- gr.Warning("This stem is empty. Cannot slice.")
88
- return None, None
89
-
90
- sample_rate, y_int = stem_audio_data
91
- y = librosa.util.buf_to_float(y_int)
92
- y_mono = librosa.to_mono(y.T) if y.ndim > 1 else y
93
-
94
- progress(0.1, desc="Detecting BPM...")
95
- tempo, beats = librosa.beat.beat_track(y=y_mono, sr=sample_rate)
96
- bpm = 120 if tempo is None else np.round(tempo)
97
- bpm_int = int(bpm.item())
98
-
99
- if bpm_int == 0:
100
- bpm_int = 120
101
- gr.Warning("BPM detection failed, defaulting to 120 BPM.")
102
- print(f"Detected BPM: {bpm_int}")
103
-
104
- output_files = []
105
- loops_dir = tempfile.mkdtemp()
106
-
107
- if "One-Shots" in loop_choice:
108
- progress(0.3, desc="Finding transients...")
109
- 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)
110
- onset_samples = librosa.frames_to_samples(onset_frames)
111
-
112
- progress(0.5, desc="Slicing one-shots...")
113
- if len(onset_samples) > 0:
114
- for i, start_sample in enumerate(onset_samples):
115
- end_sample = onset_samples[i+1] if i+1 < len(onset_samples) else len(y)
116
- slice_data = y[start_sample:end_sample]
117
- filename = os.path.join(loops_dir, f"one_shot_{i+1:03d}.wav")
118
- sf.write(filename, slice_data, sample_rate, subtype='PCM_16')
119
- output_files.append(filename)
120
- if len(onset_samples) > 1:
121
- progress(0.5 + (i / (len(onset_samples) - 1) * 0.5), desc=f"Exporting slice {i+1}...")
122
- else: # Handle bar loops
123
- bars = int(loop_choice.split(" ")[0])
124
- seconds_per_beat = 60.0 / bpm_int
125
- seconds_per_bar = seconds_per_beat * 4
126
- loop_duration_seconds = seconds_per_bar * bars
127
- loop_duration_samples = int(loop_duration_seconds * sample_rate)
128
-
129
- progress(0.4, desc=f"Slicing into {bars}-bar loops...")
130
- num_loops = len(y) // loop_duration_samples
131
- if num_loops == 0:
132
- gr.Warning(f"Audio is too short to create a {bars}-bar loop at {bpm_int} BPM.")
133
- return None, None
134
- for i in range(num_loops):
135
- start_sample = i * loop_duration_samples
136
- end_sample = start_sample + loop_duration_samples
137
- slice_data = y[start_sample:end_sample]
138
- filename = os.path.join(loops_dir, f"{bars}bar_loop_{i+1:03d}_{bpm_int}bpm.wav")
139
- sf.write(filename, slice_data, sample_rate, subtype='PCM_16')
140
- output_files.append(filename)
141
- if num_loops > 1:
142
- progress(0.4 + (i / (num_loops - 1) * 0.6), desc=f"Exporting loop {i+1}...")
143
-
144
- if not output_files:
145
- gr.Warning("No loops or one-shots could be generated.")
146
- return None, None
147
-
148
- return output_files, loops_dir
149
-
150
- async def slice_all_and_zip_real(vocals, drums, bass, other, loop_choice, sensitivity, progress=gr.Progress(track_tqdm=True)):
151
- progress(0, desc="Starting batch slice...")
152
- await asyncio.sleep(0.1)
153
- stems_to_process = {"vocals": vocals, "drums": drums, "bass": bass, "other": other}
154
-
155
- zip_path = "Loop_Architect_Pack.zip"
156
-
157
- num_stems = sum(1 for data in stems_to_process.values() if data is not None)
158
- if num_stems == 0: raise gr.Error("No stems to process! Please separate stems first.")
159
-
160
- with zipfile.ZipFile(zip_path, 'w') as zf:
161
- processed_count = 0
162
- for name, data in stems_to_process.items():
163
- if data is not None:
164
- def update_main_progress(p, desc=""):
165
- overall_progress = (processed_count + p) / num_stems
166
- progress(overall_progress, desc=f"Slicing {name}: {desc}")
167
-
168
- sliced_files, temp_dir = slice_stem_real((data[0], data[1]), loop_choice, sensitivity, progress=update_main_progress)
169
-
170
- if sliced_files:
171
- for loop_file in sliced_files:
172
- arcname = os.path.join(name, os.path.basename(loop_file))
173
- zf.write(loop_file, arcname)
174
-
175
- if temp_dir and os.path.exists(temp_dir):
176
- shutil.rmtree(temp_dir)
177
- processed_count += 1
178
-
179
- progress(1, desc="Pack Ready!")
180
- return zip_path, gr.update(visible=True)
181
-
182
-
183
  # --- Create the full Gradio Interface ---
184
  with gr.Blocks(theme=gr.themes.Default(primary_hue="blue", secondary_hue="red")) as demo:
185
  gr.Markdown("# 🎵 Loop Architect")
 
186
  with gr.Row():
187
  with gr.Column(scale=1):
188
  gr.Markdown("### 1. Separate Stems")
189
  audio_input = gr.Audio(type="filepath", label="Upload a Track")
190
- stem_options = gr.Radio(["4 Stems (Vocals, Drums, Bass, Other)", "2 Stems (Vocals + Instrumental)"], label="Separation Type", value="4 Stems (Vocals, Drums, Bass, Other)")
 
 
 
 
 
 
 
191
  submit_button = gr.Button("Separate Stems")
192
- with gr.Accordion("Slicing Options", open=True):
193
- loop_options_radio = gr.Radio(["One-Shots (All Transients)", "4 Bar Loops", "8 Bar Loops"], label="Loop Length", value="One-Shots (All Transients)")
194
- sensitivity_slider = gr.Slider(minimum=0.01, maximum=0.5, value=0.05, step=0.01, label="One-Shot Sensitivity")
195
- gr.Markdown("### 3. Create Pack")
196
- slice_all_button = gr.Button("Slice All Stems & Create Pack", variant="primary")
197
- download_zip_file = gr.File(label="Download Your Loop Pack", visible=False)
198
  with gr.Column(scale=2):
199
  with gr.Accordion("Separated Stems", open=True):
 
200
  with gr.Row():
201
  vocals_output = gr.Audio(label="Vocals", scale=4)
202
- slice_vocals_btn = gr.Button("Slice", scale=1)
203
  with gr.Row():
204
- drums_output = gr.Audio(label="Drums", scale=4)
205
- slice_drums_btn = gr.Button("Slice", scale=1)
 
 
 
206
  with gr.Row():
207
- bass_output = gr.Audio(label="Bass", scale=4)
208
- slice_bass_btn = gr.Button("Slice", scale=1)
209
  with gr.Row():
210
- other_output = gr.Audio(label="Other / Instrumental", scale=4)
211
- slice_other_btn = gr.Button("Slice", scale=1)
212
- gr.Markdown("### Sliced Loops / Samples")
213
- loop_gallery = gr.Gallery(label="Generated Loops", columns=8, object_fit="contain", height="auto")
214
 
215
  # --- Define Event Listeners ---
216
- def slice_and_display(stem_data, loop_choice, sensitivity):
217
- files, _ = slice_stem_real(stem_data, loop_choice, sensitivity)
218
- return files
219
-
220
- submit_button.click(fn=separate_stems, inputs=[audio_input, stem_options], outputs=[vocals_output, drums_output, bass_output, other_output])
221
- stem_options.change(fn=update_output_visibility, inputs=[stem_options], outputs=[vocals_output, drums_output, bass_output, other_output])
222
- slice_vocals_btn.click(fn=slice_and_display, inputs=[vocals_output, loop_options_radio, sensitivity_slider], outputs=loop_gallery)
223
- slice_drums_btn.click(fn=slice_and_display, inputs=[drums_output, loop_options_radio, sensitivity_slider], outputs=loop_gallery)
224
- slice_bass_btn.click(fn=slice_and_display, inputs=[bass_output, loop_options_radio, sensitivity_slider], outputs=loop_gallery)
225
- slice_other_btn.click(fn=slice_and_display, inputs=[other_output, loop_options_radio, sensitivity_slider], outputs=loop_gallery)
226
- slice_all_button.click(fn=slice_all_and_zip_real, inputs=[vocals_output, drums_output, bass_output, other_output, loop_options_radio, sensitivity_slider], outputs=[download_zip_file, download_zip_file])
227
-
 
228
  # --- Launch the UI ---
229
- demo.launch()
 
1
+ # 1. Install necessary libraries
2
+ !pip install gradio "demucs>=4.0.0" librosa soundfile matplotlib
 
3
 
4
  # 2. Import libraries
5
  import gradio as gr
 
15
  import tempfile
16
  import matplotlib.pyplot as plt
17
  import matplotlib
18
+ matplotlib.use('Agg')
19
 
20
  # --- Helper/Processing Functions ---
21
 
22
  def update_output_visibility(choice):
23
+ # Now handles 2, 4, and 6 stem visibility
24
+ is_6_stem = "6 Stems" in choice
25
+ is_4_stem = "4 Stems" in choice
26
+ is_2_stem = "2 Stems" in choice
27
+
28
+ return {
29
+ vocals_output: gr.update(visible=True),
30
+ drums_output: gr.update(visible=is_4_stem or is_6_stem),
31
+ bass_output: gr.update(visible=is_4_stem or is_6_stem),
32
+ guitar_output: gr.update(visible=is_6_stem), # Guitar visible only for 6 stems
33
+ piano_output: gr.update(visible=is_6_stem), # Piano visible only for 6 stems
34
+ other_output: gr.update(visible=True, label="Instrumental (No Vocals)" if is_2_stem else "Other")
35
+ }
 
36
 
37
  async def separate_stems(audio_file_path, stem_choice, progress=gr.Progress(track_tqdm=True)):
38
  if audio_file_path is None: raise gr.Error("No audio file uploaded!")
 
43
  stable_input_path = f"stable_input_{original_filename_base}.wav"
44
  shutil.copy(audio_file_path, stable_input_path)
45
 
46
+ # Determine Demucs model based on choice
47
+ model_name = "htdemucs" # Default to 4-stem model
48
+ model_arg = ""
49
+ if "2 Stems" in stem_choice:
50
+ model_arg = "--two-stems=vocals"
51
+ elif "6 Stems" in stem_choice:
52
+ model_name = "htdemucs_6s" # Use the 6-stem model
53
+
54
  output_dir = "separated"
55
  if os.path.exists(output_dir): shutil.rmtree(output_dir)
56
 
57
+ # Use -n flag to specify the model name
58
+ command = f"python3 -m demucs -n {model_name} {model_arg} -o \"{output_dir}\" \"{stable_input_path}\""
59
+ progress(0.2, desc=f"Running Demucs ({model_name})...")
 
 
 
 
60
 
61
+ process = await asyncio.create_subprocess_shell(command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
62
  stdout, stderr = await process.communicate()
63
 
64
  if process.returncode != 0:
65
+ raise gr.Error(f"Demucs failed. Error: {stderr.decode()[:500]}")
66
 
67
  progress(0.8, desc="Locating separated stem files...")
68
  stable_filename_base = os.path.basename(stable_input_path).rsplit('.', 1)[0]
69
+ # Demucs output folder now matches the model name
70
+ stems_path = os.path.join(output_dir, model_name, stable_filename_base)
71
 
72
  if not os.path.exists(stems_path):
73
+ raise gr.Error(f"Demucs finished, but output directory '{stems_path}' not found!")
74
 
75
+ # Check for all possible stem files
76
  vocals_path = os.path.join(stems_path, "vocals.wav") if os.path.exists(os.path.join(stems_path, "vocals.wav")) else None
77
  drums_path = os.path.join(stems_path, "drums.wav") if os.path.exists(os.path.join(stems_path, "drums.wav")) else None
78
  bass_path = os.path.join(stems_path, "bass.wav") if os.path.exists(os.path.join(stems_path, "bass.wav")) else None
79
+ guitar_path = os.path.join(stems_path, "guitar.wav") if os.path.exists(os.path.join(stems_path, "guitar.wav")) else None
80
+ piano_path = os.path.join(stems_path, "piano.wav") if os.path.exists(os.path.join(stems_path, "piano.wav")) else None
81
+
82
+ # Handle 'other' vs 'no_vocals' based on model
83
  other_filename = "no_vocals.wav" if "2 Stems" in stem_choice else "other.wav"
84
  other_path = os.path.join(stems_path, other_filename) if os.path.exists(os.path.join(stems_path, other_filename)) else None
85
 
86
  os.remove(stable_input_path)
87
+ return vocals_path, drums_path, bass_path, guitar_path, piano_path, other_path
88
  except Exception as e:
89
  print(f"An error occurred: {e}")
90
  raise gr.Error(str(e))
91
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  # --- Create the full Gradio Interface ---
93
  with gr.Blocks(theme=gr.themes.Default(primary_hue="blue", secondary_hue="red")) as demo:
94
  gr.Markdown("# 🎵 Loop Architect")
95
+
96
  with gr.Row():
97
  with gr.Column(scale=1):
98
  gr.Markdown("### 1. Separate Stems")
99
  audio_input = gr.Audio(type="filepath", label="Upload a Track")
100
+ # Added 6-stem option
101
+ stem_options = gr.Radio(
102
+ ["6 Stems (Vocals, Drums, Bass, Guitar, Piano, Other)",
103
+ "4 Stems (Vocals, Drums, Bass, Other)",
104
+ "2 Stems (Vocals + Instrumental)"],
105
+ label="Separation Type",
106
+ value="4 Stems (Vocals, Drums, Bass, Other)" # Default still 4 stems
107
+ )
108
  submit_button = gr.Button("Separate Stems")
109
+
 
 
 
 
 
110
  with gr.Column(scale=2):
111
  with gr.Accordion("Separated Stems", open=True):
112
+ # Added players for guitar and piano, initially hidden
113
  with gr.Row():
114
  vocals_output = gr.Audio(label="Vocals", scale=4)
 
115
  with gr.Row():
116
+ drums_output = gr.Audio(label="Drums", scale=4, visible=True)
117
+ with gr.Row():
118
+ bass_output = gr.Audio(label="Bass", scale=4, visible=True)
119
+ with gr.Row():
120
+ guitar_output = gr.Audio(label="Guitar", scale=4, visible=False)
121
  with gr.Row():
122
+ piano_output = gr.Audio(label="Piano", scale=4, visible=False)
 
123
  with gr.Row():
124
+ other_output = gr.Audio(label="Other / Instrumental", scale=4, visible=True)
 
 
 
125
 
126
  # --- Define Event Listeners ---
127
+ submit_button.click(
128
+ fn=separate_stems,
129
+ inputs=[audio_input, stem_options],
130
+ # Update outputs to include new players
131
+ outputs=[vocals_output, drums_output, bass_output, guitar_output, piano_output, other_output]
132
+ )
133
+ stem_options.change(
134
+ fn=update_output_visibility,
135
+ inputs=stem_options,
136
+ # Update outputs to include new players
137
+ outputs=[vocals_output, drums_output, bass_output, guitar_output, piano_output, other_output]
138
+ )
139
+
140
  # --- Launch the UI ---
141
+ demo.launch()