Spaces:
Running on Zero
Running on Zero
File size: 7,229 Bytes
ad73d02 fb24452 ad73d02 fb24452 ad73d02 fb24452 ad73d02 fb24452 ad73d02 fb24452 ad73d02 fb24452 ad73d02 fb24452 ad73d02 fb24452 ad73d02 fb24452 ad73d02 | 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 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 | import sys
sys.stdout.reconfigure(line_buffering=True)
try:
import spaces
except ImportError:
# keep @spaces.GPU usable as a no-op; ZeroGPU requires this exact name.
class spaces:
class GPU:
def __init__(self, func=None, duration=60):
self.func = func
def __call__(self, *args, **kwargs):
if self.func is not None:
return self.func(*args, **kwargs)
func = args[0]
return func
import os
import shutil
import tempfile
import threading
import gradio as gr
import numpy as np
import soundfile as sf
import torch
from huggingface_hub import hf_hub_download
from pyharp import ModelCard, build_endpoint
from songgen import (
SongGenDualTrackForConditionalGeneration,
SongGenMixedForConditionalGeneration,
SongGenProcessor,
)
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
MODES = {
"Mixed": SongGenMixedForConditionalGeneration,
"Dual-track (vocals + accompaniment)": SongGenDualTrackForConditionalGeneration,
}
CHECKPOINTS = {
"Mixed": "LiuZH-19/SongGen_mixed_pro",
"Dual-track (vocals + accompaniment)": "LiuZH-19/SongGen_interleaving_A_V",
}
state = {name: {"model": None, "processor": None} for name in MODES}
xcodec_ready = False
xcodec_error = None
def fetch_xcodec_assets():
"""Grab the X-Codec checkpoint and its config. SongGen's XCodecModel
hardcodes this exact folder rather than taking a path argument, so the
files have to be here before any SongGen model is constructed."""
ckpt_dir = os.path.join(
os.path.dirname(__file__), "songgen", "xcodec_wrapper", "xcodec_infer", "ckpts", "general_more"
)
os.makedirs(ckpt_dir, exist_ok=True)
for filename in ("xcodec_hubert_general_audio_v2.pth", "config_hubert_general.yaml"):
dest = os.path.join(ckpt_dir, filename)
if not os.path.exists(dest):
src = hf_hub_download(repo_id="ZhenYe234/xcodec", filename=filename)
shutil.copy(src, dest)
def load_xcodec_assets_bg():
"""Fetch the shared X-Codec assets in the background. Pure file I/O, so
unlike the actual SongGen models it's safe to do off the main thread."""
global xcodec_ready, xcodec_error
try:
fetch_xcodec_assets()
except Exception as e:
xcodec_error = str(e)
finally:
xcodec_ready = True
threading.Thread(target=load_xcodec_assets_bg, daemon=True).start()
model_card = ModelCard(
name="SongGen",
description=(
"Single-stage auto-regressive transformer for text-to-song generation. "
"Give it lyrics and a description of the music, plus an optional reference "
"voice, and it generates a full song."
),
author="Zihan Liu, Shuangrui Ding, Zhixiong Zhang, Xiaoyi Dong, Pan Zhang, Yuhang Zang, Yuhang Cao, Dahua Lin, Jiaqi Wang",
tags=["text-to-music", "song-generation"],
)
def get_mode(mode_name):
"""Build a mode's model + processor on first use, then reuse them.
Loading MERT's remote code touches CUDA, so this can only happen inside
an @spaces.GPU call, not at startup."""
entry = state[mode_name]
if entry["model"] is None:
if not xcodec_ready:
raise gr.Error("Still preparing shared assets, please wait a moment and try again.")
if xcodec_error:
raise gr.Error(f"Failed to prepare shared assets: {xcodec_error}")
repo_id = CHECKPOINTS[mode_name]
model = MODES[mode_name].from_pretrained(repo_id, attn_implementation="sdpa").to(DEVICE)
processor = SongGenProcessor(repo_id, DEVICE)
entry["model"] = model
entry["processor"] = processor
return entry["model"], entry["processor"]
def write_wav(audio_arr, sample_rate):
"""Save a numpy audio array to a temp wav file and return its path."""
path = tempfile.NamedTemporaryFile(suffix=".wav", delete=False).name
sf.write(path, audio_arr, sample_rate)
return path
def silent_wav(sample_rate, seconds=0.1):
"""A short silent placeholder for the vocals-stem output when the
selected mode doesn't produce a separate vocal track."""
return write_wav(np.zeros(int(seconds * sample_rate), dtype=np.float32), sample_rate)
@spaces.GPU(duration=180)
@torch.inference_mode()
def process_fn(mode_name, description, lyrics, ref_voice_path, extract_vocals, temperature):
"""Generate a song from a description and lyrics, optionally guided by a reference voice."""
model, processor = get_mode(mode_name)
model_inputs = processor(
text=description,
lyrics=lyrics,
ref_voice_path=ref_voice_path if ref_voice_path else None,
separate=extract_vocals,
)
generation = model.generate(**model_inputs, do_sample=True, temperature=temperature)
sample_rate = model.config.sampling_rate
if mode_name == "Mixed":
song_path = write_wav(generation.cpu().numpy().squeeze(), sample_rate)
vocals_path = silent_wav(sample_rate)
else:
acc_array = generation[0].cpu().numpy().squeeze()
vocal_array = generation[1].cpu().numpy().squeeze()
min_len = min(vocal_array.shape[0], acc_array.shape[0])
acc_array = acc_array[:min_len]
vocal_array = vocal_array[:min_len]
song_path = write_wav(vocal_array + acc_array, sample_rate)
vocals_path = write_wav(vocal_array, sample_rate)
return song_path, vocals_path
with gr.Blocks() as demo:
input_components = [
gr.Dropdown(
choices=list(MODES.keys()),
value="Mixed",
label="Mode",
info="Mixed outputs one finished track; Dual-track also gives you an isolated vocal stem",
),
gr.Textbox(
label="Music Description",
info="Genre, mood, instruments (e.g. \"upbeat pop song with piano and drums\")",
),
gr.Textbox(
label="Lyrics",
info="English lyrics for the song (required)",
),
gr.Audio(type="filepath", label="Reference Voice (optional)").harp_required(False),
gr.Checkbox(
value=True,
label="Extract vocals from reference",
info="(default: True, per repo example) turn off only if the reference audio is already an isolated vocal, not a full mix",
),
gr.Slider(
minimum=0.1, maximum=2.0, step=0.1, value=1.0,
label="Creativity",
info="(default: 1.0, per repo config) sampling temperature — higher is more varied/unpredictable, lower is safer/more repetitive",
),
]
output_components = [
gr.Audio(type="filepath", label="Song").set_info(
"The generated song. In Dual-track mode this is vocals + accompaniment combined."
),
gr.Audio(type="filepath", label="Isolated Vocals").set_info(
"Vocal-only stem. Only populated in Dual-track mode; silent in Mixed mode."
),
]
build_endpoint(
model_card=model_card,
input_components=input_components,
output_components=output_components,
process_fn=process_fn,
)
if __name__ == "__main__":
demo.queue().launch(pwa=True)
|