SaltProphet commited on
Commit
d3d37ec
·
verified ·
1 Parent(s): c61c187

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +13 -216
app.py CHANGED
@@ -1,216 +1,13 @@
1
- import gradio as gr
2
- import os
3
- import shutil
4
- import zipfile
5
- import librosa
6
- import numpy as np
7
- from pydub import AudioSegment
8
- from moviepy.editor import AudioFileClip, ImageClip, CompositeVideoClip
9
- import subprocess
10
- from pathlib import Path
11
- import sys
12
-
13
- # --- Configuration ---
14
- OUTPUT_DIR = Path("nightpulse_output")
15
- TEMP_DIR = Path("temp_processing")
16
-
17
- # --- Core Logic Functions ---
18
-
19
- def analyze_and_separate(audio_file):
20
- """Phase 1: Separate 6 Stems (Drums, Bass, Guitar, Piano, Vocals, Other)"""
21
- try:
22
- if not audio_file:
23
- raise ValueError("No audio file provided.")
24
-
25
- # Cleanup
26
- if OUTPUT_DIR.exists(): shutil.rmtree(OUTPUT_DIR)
27
- if TEMP_DIR.exists(): shutil.rmtree(TEMP_DIR)
28
- (OUTPUT_DIR / "Stems").mkdir(parents=True, exist_ok=True)
29
- (OUTPUT_DIR / "Loops").mkdir(parents=True, exist_ok=True)
30
- TEMP_DIR.mkdir(parents=True, exist_ok=True)
31
-
32
- filename = Path(audio_file).stem
33
-
34
- # 1. BPM Detection
35
- print(f"Analyzing {filename}...")
36
- y, sr = librosa.load(audio_file, duration=60, mono=True)
37
- tempo, _ = librosa.beat.beat_track(y=y, sr=sr)
38
-
39
- # Robust BPM logic
40
- if np.ndim(tempo) > 0:
41
- bpm = int(round(tempo[0]))
42
- else:
43
- bpm = int(round(tempo))
44
- print(f"Detected BPM: {bpm}")
45
-
46
- # 2. Demucs Separation (Using 6-Stem Model)
47
- print("Separating stems (6-Stem Model)...")
48
- subprocess.run([
49
- sys.executable, "-m", "demucs", "-n", "htdemucs_6s", "--out", str(TEMP_DIR), audio_file
50
- ], check=True, capture_output=True)
51
-
52
- # 3. Locate Stems
53
- demucs_out = TEMP_DIR / "htdemucs_6s"
54
- track_folder = next(demucs_out.iterdir(), None)
55
- if not track_folder: raise FileNotFoundError("Demucs failed to output files.")
56
-
57
- # Map all 6 stems
58
- drums = track_folder / "drums.wav"
59
- bass = track_folder / "bass.wav"
60
- guitar = track_folder / "guitar.wav"
61
- piano = track_folder / "piano.wav"
62
- vocals = track_folder / "vocals.wav"
63
- other = track_folder / "other.wav"
64
-
65
- # Return paths to the UI and the BPM
66
- return str(drums), str(bass), str(guitar), str(piano), str(other), str(vocals), bpm, str(track_folder)
67
-
68
- except Exception as e:
69
- raise gr.Error(f"Separation Failed: {str(e)}")
70
-
71
- def package_and_export(track_folder_str, bpm, start_offset_sec, cover_art):
72
- """Phase 2: Chop Loops, Generate Video, Zip"""
73
- try:
74
- track_folder = Path(track_folder_str)
75
- # Re-map paths
76
- stems = {
77
- "Drums": track_folder / "drums.wav",
78
- "Bass": track_folder / "bass.wav",
79
- "Guitar": track_folder / "guitar.wav",
80
- "Piano": track_folder / "piano.wav",
81
- "Synths": track_folder / "other.wav",
82
- "Vocals": track_folder / "vocals.wav"
83
- }
84
-
85
- # 1. Save Full Stems (Copy to Stems folder)
86
- for name, path in stems.items():
87
- if path.exists():
88
- shutil.copy(path, OUTPUT_DIR / "Stems" / f"{bpm}BPM_Full_{name}.wav")
89
-
90
- # 2. Create Loops (With User Offset)
91
- ms_per_beat = (60 / bpm) * 1000
92
- eight_bars_ms = ms_per_beat * 4 * 8
93
- start_ms = start_offset_sec * 1000
94
-
95
- created_loops = {}
96
-
97
- def make_loop(src, name):
98
- if not src.exists(): return None
99
- audio = AudioSegment.from_wav(str(src))
100
-
101
- end_ms = start_ms + eight_bars_ms
102
- if len(audio) < end_ms:
103
- s = 0
104
- e = min(len(audio), eight_bars_ms)
105
- else:
106
- s, e = start_ms, end_ms
107
-
108
- loop = audio[s:int(e)].fade_in(15).fade_out(15).normalize()
109
- out_path = OUTPUT_DIR / "Loops" / f"{bpm}BPM_{name}.wav"
110
- loop.export(out_path, format="wav")
111
- return out_path
112
-
113
- # Generate loops for all 6 stems
114
- created_loops['melody'] = make_loop(stems['Synths'], "SynthLoop")
115
- make_loop(stems['Drums'], "DrumLoop")
116
- make_loop(stems['Bass'], "BassLoop")
117
- make_loop(stems['Guitar'], "GuitarLoop")
118
- make_loop(stems['Piano'], "PianoLoop")
119
- make_loop(stems['Vocals'], "VocalChop")
120
-
121
- # 3. Generate Video
122
- video_path = None
123
- if cover_art and created_loops['melody']:
124
- print("Rendering Dynamic Video...")
125
- vid_out = OUTPUT_DIR / "Promo_Video.mp4"
126
- audio_clip = AudioFileClip(str(created_loops['melody']))
127
- duration = audio_clip.duration
128
-
129
- # Load and Resize Image
130
- img = ImageClip(cover_art).resize(width=1080)
131
-
132
- # Simple Zoom Animation
133
- img = img.resize(lambda t : 1 + 0.02*t)
134
- img = img.set_position(('center', 'center'))
135
- img = img.set_duration(duration)
136
- img = img.set_audio(audio_clip)
137
-
138
- final_clip = CompositeVideoClip([img], size=(1080, 1920))
139
- final_clip.duration = duration
140
- final_clip.audio = audio_clip
141
- final_clip.fps = 24
142
-
143
- final_clip.write_videofile(str(vid_out), codec="libx264", audio_codec="aac", logger=None)
144
- video_path = str(vid_out)
145
-
146
- # 4. Zip
147
- zip_file = "NightPulse_Pack.zip"
148
- with zipfile.ZipFile(zip_file, 'w') as zf:
149
- for root, dirs, files in os.walk(OUTPUT_DIR):
150
- for file in files:
151
- file_path = Path(root) / file
152
- arcname = file_path.relative_to(OUTPUT_DIR)
153
- zf.write(file_path, arcname)
154
-
155
- return zip_file, video_path
156
-
157
- except Exception as e:
158
- raise gr.Error(f"Packaging Failed: {str(e)}")
159
-
160
-
161
- # --- GUI (Blocks) ---
162
- # FIXED: Removed 'theme=' argument to prevent TypeError
163
- with gr.Blocks(title="Night Pulse | Command Center (6-Stem)") as app:
164
- gr.Markdown("# 🎛️ Night Pulse | 6-Stem Command Center")
165
- gr.Markdown("Deconstruct audio into 6 stems: Drums, Bass, Guitar, Piano, Vocals, Synths.")
166
-
167
- # State storage
168
- stored_folder = gr.State()
169
- stored_bpm = gr.State()
170
-
171
- with gr.Row():
172
- with gr.Column(scale=1):
173
- input_audio = gr.Audio(type="filepath", label="1. Upload Master Track")
174
- input_art = gr.Image(type="filepath", label="Cover Art (9:16)")
175
- btn_analyze = gr.Button("🔍 Phase 1: Separate (6 Stems)", variant="primary")
176
-
177
- with gr.Column(scale=1):
178
- gr.Markdown("### 2. Stem Preview")
179
- with gr.Row():
180
- p_drums = gr.Audio(label="Drums")
181
- p_bass = gr.Audio(label="Bass")
182
- with gr.Row():
183
- p_guitar = gr.Audio(label="Guitar")
184
- p_piano = gr.Audio(label="Piano")
185
- with gr.Row():
186
- p_other = gr.Audio(label="Synths/Other")
187
- p_vocals = gr.Audio(label="Vocals")
188
-
189
- gr.Markdown("---")
190
-
191
- with gr.Row():
192
- with gr.Column():
193
- gr.Markdown("### 3. Loop Logic")
194
- slider_start = gr.Slider(minimum=0, maximum=120, value=15, label="Loop Start Time (Seconds)", info="Select the start point for the 8-bar loop cut.")
195
- btn_package = gr.Button("📦 Phase 2: Package & Export", variant="primary")
196
-
197
- with gr.Column():
198
- gr.Markdown("### 4. Final Output")
199
- out_zip = gr.File(label="Download Pack (ZIP)")
200
- out_video = gr.Video(label="Promo Video")
201
-
202
- # Events
203
- btn_analyze.click(
204
- fn=analyze_and_separate,
205
- inputs=[input_audio],
206
- outputs=[p_drums, p_bass, p_guitar, p_piano, p_other, p_vocals, stored_bpm, stored_folder]
207
- )
208
-
209
- btn_package.click(
210
- fn=package_and_export,
211
- inputs=[stored_folder, stored_bpm, slider_start, input_art],
212
- outputs=[out_zip, out_video]
213
- )
214
-
215
- if __name__ == "__main__":
216
- app.launch()
 
1
+ demucs
2
+ pydub
3
+ librosa
4
+ moviepy==1.0.3
5
+ decorator<5.0
6
+ numpy<2
7
+ gradio
8
+ scipy
9
+ soundfile
10
+ imageio-ffmpeg
11
+ torch==2.1.2
12
+ torchaudio==2.1.2
13
+ Pillow<10.0.0