SaltProphet commited on
Commit
d2b013b
·
verified ·
1 Parent(s): 5352552

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +75 -37
app.py CHANGED
@@ -6,30 +6,74 @@ import librosa
6
  import numpy as np
7
  from pydub import AudioSegment
8
  from moviepy.editor import AudioFileClip, ImageClip, CompositeVideoClip
9
- # --- FIX: Import the video effects explicitly ---
10
  from moviepy.video.fx.all import blackwhite, lum_contrast
11
  import subprocess
12
  from pathlib import Path
13
  import sys
 
14
 
15
  # --- Configuration ---
16
  OUTPUT_DIR = Path("nightpulse_output")
17
  TEMP_DIR = Path("temp_processing")
18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  # --- Core Logic Functions ---
20
 
21
- def analyze_and_separate(audio_file):
22
  """Phase 1: Separate 6 Stems (Drums, Bass, Guitar, Piano, Vocals, Other)"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  try:
24
- if not audio_file:
25
- raise ValueError("No audio file provided.")
26
-
27
- # Cleanup
28
  if OUTPUT_DIR.exists(): shutil.rmtree(OUTPUT_DIR)
29
- if TEMP_DIR.exists(): shutil.rmtree(TEMP_DIR)
30
  (OUTPUT_DIR / "Stems").mkdir(parents=True, exist_ok=True)
31
  (OUTPUT_DIR / "Loops").mkdir(parents=True, exist_ok=True)
32
- TEMP_DIR.mkdir(parents=True, exist_ok=True)
33
 
34
  filename = Path(audio_file).stem
35
 
@@ -45,7 +89,7 @@ def analyze_and_separate(audio_file):
45
  print(f"Detected BPM: {bpm}")
46
 
47
  # 2. Demucs Separation (Using 6-Stem Model)
48
- print("Separating stems (6-Stem Model)...")
49
  subprocess.run([
50
  sys.executable, "-m", "demucs", "-n", "htdemucs_6s", "--out", str(TEMP_DIR), audio_file
51
  ], check=True, capture_output=True)
@@ -53,9 +97,9 @@ def analyze_and_separate(audio_file):
53
  # 3. Locate Stems
54
  demucs_out = TEMP_DIR / "htdemucs_6s"
55
  track_folder = next(demucs_out.iterdir(), None)
56
- if not track_folder: raise FileNotFoundError("Demucs failed to output files.")
57
 
58
- # Map all 6 stems
59
  drums = track_folder / "drums.wav"
60
  bass = track_folder / "bass.wav"
61
  guitar = track_folder / "guitar.wav"
@@ -63,17 +107,15 @@ def analyze_and_separate(audio_file):
63
  vocals = track_folder / "vocals.wav"
64
  other = track_folder / "other.wav"
65
 
66
- # Return paths to the UI and the BPM
67
  return str(drums), str(bass), str(guitar), str(piano), str(other), str(vocals), bpm, str(track_folder)
68
 
69
  except Exception as e:
70
- raise gr.Error(f"Separation Failed: {str(e)}")
71
 
72
  def package_and_export(track_folder_str, bpm, start_offset_sec, cover_art):
73
- """Phase 2: Chop Loops, Generate Video, Zip"""
74
  try:
75
  track_folder = Path(track_folder_str)
76
- # Re-map paths
77
  stems = {
78
  "Drums": track_folder / "drums.wav",
79
  "Bass": track_folder / "bass.wav",
@@ -83,12 +125,12 @@ def package_and_export(track_folder_str, bpm, start_offset_sec, cover_art):
83
  "Vocals": track_folder / "vocals.wav"
84
  }
85
 
86
- # 1. Save Full Stems (Copy to Stems folder)
87
  for name, path in stems.items():
88
  if path.exists():
89
  shutil.copy(path, OUTPUT_DIR / "Stems" / f"{bpm}BPM_Full_{name}.wav")
90
 
91
- # 2. Create Loops (With User Offset)
92
  ms_per_beat = (60 / bpm) * 1000
93
  eight_bars_ms = ms_per_beat * 4 * 8
94
  start_ms = start_offset_sec * 1000
@@ -98,20 +140,17 @@ def package_and_export(track_folder_str, bpm, start_offset_sec, cover_art):
98
  def make_loop(src, name):
99
  if not src.exists(): return None
100
  audio = AudioSegment.from_wav(str(src))
101
-
102
  end_ms = start_ms + eight_bars_ms
103
  if len(audio) < end_ms:
104
  s = 0
105
  e = min(len(audio), eight_bars_ms)
106
  else:
107
  s, e = start_ms, end_ms
108
-
109
  loop = audio[s:int(e)].fade_in(15).fade_out(15).normalize()
110
  out_path = OUTPUT_DIR / "Loops" / f"{bpm}BPM_{name}.wav"
111
  loop.export(out_path, format="wav")
112
  return out_path
113
 
114
- # Generate loops for all 6 stems
115
  created_loops['melody'] = make_loop(stems['Synths'], "SynthLoop")
116
  make_loop(stems['Drums'], "DrumLoop")
117
  make_loop(stems['Bass'], "BassLoop")
@@ -122,22 +161,14 @@ def package_and_export(track_folder_str, bpm, start_offset_sec, cover_art):
122
  # 3. Generate Video
123
  video_path = None
124
  if cover_art and created_loops['melody']:
125
- print("Rendering Dynamic Noir Video...")
126
  vid_out = OUTPUT_DIR / "Promo_Video.mp4"
127
  audio_clip = AudioFileClip(str(created_loops['melody']))
128
  duration = audio_clip.duration
129
 
130
- # Load and Resize Image
131
  img = ImageClip(cover_art).resize(width=1080)
132
 
133
- # --- NIGHT PULSE AESTHETIC FILTER ---
134
- # 1. Force Black & White
135
- img = img.fx(blackwhite)
136
- # 2. Boost Contrast (make the blacks deeper, whites brighter)
137
- img = img.fx(lum_contrast, lum=0, contrast=1.3)
138
- # ------------------------------------
139
-
140
- # Simple Zoom Animation
141
  img = img.resize(lambda t : 1 + 0.02*t)
142
  img = img.set_position(('center', 'center'))
143
  img = img.set_duration(duration)
@@ -167,17 +198,24 @@ def package_and_export(track_folder_str, bpm, start_offset_sec, cover_art):
167
 
168
 
169
  # --- GUI (Blocks) ---
170
- with gr.Blocks(title="Night Pulse | Command Center (6-Stem)") as app:
171
- gr.Markdown("# 🎛️ Night Pulse | 6-Stem Command Center")
172
- gr.Markdown("Deconstruct audio into 6 stems: Drums, Bass, Guitar, Piano, Vocals, Synths.")
173
 
174
- # State storage
175
  stored_folder = gr.State()
176
  stored_bpm = gr.State()
177
 
178
  with gr.Row():
179
  with gr.Column(scale=1):
180
- input_audio = gr.Audio(type="filepath", label="1. Upload Master Track")
 
 
 
 
 
 
 
 
181
  input_art = gr.Image(type="filepath", label="Cover Art (9:16)")
182
  btn_analyze = gr.Button("🔍 Phase 1: Separate (6 Stems)", variant="primary")
183
 
@@ -198,7 +236,7 @@ with gr.Blocks(title="Night Pulse | Command Center (6-Stem)") as app:
198
  with gr.Row():
199
  with gr.Column():
200
  gr.Markdown("### 3. Loop Logic")
201
- 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.")
202
  btn_package = gr.Button("📦 Phase 2: Package & Export", variant="primary")
203
 
204
  with gr.Column():
@@ -209,7 +247,7 @@ with gr.Blocks(title="Night Pulse | Command Center (6-Stem)") as app:
209
  # Events
210
  btn_analyze.click(
211
  fn=analyze_and_separate,
212
- inputs=[input_audio],
213
  outputs=[p_drums, p_bass, p_guitar, p_piano, p_other, p_vocals, stored_bpm, stored_folder]
214
  )
215
 
 
6
  import numpy as np
7
  from pydub import AudioSegment
8
  from moviepy.editor import AudioFileClip, ImageClip, CompositeVideoClip
9
+ # Keep import available but unused (Full feature set available)
10
  from moviepy.video.fx.all import blackwhite, lum_contrast
11
  import subprocess
12
  from pathlib import Path
13
  import sys
14
+ import yt_dlp
15
 
16
  # --- Configuration ---
17
  OUTPUT_DIR = Path("nightpulse_output")
18
  TEMP_DIR = Path("temp_processing")
19
 
20
+ # --- Helper: Cloud Import (The Fix) ---
21
+ def download_from_url(url):
22
+ """Downloads audio from YouTube/SC/Direct Link to bypass file picker crashes."""
23
+ if not url: return None
24
+
25
+ print(f"Fetching URL: {url}")
26
+ # Configuration for high-quality audio extraction
27
+ ydl_opts = {
28
+ 'format': 'bestaudio/best',
29
+ 'outtmpl': str(TEMP_DIR / '%(title)s.%(ext)s'),
30
+ 'postprocessors': [{
31
+ 'key': 'FFmpegExtractAudio',
32
+ 'preferredcodec': 'wav',
33
+ 'preferredquality': '192',
34
+ }],
35
+ 'quiet': True,
36
+ 'no_warnings': True,
37
+ }
38
+
39
+ # Clean temp before downloading
40
+ if TEMP_DIR.exists(): shutil.rmtree(TEMP_DIR)
41
+ TEMP_DIR.mkdir(parents=True, exist_ok=True)
42
+
43
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
44
+ info = ydl.extract_info(url, download=True)
45
+ filename = ydl.prepare_filename(info)
46
+ # yt-dlp might change extension to .wav after conversion
47
+ final_path = Path(filename).with_suffix(".wav")
48
+ return str(final_path)
49
+
50
  # --- Core Logic Functions ---
51
 
52
+ def analyze_and_separate(file_input, url_input):
53
  """Phase 1: Separate 6 Stems (Drums, Bass, Guitar, Piano, Vocals, Other)"""
54
+
55
+ # LOGIC: Check URL first (Safe Mode), then File (Desktop Mode)
56
+ audio_file = None
57
+
58
+ if url_input and len(url_input) > 5:
59
+ print("Using Cloud Import...")
60
+ try:
61
+ audio_file = download_from_url(url_input)
62
+ except Exception as e:
63
+ raise gr.Error(f"Link Download Failed: {str(e)}")
64
+ elif file_input:
65
+ print("Using File Upload...")
66
+ audio_file = file_input
67
+
68
+ if not audio_file:
69
+ raise gr.Error("No audio source found. Please paste a link or upload a file.")
70
+
71
  try:
72
+ # Cleanup Output
 
 
 
73
  if OUTPUT_DIR.exists(): shutil.rmtree(OUTPUT_DIR)
 
74
  (OUTPUT_DIR / "Stems").mkdir(parents=True, exist_ok=True)
75
  (OUTPUT_DIR / "Loops").mkdir(parents=True, exist_ok=True)
76
+ if not TEMP_DIR.exists(): TEMP_DIR.mkdir(parents=True, exist_ok=True)
77
 
78
  filename = Path(audio_file).stem
79
 
 
89
  print(f"Detected BPM: {bpm}")
90
 
91
  # 2. Demucs Separation (Using 6-Stem Model)
92
+ print("Separating stems...")
93
  subprocess.run([
94
  sys.executable, "-m", "demucs", "-n", "htdemucs_6s", "--out", str(TEMP_DIR), audio_file
95
  ], check=True, capture_output=True)
 
97
  # 3. Locate Stems
98
  demucs_out = TEMP_DIR / "htdemucs_6s"
99
  track_folder = next(demucs_out.iterdir(), None)
100
+ if not track_folder: raise FileNotFoundError("Demucs separation failed.")
101
 
102
+ # Map stems
103
  drums = track_folder / "drums.wav"
104
  bass = track_folder / "bass.wav"
105
  guitar = track_folder / "guitar.wav"
 
107
  vocals = track_folder / "vocals.wav"
108
  other = track_folder / "other.wav"
109
 
 
110
  return str(drums), str(bass), str(guitar), str(piano), str(other), str(vocals), bpm, str(track_folder)
111
 
112
  except Exception as e:
113
+ raise gr.Error(f"Process Failed: {str(e)}")
114
 
115
  def package_and_export(track_folder_str, bpm, start_offset_sec, cover_art):
116
+ """Phase 2: Package & Export"""
117
  try:
118
  track_folder = Path(track_folder_str)
 
119
  stems = {
120
  "Drums": track_folder / "drums.wav",
121
  "Bass": track_folder / "bass.wav",
 
125
  "Vocals": track_folder / "vocals.wav"
126
  }
127
 
128
+ # 1. Save Full Stems
129
  for name, path in stems.items():
130
  if path.exists():
131
  shutil.copy(path, OUTPUT_DIR / "Stems" / f"{bpm}BPM_Full_{name}.wav")
132
 
133
+ # 2. Create Loops
134
  ms_per_beat = (60 / bpm) * 1000
135
  eight_bars_ms = ms_per_beat * 4 * 8
136
  start_ms = start_offset_sec * 1000
 
140
  def make_loop(src, name):
141
  if not src.exists(): return None
142
  audio = AudioSegment.from_wav(str(src))
 
143
  end_ms = start_ms + eight_bars_ms
144
  if len(audio) < end_ms:
145
  s = 0
146
  e = min(len(audio), eight_bars_ms)
147
  else:
148
  s, e = start_ms, end_ms
 
149
  loop = audio[s:int(e)].fade_in(15).fade_out(15).normalize()
150
  out_path = OUTPUT_DIR / "Loops" / f"{bpm}BPM_{name}.wav"
151
  loop.export(out_path, format="wav")
152
  return out_path
153
 
 
154
  created_loops['melody'] = make_loop(stems['Synths'], "SynthLoop")
155
  make_loop(stems['Drums'], "DrumLoop")
156
  make_loop(stems['Bass'], "BassLoop")
 
161
  # 3. Generate Video
162
  video_path = None
163
  if cover_art and created_loops['melody']:
164
+ print("Rendering Video...")
165
  vid_out = OUTPUT_DIR / "Promo_Video.mp4"
166
  audio_clip = AudioFileClip(str(created_loops['melody']))
167
  duration = audio_clip.duration
168
 
 
169
  img = ImageClip(cover_art).resize(width=1080)
170
 
171
+ # Zoom Animation (Full Color)
 
 
 
 
 
 
 
172
  img = img.resize(lambda t : 1 + 0.02*t)
173
  img = img.set_position(('center', 'center'))
174
  img = img.set_duration(duration)
 
198
 
199
 
200
  # --- GUI (Blocks) ---
201
+ with gr.Blocks(title="Night Pulse | Studio Pro") as app:
202
+ gr.Markdown("# 🎛️ Night Pulse | Studio Command Center")
203
+ gr.Markdown("Full 6-stem separation and video generation.")
204
 
 
205
  stored_folder = gr.State()
206
  stored_bpm = gr.State()
207
 
208
  with gr.Row():
209
  with gr.Column(scale=1):
210
+ gr.Markdown("### 1. Audio Source")
211
+ with gr.Tabs():
212
+ with gr.TabItem("☁️ Import Link (Mobile Safe)"):
213
+ gr.Markdown("**Paste a link (YouTube/SoundCloud) to avoid browser reloads.**")
214
+ input_url = gr.Textbox(label="Paste URL Here", placeholder="https://youtube.com/watch?v=...", show_label=False)
215
+
216
+ with gr.TabItem("📂 Upload File (Desktop)"):
217
+ input_file = gr.Audio(type="filepath", label="Upload Master Track")
218
+
219
  input_art = gr.Image(type="filepath", label="Cover Art (9:16)")
220
  btn_analyze = gr.Button("🔍 Phase 1: Separate (6 Stems)", variant="primary")
221
 
 
236
  with gr.Row():
237
  with gr.Column():
238
  gr.Markdown("### 3. Loop Logic")
239
+ slider_start = gr.Slider(minimum=0, maximum=120, value=15, label="Loop Start Time (Seconds)")
240
  btn_package = gr.Button("📦 Phase 2: Package & Export", variant="primary")
241
 
242
  with gr.Column():
 
247
  # Events
248
  btn_analyze.click(
249
  fn=analyze_and_separate,
250
+ inputs=[input_file, input_url],
251
  outputs=[p_drums, p_bass, p_guitar, p_piano, p_other, p_vocals, stored_bpm, stored_folder]
252
  )
253