File size: 5,245 Bytes
55c17c3
b1477ac
 
55c17c3
f04ed54
 
 
 
44c1e23
f04ed54
 
b1477ac
 
f04ed54
 
b1477ac
f04ed54
6f33f17
f04ed54
 
55c17c3
b1477ac
6f33f17
f04ed54
6f33f17
b1477ac
f04ed54
 
 
 
 
 
 
 
 
 
 
 
 
6f33f17
f04ed54
 
b1477ac
6f33f17
f04ed54
 
 
 
 
b1477ac
6f33f17
f04ed54
b1477ac
f04ed54
6f33f17
f04ed54
6f33f17
f04ed54
 
 
 
 
 
 
6f33f17
f04ed54
6f33f17
f04ed54
 
 
 
6f33f17
f04ed54
 
 
 
 
6f33f17
f04ed54
 
 
 
 
6f33f17
b1477ac
6f33f17
 
 
 
b1477ac
6f33f17
 
 
b1477ac
6f33f17
 
 
 
f04ed54
6f33f17
 
f04ed54
 
6f33f17
 
 
b1477ac
6f33f17
 
 
b1477ac
6f33f17
 
 
 
 
f04ed54
6f33f17
 
b1477ac
6f33f17
 
 
 
 
 
 
 
b1477ac
f04ed54
44c1e23
 
f04ed54
 
6f33f17
f04ed54
 
 
6f33f17
 
f04ed54
 
6f33f17
 
f04ed54
 
6f33f17
f04ed54
44c1e23
 
f04ed54
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
import gradio as gr
import os
import shutil
import zipfile
import librosa
import numpy as np
from pydub import AudioSegment
from moviepy.editor import AudioFileClip, ImageClip
import subprocess
from pathlib import Path
import sys

# --- Configuration ---
OUTPUT_DIR = Path("nightpulse_output")
TEMP_DIR = Path("temp_processing")

def process_track(audio_file, cover_art_image):
    # Initialize return variables
    zip_path = None
    video_path = None

    try:
        # --- 0. Input Validation ---
        if not audio_file:
            raise ValueError("No audio file provided.")
            
        # --- 1. Setup Directories ---
        if OUTPUT_DIR.exists():
            shutil.rmtree(OUTPUT_DIR)
        if TEMP_DIR.exists():
            shutil.rmtree(TEMP_DIR)
        OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
        TEMP_DIR.mkdir(parents=True, exist_ok=True)
        
        filename = Path(audio_file).stem
        
        # --- 2. Analyze BPM & Key (Librosa) ---
        print(f"Analyzing {filename}...")
        try:
            # Mono load for robust BPM detection
            y, sr = librosa.load(audio_file, duration=60, mono=True)
            tempo, _ = librosa.beat.beat_track(y=y, sr=sr)
            
            # Handle different librosa return types (float vs array)
            if np.ndim(tempo) > 0:
                detected_bpm = int(round(tempo[0]))
            else:
                detected_bpm = int(round(tempo))
            print(f"Detected BPM: {detected_bpm}")
        except Exception as e:
            print(f"BPM Warning: {e}")
            detected_bpm = 120 # Safe Fallback
        
        # --- 3. AI Stem Separation (Demucs) ---
        print("Separating stems...")
        try:
            # Call Demucs via subprocess to ensure clean execution
            subprocess.run([
                sys.executable, "-m", "demucs", 
                "-n", "htdemucs", 
                "--out", str(TEMP_DIR), 
                audio_file
            ], check=True, capture_output=True)
        except subprocess.CalledProcessError as e:
            raise RuntimeError(f"Demucs failed: {e.stderr.decode()}")
        
        # Locate Stems (Robust Search)
        demucs_out = TEMP_DIR / "htdemucs"
        track_folder = next(demucs_out.iterdir(), None)
        
        if not track_folder:
             raise FileNotFoundError("Demucs output folder missing.")

        drums_path = track_folder / "drums.wav"
        melody_path = track_folder / "other.wav"
        bass_path = track_folder / "bass.wav"

        # --- 4. Loop Logic ---
        if detected_bpm <= 0: detected_bpm = 120
        ms_per_beat = (60 / detected_bpm) * 1000
        eight_bars_ms = ms_per_beat * 4 * 8
        
        def create_loop(source_path, output_name):
            if not source_path.exists(): return None, None
            
            audio = AudioSegment.from_wav(str(source_path))
            # Grab middle 8 bars
            start = len(audio) // 3
            end = start + eight_bars_ms
            
            if len(audio) < end:
                start = 0
                end = min(len(audio), eight_bars_ms)
                
            loop = audio[start:int(end)].fade_in(15).fade_out(15).normalize()
            out_name = OUTPUT_DIR / f"{detected_bpm}BPM_{output_name}.wav"
            loop.export(out_name, format="wav")
            return out_name, loop

        loop_drums, _ = create_loop(drums_path, "DrumLoop")
        loop_melody, _ = create_loop(melody_path, "MelodyLoop")
        create_loop(bass_path, "BassLoop")
        
        # --- 5. Video Generation ---
        if cover_art_image and loop_melody:
            print("Rendering Video...")
            try:
                vid_out = OUTPUT_DIR / "Promo_Video.mp4"
                audio_clip = AudioFileClip(str(loop_melody))
                img_clip = ImageClip(cover_art_image)
                
                # Resize to 1080w (maintain aspect ratio)
                img_clip = img_clip.resize(width=1080)
                img_clip = img_clip.set_duration(audio_clip.duration)
                img_clip = img_clip.set_audio(audio_clip)
                img_clip.fps = 24
                
                img_clip.write_videofile(str(vid_out), codec="libx264", audio_codec="aac", logger=None)
                video_path = str(vid_out)
            except Exception as e:
                print(f"Video skipped: {e}")

        # --- 6. Zip Export ---
        zip_file = "NightPulse_Pack.zip"
        with zipfile.ZipFile(zip_file, 'w') as zf:
            for f in OUTPUT_DIR.iterdir():
                zf.write(f, f.name)
        zip_path = zip_file
        
        return zip_path, video_path

    except Exception as e:
        raise gr.Error(f"System Error: {str(e)}")

# --- UI Definition (Corrected) ---
iface = gr.Interface(
    fn=process_track,
    inputs=[
        gr.Audio(type="filepath", label="Upload Suno Track"),
        gr.Image(type="filepath", label="Upload Cover Art")
    ],
    outputs=[
        gr.File(label="Download ZIP"),
        gr.Video(label="Preview Video")
    ],
    title="Night Pulse Audio | Automator",
    description="Night Pulse Pipeline v1.1 (Stable)"
)

if __name__ == "__main__":
    iface.launch()