File size: 7,318 Bytes
b7661f0
 
 
3959a20
b7661f0
3959a20
aa4ca52
b7661f0
aa4ca52
b7661f0
 
f882299
 
bf455aa
aa4ca52
 
 
bf455aa
b7661f0
 
3959a20
f85bfad
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b7661f0
 
 
f85bfad
b7661f0
aa4ca52
 
b7661f0
 
 
3959a20
b7661f0
 
 
 
 
 
 
3959a20
 
 
 
 
 
 
 
 
ee2ba54
 
 
 
 
 
 
 
3959a20
 
 
aa4ca52
3959a20
 
 
 
 
 
 
 
 
 
 
 
 
aa4ca52
f882299
3959a20
 
aa4ca52
3959a20
f882299
 
3959a20
 
 
f882299
 
 
 
3959a20
aa4ca52
3959a20
 
b7661f0
d6ab544
b7661f0
 
3959a20
 
 
 
 
 
b7661f0
 
 
3959a20
 
 
 
 
b7661f0
 
3959a20
ee2ba54
 
 
 
 
 
3959a20
aa4ca52
 
686a26c
aa4ca52
3959a20
 
 
 
 
aa4ca52
 
686a26c
3959a20
 
 
aa4ca52
 
b7661f0
d6ab544
bf455aa
54b3296
aa4ca52
 
a3ffcd6
aa4ca52
9146b4b
 
b7661f0
aa4ca52
 
 
 
7875fb9
aa4ca52
 
 
 
ee2ba54
aa4ca52
d6ab544
aa4ca52
cc0498a
 
f882299
eb50e6c
4ee0f46
 
bbc5c51
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
import json
import os
import random
import secrets
import subprocess
from datetime import datetime, timezone

import gradio as gr
import spaces
import torch

COMMUNITY_BUCKET = "directedbykobyperez/YuE2-community"
COMMUNITY_URL = "https://huggingface.co/buckets/directedbykobyperez/YuE2-community"

REPO = "m-a-p/YuE2-3B"
OUT = "/tmp/yue2_out"
os.makedirs(OUT, exist_ok=True)

pipe = None


def _ensure_yue2():
    """Install the vendored yue2-infer wheel at runtime (not build time).

    It can't go in requirements.txt: it pins huggingface-hub==0.36.2 /
    transformers==4.57.6 which conflict with Gradio 6 (hub>=1.16) and break
    the Docker build. --no-deps sidesteps the pins; the package only needs
    torch/transformers/hub/safetensors/tiktoken/soundfile which are present.
    """
    try:
        import yue2  # noqa: F401
        return
    except ImportError:
        pass
    import sys
    whl = os.path.join(os.path.dirname(os.path.abspath(__file__)), "yue2_infer-0.1.5-py3-none-any.whl")
    print(f"[yue2] installing vendored wheel: {whl}")
    subprocess.run(
        [sys.executable, "-m", "pip", "install", "--no-deps", whl],
        check=True, capture_output=True, text=True,
    )
    print("[yue2] installed.")


def get_pipeline():
    global pipe
    if pipe is None:
        _ensure_yue2()
        from yue2 import YuE2Pipeline
        print("Loading YuE2-3B pipeline (first run downloads weights)...")
        pipe = YuE2Pipeline.from_pretrained(REPO, device="cuda" if torch.cuda.is_available() else "cpu")
        print("Pipeline loaded.")
    return pipe


def flac_to_mp3(flac_path, mp3_path, bitrate="320k"):
    subprocess.run(
        ["ffmpeg", "-y", "-i", flac_path, "-b:a", bitrate, mp3_path],
        capture_output=True, check=True,
    )
    return mp3_path


def flac_to_wav(flac_path, wav_path):
    subprocess.run(
        ["ffmpeg", "-y", "-i", flac_path, "-c:a", "pcm_s16le", wav_path],
        capture_output=True, check=True,
    )
    return wav_path


def flac_to_ogg(flac_path, ogg_path, bitrate="192k"):
    subprocess.run(
        ["ffmpeg", "-y", "-i", flac_path, "-c:a", "libopus", "-b:a", bitrate, ogg_path],
        capture_output=True, check=True,
    )
    return ogg_path


def get_duration_seconds(audio_path):
    try:
        import soundfile as sf
        return float(sf.info(audio_path).duration)
    except Exception:
        pass
    try:
        r = subprocess.run(
            ["ffprobe", "-v", "error", "-show_entries", "format=duration",
             "-of", "default=noprint_wrappers=1:nokey=1", audio_path],
            capture_output=True, text=True, check=True,
        )
        return float(r.stdout.strip())
    except Exception:
        return 0.0


