File size: 4,480 Bytes
34034a6
 
7a0e0e3
 
34034a6
 
 
 
 
 
 
6a09a47
7a0e0e3
34034a6
 
 
 
 
 
 
 
610c2ae
7a0e0e3
 
8773b1b
 
 
610c2ae
 
 
7a0e0e3
34034a6
 
 
212a4a4
 
 
 
 
 
 
34034a6
 
 
 
 
 
 
fa65af9
 
34034a6
 
 
 
 
 
 
 
 
 
 
 
 
9af9b85
 
34034a6
 
 
 
9af9b85
 
34034a6
9af9b85
 
34034a6
 
 
 
 
 
 
 
 
612110a
 
 
 
 
 
34034a6
 
 
 
 
 
 
 
abbb507
 
 
f36638a
abbb507
 
 
 
8fe5d58
 
 
 
abbb507
 
 
 
 
8fe5d58
 
 
 
abbb507
 
 
8fe5d58
 
 
abbb507
 
 
 
 
 
 
34034a6
 
92ba003
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
from __future__ import annotations

import spaces  # imported first, as ZeroGPU requires

import uuid
from pathlib import Path
from tempfile import gettempdir

import gradio as gr
import soundfile as sf

from pyharp import ModelCard, build_endpoint
from pyharp.tags import Channels, SampleRate, Subcategory

from musicgen_runtime import generate, warmup

MAX_INPUT_SECONDS = 30
OUTPUT_ROOT = Path(gettempdir()) / "musicgen_outputs"

model_card = ModelCard(
    name="MusicGen",
    description=(
        "Generate music from a text prompt with melody conditioning, as 32 kHz mono audio. "
        "The code "
        "is MIT, but Meta's MusicGen weights are CC-BY-NC-4.0, so results "
        "cannot be used commercially. Repository: "
        "https://github.com/facebookresearch/audiocraft Paper: "
        "https://arxiv.org/abs/2306.05284"
    ),
    author="Jade Copet, Felix Kreuk, Itai Gat, et al.",
    tags=[Subcategory.HOLISTIC, SampleRate(32000), Channels(1), 'melody-conditioning'],
)


# pyharp runs process_fn in a worker, which imports this file as __mp_main__ and starts with the
# server. The model is downloaded, built and placed there alone, where ZeroGPU's emulation records
# the CUDA placement, so that no request moves it inside its GPU reservation.
if __name__ != "__main__":
    warmup()


def _validate_audio(path: str | None) -> str | None:
    if not path:
        return None
    try:
        duration = sf.info(path).duration
    except Exception as exc:
        raise gr.Error(f"Could not read the audio file: {exc}") from exc
    if duration == 0:
        raise gr.Error("The melody file is empty.")
    if duration > MAX_INPUT_SECONDS:
        raise gr.Error(
            f"Melody audio must be no longer than {MAX_INPUT_SECONDS} seconds. "
            f"Received {duration:.1f} seconds."
        )
    return path


def process_fn(
    audio_path: str | None,
    prompt: str,
    duration: str,
) -> str:
    # Checked before the GPU is reserved, so a request the model cannot take spends none of the
    # caller's ZeroGPU allowance
    prompt = (prompt or "").strip()
    if not prompt:
        raise gr.Error("Please enter a text description.")

    return compose(_validate_audio(audio_path), prompt, int(duration))


@spaces.GPU(duration=120)
def compose(melody_path: str | None, prompt: str, duration_seconds: int) -> str:
    try:
        audio, sample_rate = generate(
            prompt=prompt,
            duration_seconds=duration_seconds,
            audio_path=melody_path,
        )
    except Exception as exc:
        raise gr.Error(f"MusicGen generation failed: {exc}") from exc

    # Generation can overshoot full scale, which writing would flatten, so the result is scaled
    # down to fit when it exceeds it
    peak = audio.abs().max()
    if peak > 1.0:
        audio = audio / peak

    output_dir = OUTPUT_ROOT / uuid.uuid4().hex
    output_dir.mkdir(parents=True, exist_ok=True)
    output_path = output_dir / "musicgen_output.wav"
    sf.write(str(output_path), audio.numpy(), sample_rate)

    return str(output_path)


if __name__ == "__main__":
    with gr.Blocks(title="MusicGen") as demo:
        input_components = [
            gr.Audio(type="filepath", label="Melody (optional)").harp_required(False)
            .set_info("Melody audio to condition on, up to 30 seconds."),
            gr.Textbox(
                label="Text Description",
                placeholder="e.g. An upbeat electronic dance track with heavy bass",
                info=(
                    "Describe the music to generate: genre, instrumentation, mood, "
                    "tempo."
                ),
            ),
            gr.Dropdown(
                choices=["5", "10", "15", "20"],
                value="10",
                label="Duration (seconds)",
                info=(
                    "Length of the result. Longer generations drift further from the "
                    "prompt."
                ),
            ),
        ]
        output_components = [
            gr.Audio(type="filepath", label="Generated Music").set_info((
                "The generated music, at 32 kHz mono."
            )),
        ]
        build_endpoint(
            model_card=model_card,
            input_components=input_components,
            output_components=output_components,
            process_fn=process_fn,
        )


    demo.queue(default_concurrency_limit=1).launch(show_error=True, pwa=True, ssr_mode=False)