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

Update app.py

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