def _upload_to_community(wav_path, meta_dict, song_id):
    """Silently share songs/<id>/<id>.wav + meta.json to the community bucket. Never raises."""
    token = os.environ.get("HF_TOKEN") or os.environ.get("HUGGINGFACE_TOKEN")
    if not token:
        return
    try:
        from huggingface_hub import HfFileSystem
        fs = HfFileSystem(token=token)
        meta_local = os.path.join(OUT, f"{song_id}_meta.json")
        with open(meta_local, "w", encoding="utf-8") as f:
            json.dump(meta_dict, f, ensure_ascii=False, indent=2)
        base = f"buckets/{COMMUNITY_BUCKET}/songs/{song_id}"
        fs.put_file(wav_path, f"{base}/{song_id}.wav")
        fs.put_file(meta_local, f"{base}/meta.json")
        print(f"[community] uploaded {song_id} to bucket")
    except Exception as e:
        print(f"[community] upload failed: {e}")


@spaces.GPU(duration=120)
def generate(style, lyrics, cot, seed, cfg_scale):
    pipeline = get_pipeline()
    if seed is None or seed < 1:
        try:
            seed = int(seed) if seed else random.randint(1, 10**6)
        except Exception:
            seed = random.randint(1, 10**6)
    seed = int(seed)
    gen_kwargs = dict(style=style, lyrics=lyrics, cot=cot, seed=seed)
    if cfg_scale and float(cfg_scale) != 1.0:
        gen_kwargs["cfg_scale"] = float(cfg_scale)
    song = pipeline(**gen_kwargs)

    song_id = secrets.token_hex(6)
    flac_path = os.path.join(OUT, f"song_{song_id}.flac")
    mp3_path = os.path.join(OUT, f"song_{song_id}.mp3")
    wav_path = os.path.join(OUT, f"{song_id}.wav")
    song.save(flac_path)
    flac_to_mp3(flac_path, mp3_path)
    flac_to_wav(flac_path, wav_path)
    ogg_path = os.path.join(OUT, f"{song_id}.ogg")
    try:
        flac_to_ogg(flac_path, ogg_path)
    except Exception as e:
        print(f"[ogg] skipped: {e}")
        ogg_path = None

    words = (style or "").strip().replace("\n", " ").split()[:8]
    title = " ".join(words).title()[:80] if words else f"YuE2 Song {song_id[:6]}"
    actual = get_duration_seconds(wav_path)
    _upload_to_community(wav_path, {
        "id": song_id,
        "title": title,
        "description": (style or "").strip(),
        "lyrics": lyrics or "",
        "caption": (style or "").strip(),
        "bpm": 120,
        "language": "",
        "duration": actual,
        "seed": seed,
        "generation_mode": cot or "auto",
        "audio_file": f"{song_id}.wav",
        "created_at": datetime.now(timezone.utc).isoformat(),
    }, song_id)

    return flac_path, flac_path, mp3_path, ogg_path, f"seed={seed} | {actual:.0f}s | {os.path.getsize(mp3_path) // 1024}KB MP3 320kbps"


with gr.Blocks(title="YuE2-3B Music") as demo:
    gr.Markdown("# YuE2-3B Music Generator\nFull song (vocals + accompaniment) from a style prompt and lyrics.")
    with gr.Row():
        with gr.Column():
            style = gr.Textbox(label="Style prompt", value="Cyber metal, English, aggressive male vocals, distorted guitars, double-kick drums", lines=3)
            lyrics = gr.Textbox(label="Lyrics ([verse]/[chorus] etc.)", value="[verse]\nnothing here but static skies\nwe ride the current till it dies", lines=8)
            with gr.Row():
                cot = gr.Radio(["full", "melody", "off"], value="full", label="CoT mode")
                seed = gr.Number(label="Seed", value=0, precision=0)
                cfg = gr.Number(label="CFG scale (1.0 = default)", value=1.0, precision=2)
            btn = gr.Button("Generate song", variant="primary")
            gr.HTML("<div style='text-align:center;margin:-12px 0 -8px;font-size:0.9rem;'>Don't forget to<a style='margin-left:5px;padding:0;' href='https://huggingface.co/m-a-p/YuE2-3B' target='_blank'>like the model ❤️</a></div>")
        with gr.Column():
            audio = gr.Audio(label="Play song", type="filepath")
            flac_file = gr.File(label="Download FLAC")
            mp3_file = gr.File(label="Download MP3 320kbps")
            ogg_file = gr.File(label="Download OGG (Opus)")
            info = gr.Textbox(label="Info")
    btn.click(fn=generate, inputs=[style, lyrics, cot, seed, cfg], outputs=[audio, flac_file, mp3_file, ogg_file, info], queue=True)
    gr.Markdown(
        "---\nPowered by [YuE2](https://huggingface.co/m-a-p/YuE2-3B) · "
        "🎶 **Community songs:** every generation is auto-shared to the "
        "[YuE2-community bucket](https://huggingface.co/buckets/directedbykobyperez/YuE2-community)"
        " · [YuE2 website](https://map-yue2.github.io/)"
    )

demo.queue().launch(footer_links=[], favicon_path="favicon.png")