harp-dev commited on
Commit
2c29da2
·
0 Parent(s):

Deploy Stable Audio 3 editing endpoint

Browse files
Files changed (9) hide show
  1. .gitattributes +35 -0
  2. .gitignore +5 -0
  3. README.md +24 -0
  4. SOURCES.md +22 -0
  5. THIRD_PARTY_NOTICES.md +34 -0
  6. app.py +162 -0
  7. model.json +30 -0
  8. requirements.txt +26 -0
  9. stable_audio_runtime.py +172 -0
.gitattributes ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.py[cod]
3
+ .DS_Store
4
+ .ruff_cache/
5
+ .venv/
README.md ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Stable Audio 3
3
+ emoji: 🎛️
4
+ colorFrom: indigo
5
+ colorTo: gray
6
+ sdk: gradio
7
+ sdk_version: 5.28.0
8
+ python_version: '3.10'
9
+ app_file: app.py
10
+ pinned: false
11
+ license: other
12
+ short_description: Edit, inpaint, and continue music audio from text prompts
13
+ startup_duration_timeout: 1h
14
+ models:
15
+ - stabilityai/stable-audio-3-small-music
16
+ ---
17
+
18
+ HARP endpoint for editing music with Stable Audio 3 Small-Music. The endpoint
19
+ supports prompt-guided restyling, region inpainting, and continuation of an
20
+ uploaded music clip.
21
+
22
+ Model access is governed by the Stability AI Community License and the Gemma
23
+ Terms of Use. Source and license details are recorded in `SOURCES.md` and
24
+ `THIRD_PARTY_NOTICES.md`.
SOURCES.md ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Sources
2
+
3
+ ## Stable Audio 3
4
+
5
+ - Source: https://github.com/Stability-AI/stable-audio-3
6
+ - Source revision: `124e8a799f57a1f665495ecb72e547d0a62867f1`
7
+ - Source license: MIT
8
+ - Model: https://huggingface.co/stabilityai/stable-audio-3-small-music
9
+ - Model revision: `0fef1392cd842149a2b6d445e181c97608faac06`
10
+ - Model license: Stability AI Community License
11
+ - Paper: https://arxiv.org/abs/2605.17991
12
+
13
+ ## Runtime
14
+
15
+ - Source: https://github.com/Stability-AI/stable-audio-tools
16
+ - Source revision: `3241adba4fc2a85cf5b29d9eb68d42f40a28e820`
17
+ - Source license: MIT
18
+
19
+ The Space installs the runtime source without dependencies because its package
20
+ metadata fixes an older Torch version. Runtime dependencies are declared
21
+ separately in `requirements.txt` to remain compatible with Hugging Face
22
+ ZeroGPU and pyHARP.
THIRD_PARTY_NOTICES.md ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Third-Party Notices
2
+
3
+ ## Stable Audio 3 Source Code
4
+
5
+ The Stable Audio 3 source code is provided by Stability AI under the MIT
6
+ License:
7
+
8
+ https://github.com/Stability-AI/stable-audio-3
9
+
10
+ ## Stable Audio Tools
11
+
12
+ The Stable Audio Tools runtime is provided by Stability AI under the MIT
13
+ License:
14
+
15
+ https://github.com/Stability-AI/stable-audio-tools
16
+
17
+ ## Stable Audio 3 Small-Music Weights
18
+
19
+ The model weights are governed by the Stability AI Community License:
20
+
21
+ https://huggingface.co/stabilityai/stable-audio-3-small-music
22
+
23
+ https://stability.ai/license
24
+
25
+ Access to the model repository requires accepting its terms on Hugging Face.
26
+
27
+ ## T5Gemma
28
+
29
+ Stable Audio 3 Small-Music includes a T5Gemma text-conditioning component
30
+ redistributed under the Gemma Terms of Use:
31
+
32
+ https://huggingface.co/google/t5gemma-b-b-ul2
33
+
34
+ https://ai.google.dev/gemma/terms
app.py ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import gradio as gr
4
+ import soundfile as sf
5
+
6
+ try:
7
+ import spaces
8
+ except ImportError:
9
+ class spaces:
10
+ class GPU:
11
+ def __init__(self, func=None, duration=180):
12
+ self.func = func
13
+
14
+ def __call__(self, *args, **kwargs):
15
+ if self.func is not None:
16
+ return self.func(*args, **kwargs)
17
+ return args[0]
18
+
19
+ from pyharp import ModelCard, build_endpoint
20
+
21
+ from stable_audio_runtime import edit_audio
22
+
23
+ MIN_AUDIO_SECONDS = 5
24
+ MAX_AUDIO_SECONDS = 30
25
+
26
+ model_card = ModelCard(
27
+ name="Stable Audio 3",
28
+ description=(
29
+ "Restyle, inpaint, or continue music audio using a text prompt."
30
+ ),
31
+ author="Stability AI",
32
+ tags=[
33
+ "audio-generation",
34
+ "music-editing",
35
+ "audio-inpainting",
36
+ "audio-continuation",
37
+ ],
38
+ )
39
+
40
+
41
+ def _audio_duration(path: str | None) -> float:
42
+ if not path:
43
+ raise gr.Error("Please upload a music clip.")
44
+ try:
45
+ duration = float(sf.info(path).duration)
46
+ except Exception as exc:
47
+ raise gr.Error(f"Could not read the audio file: {exc}") from exc
48
+ if duration < MIN_AUDIO_SECONDS:
49
+ raise gr.Error(f"Audio must be at least {MIN_AUDIO_SECONDS} seconds.")
50
+ if duration > MAX_AUDIO_SECONDS:
51
+ raise gr.Error(f"Audio must be no longer than {MAX_AUDIO_SECONDS} seconds.")
52
+ return duration
53
+
54
+
55
+ @spaces.GPU(duration=180)
56
+ def process_fn(
57
+ audio_path: str | None,
58
+ prompt: str,
59
+ mode: str,
60
+ edit_start: float,
61
+ edit_end: float,
62
+ continuation_length: float,
63
+ strength: float,
64
+ seed: int,
65
+ ) -> str:
66
+ duration = _audio_duration(audio_path)
67
+ prompt = (prompt or "").strip()
68
+ if not prompt:
69
+ raise gr.Error("Please enter an editing prompt.")
70
+
71
+ if mode == "Inpaint" and (
72
+ edit_start < 0 or edit_end > duration or edit_end <= edit_start
73
+ ):
74
+ raise gr.Error(
75
+ "The inpaint region must be inside the uploaded clip, "
76
+ "with the end after the start."
77
+ )
78
+
79
+ try:
80
+ return edit_audio(
81
+ audio_path=audio_path,
82
+ prompt=prompt,
83
+ mode=mode,
84
+ edit_start=float(edit_start),
85
+ edit_end=float(edit_end),
86
+ continuation_length=float(continuation_length),
87
+ strength=float(strength),
88
+ seed=int(seed),
89
+ )
90
+ except gr.Error:
91
+ raise
92
+ except Exception as exc:
93
+ raise gr.Error(f"Stable Audio 3 inference failed: {exc}") from exc
94
+
95
+
96
+ with gr.Blocks(title="Stable Audio 3") as demo:
97
+ input_components = [
98
+ gr.Audio(type="filepath", label="Music Audio")
99
+ .harp_required(True)
100
+ .set_info("Music clip between 5 and 30 seconds."),
101
+ gr.Textbox(
102
+ label="Prompt",
103
+ placeholder="A warm synthwave groove with punchy drums",
104
+ ).harp_required(True),
105
+ gr.Dropdown(
106
+ choices=["Restyle", "Inpaint", "Continue"],
107
+ value="Restyle",
108
+ label="Edit Mode",
109
+ ),
110
+ gr.Slider(
111
+ minimum=0,
112
+ maximum=30,
113
+ value=4,
114
+ step=0.1,
115
+ label="Edit Start (seconds)",
116
+ ).set_info("Used by Inpaint mode."),
117
+ gr.Slider(
118
+ minimum=0,
119
+ maximum=30,
120
+ value=8,
121
+ step=0.1,
122
+ label="Edit End (seconds)",
123
+ ).set_info("Used by Inpaint mode."),
124
+ gr.Slider(
125
+ minimum=1,
126
+ maximum=15,
127
+ value=8,
128
+ step=1,
129
+ label="Continuation Length (seconds)",
130
+ ).set_info("Used by Continue mode."),
131
+ gr.Slider(
132
+ minimum=0.1,
133
+ maximum=1.0,
134
+ value=0.75,
135
+ step=0.05,
136
+ label="Transformation Strength",
137
+ ).set_info("Used by Restyle mode."),
138
+ gr.Slider(
139
+ minimum=0,
140
+ maximum=99999,
141
+ value=0,
142
+ step=1,
143
+ label="Seed",
144
+ ).set_info("Use 0 for a random seed."),
145
+ ]
146
+ output_components = [
147
+ gr.Audio(type="filepath", label="Edited Audio"),
148
+ ]
149
+ build_endpoint(
150
+ model_card=model_card,
151
+ input_components=input_components,
152
+ output_components=output_components,
153
+ process_fn=process_fn,
154
+ )
155
+
156
+
157
+ if __name__ == "__main__":
158
+ demo.queue(default_concurrency_limit=1).launch(
159
+ show_error=True,
160
+ pwa=True,
161
+ ssr_mode=False,
162
+ )
model.json ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "Stable Audio 3 Small-Music",
3
+ "task": [
4
+ "audio-to-audio editing",
5
+ "audio inpainting",
6
+ "audio continuation"
7
+ ],
8
+ "source": {
9
+ "repo": "Stability-AI/stable-audio-3",
10
+ "revision": "124e8a799f57a1f665495ecb72e547d0a62867f1",
11
+ "license": "MIT"
12
+ },
13
+ "runtime": {
14
+ "repo": "Stability-AI/stable-audio-tools",
15
+ "revision": "3241adba4fc2a85cf5b29d9eb68d42f40a28e820",
16
+ "license": "MIT"
17
+ },
18
+ "model": {
19
+ "repo": "stabilityai/stable-audio-3-small-music",
20
+ "revision": "0fef1392cd842149a2b6d445e181c97608faac06",
21
+ "license": "Stability AI Community License"
22
+ },
23
+ "limits": {
24
+ "input_seconds": {
25
+ "minimum": 5,
26
+ "maximum": 30
27
+ },
28
+ "maximum_continuation_seconds": 15
29
+ }
30
+ }
requirements.txt ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ gradio==5.28.0
2
+ torch==2.8.0
3
+ torchaudio==2.8.0
4
+ numpy<2
5
+ soundfile>=0.13.1
6
+ einops>=0.8.2
7
+ einops-exts>=0.0.4
8
+ alias-free-torch==0.0.6
9
+ dill>=0.4
10
+ huggingface-hub>=1.7.1,<2
11
+ importlib-resources>=5.12
12
+ k-diffusion==0.1.1
13
+ nnAudio
14
+ PyWavelets>=1.6,<2
15
+ pytorch-lightning==2.5.5
16
+ safetensors>=0.7.0
17
+ scipy
18
+ sentencepiece>=0.2,<1
19
+ setuptools<81
20
+ soxr
21
+ torchsde
22
+ tqdm
23
+ transformers>=5.8.0,<6
24
+ v-diffusion-pytorch==0.0.2
25
+ vector-quantize-pytorch==1.14.41
26
+ git+https://github.com/TEAMuP-dev/pyharp.git@d65c4f7d0264dcdb3024a6c5466cddd7b2defdca
stable_audio_runtime.py ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import subprocess
5
+ import sys
6
+ import tempfile
7
+ import threading
8
+ from pathlib import Path
9
+
10
+ MODEL_REPO = "stabilityai/stable-audio-3-small-music"
11
+ MODEL_REVISION = "0fef1392cd842149a2b6d445e181c97608faac06"
12
+ STABLE_AUDIO_TOOLS_REVISION = "3241adba4fc2a85cf5b29d9eb68d42f40a28e820"
13
+ OUTPUT_ROOT = Path(tempfile.gettempdir()) / "stable_audio_3_outputs"
14
+
15
+ _MODEL = None
16
+ _MODEL_CONFIG = None
17
+ _MODEL_LOCK = threading.Lock()
18
+
19
+
20
+ def _ensure_stable_audio_tools() -> None:
21
+ try:
22
+ import stable_audio_tools # noqa: F401
23
+
24
+ return
25
+ except ImportError:
26
+ pass
27
+
28
+ subprocess.check_call(
29
+ [
30
+ sys.executable,
31
+ "-m",
32
+ "pip",
33
+ "install",
34
+ "--quiet",
35
+ "--no-deps",
36
+ (
37
+ "git+https://github.com/Stability-AI/stable-audio-tools.git@"
38
+ f"{STABLE_AUDIO_TOOLS_REVISION}"
39
+ ),
40
+ ]
41
+ )
42
+
43
+
44
+ def _load_model():
45
+ global _MODEL, _MODEL_CONFIG
46
+ if _MODEL is not None:
47
+ return _MODEL, _MODEL_CONFIG
48
+
49
+ with _MODEL_LOCK:
50
+ if _MODEL is not None:
51
+ return _MODEL, _MODEL_CONFIG
52
+
53
+ _ensure_stable_audio_tools()
54
+ token = os.environ.get("HF_TOKEN")
55
+ if not token:
56
+ raise RuntimeError("The HF_TOKEN Space secret is not configured.")
57
+
58
+ import torch
59
+ from stable_audio_tools.models import pretrained
60
+
61
+ original_download = pretrained.hf_hub_download
62
+
63
+ def pinned_download(repo_id, *args, **kwargs):
64
+ if repo_id == MODEL_REPO:
65
+ kwargs.setdefault("revision", MODEL_REVISION)
66
+ kwargs.setdefault("token", token)
67
+ return original_download(repo_id, *args, **kwargs)
68
+
69
+ pretrained.hf_hub_download = pinned_download
70
+ try:
71
+ model, config = pretrained.get_pretrained_model(MODEL_REPO)
72
+ finally:
73
+ pretrained.hf_hub_download = original_download
74
+
75
+ model = model.to("cuda").to(torch.float16)
76
+ model.eval().requires_grad_(False)
77
+ _MODEL = model
78
+ _MODEL_CONFIG = config
79
+ return _MODEL, _MODEL_CONFIG
80
+
81
+
82
+ def _load_audio(path: str):
83
+ import torch
84
+ import torchaudio
85
+
86
+ audio, sample_rate = torchaudio.load(path)
87
+ if audio.shape[0] > 2:
88
+ audio = audio[:2]
89
+ return int(sample_rate), audio.to(torch.float32)
90
+
91
+
92
+ def _save_audio(output, sample_rate: int) -> str:
93
+ import soundfile as sf
94
+ import torch
95
+
96
+ output = output.permute(1, 0, 2).reshape(output.shape[1], -1)
97
+ output = output.to(torch.float32)
98
+ peak = output.abs().max().clamp(min=1e-9)
99
+ output = output.div(peak).clamp(-1, 1).cpu().numpy().T
100
+
101
+ OUTPUT_ROOT.mkdir(parents=True, exist_ok=True)
102
+ output_dir = Path(tempfile.mkdtemp(dir=OUTPUT_ROOT))
103
+ output_path = output_dir / "stable_audio_3_edit.wav"
104
+ sf.write(output_path, output, sample_rate, subtype="PCM_16")
105
+ return str(output_path)
106
+
107
+
108
+ def edit_audio(
109
+ audio_path: str,
110
+ prompt: str,
111
+ mode: str,
112
+ edit_start: float,
113
+ edit_end: float,
114
+ continuation_length: float,
115
+ strength: float,
116
+ seed: int,
117
+ ) -> str:
118
+ import torch
119
+ import torchaudio
120
+
121
+ model, config = _load_model()
122
+ from stable_audio_tools.inference.generation import (
123
+ generate_diffusion_cond_inpaint,
124
+ )
125
+ sample_rate = int(config["sample_rate"])
126
+ sample_size = int(config["sample_size"])
127
+ source_rate, source = _load_audio(audio_path)
128
+ source_duration = source.shape[-1] / source_rate
129
+
130
+ if source_rate != sample_rate:
131
+ source = torchaudio.functional.resample(source, source_rate, sample_rate)
132
+ model_dtype = next(model.parameters()).dtype
133
+ source_tuple = (sample_rate, source.to(model_dtype))
134
+
135
+ if mode == "Continue":
136
+ output_duration = source_duration + continuation_length
137
+ else:
138
+ output_duration = source_duration
139
+
140
+ conditioning = [{"prompt": prompt, "seconds_total": output_duration}]
141
+ kwargs = {
142
+ "steps": 8,
143
+ "cfg_scale": 1.0,
144
+ "conditioning": conditioning,
145
+ "sample_size": sample_size,
146
+ "sampler_type": "pingpong",
147
+ "seed": int(seed) if seed > 0 else -1,
148
+ "device": "cuda",
149
+ "sigma_max": 1.0,
150
+ "apg_scale": 1.0,
151
+ "duration_padding_sec": 6.0,
152
+ }
153
+
154
+ if mode == "Restyle":
155
+ kwargs["init_audio"] = source_tuple
156
+ kwargs["init_noise_level"] = float(strength)
157
+ elif mode == "Inpaint":
158
+ kwargs["inpaint_audio"] = source_tuple
159
+ kwargs["inpaint_mask_start_seconds"] = float(edit_start)
160
+ kwargs["inpaint_mask_end_seconds"] = float(edit_end)
161
+ elif mode == "Continue":
162
+ kwargs["inpaint_audio"] = source_tuple
163
+ kwargs["inpaint_mask_start_seconds"] = float(source_duration)
164
+ kwargs["inpaint_mask_end_seconds"] = float(output_duration)
165
+ else:
166
+ raise ValueError(f"Unknown edit mode: {mode}")
167
+
168
+ with torch.inference_mode():
169
+ output = generate_diffusion_cond_inpaint(model, **kwargs)
170
+
171
+ output = output[..., : int(output_duration * sample_rate)]
172
+ return _save_audio(output, sample_rate)