Instructions to use rooroo79/music-spectrogram-diffusion with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Diffusers
How to use rooroo79/music-spectrogram-diffusion with Diffusers:
pip install -U diffusers transformers accelerate
import torch from diffusers import DiffusionPipeline # switch to "mps" for apple devices pipe = DiffusionPipeline.from_pretrained("rooroo79/music-spectrogram-diffusion", dtype=torch.bfloat16, device_map="cuda") prompt = "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k" image = pipe(prompt).images[0] - Notebooks
- Google Colab
- Kaggle
File size: 8,429 Bytes
d8d28a6 89eb823 d8d28a6 89eb823 c62ef95 97aa642 d8d28a6 89eb823 d8d28a6 89eb823 d8d28a6 7315c49 d8d28a6 c14d57a d8d28a6 c62ef95 97aa642 d8d28a6 c62ef95 d8d28a6 c62ef95 d8d28a6 8672734 d8d28a6 | 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 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 | """Custom Hugging Face Inference Endpoint handler.
Loads Google's Multi-Instrument Spectrogram Diffusion
(`google/music-spectrogram-diffusion`) from the endpoint's local snapshot
and turns a MIDI file into a 16 kHz WAV.
This file must live at the *root* of the Hub repo the endpoint points at,
alongside `requirements.txt`. `google/music-spectrogram-diffusion` has no
`handler.py` and no Transformers `config.json`, so a Custom task against
that repo falls back to `pipeline()` and crashes. Fork the model, add these
two files, then set the endpoint to that fork with task=custom.
Request body (Band's `/midi-to-audio` client):
{ "inputs": "<base64 .mid>", "parameters": { "prompt": "optional extra text" } }
Response:
{ "audio_base64": "<base64 wav>", "sample_rate": 16000, "format": "wav" }
The prompt is accepted so Band can send clip text; this pipeline does not
condition on it. Keep the field so a later handler can without a client change.
"""
from __future__ import annotations
import base64
import importlib
import io
import os
import subprocess
import sys
import tempfile
import traceback
import wave
from typing import Any
import numpy as np
import torch
def _ensure_pkg(module: str, *pip_names: str) -> None:
try:
importlib.import_module(module)
return
except ImportError:
pass
subprocess.check_call(
[sys.executable, "-m", "pip", "install", "--no-cache-dir", *pip_names],
)
importlib.invalidate_caches()
importlib.import_module(module)
# Diffusers exposes dummy SpectrogramDiffusionPipeline objects unless note-seq
# is present *before* that symbol is first resolved. Install extras first.
_ensure_pkg("note_seq", "note-seq", "pretty_midi")
_ensure_pkg("onnxruntime", "onnxruntime")
def _pipeline_classes():
try:
from diffusers import MidiProcessor, SpectrogramDiffusionPipeline
except ImportError: # moved under deprecated/ in newer Diffusers
from diffusers.pipelines.deprecated.spectrogram_diffusion import (
MidiProcessor,
SpectrogramDiffusionPipeline,
)
dummy = "dummy" in getattr(SpectrogramDiffusionPipeline, "__module__", "")
dummy = dummy or bool(getattr(SpectrogramDiffusionPipeline, "_backends", None))
if not dummy:
return MidiProcessor, SpectrogramDiffusionPipeline
for key in list(sys.modules):
if key == "diffusers" or key.startswith("diffusers."):
del sys.modules[key]
from diffusers import MidiProcessor, SpectrogramDiffusionPipeline
return MidiProcessor, SpectrogramDiffusionPipeline
MidiProcessor, SpectrogramDiffusionPipeline = _pipeline_classes()
def _patch_t5_cache_position() -> None:
"""Newer Transformers T5Attention requires cache_position; this pipeline never passes it."""
import inspect
try:
from transformers.models.t5.modeling_t5 import T5Attention
except ImportError:
return
orig = T5Attention.forward
if "cache_position" not in inspect.signature(orig).parameters:
return
def wrapped(self, *args, **kwargs): # type: ignore[no-untyped-def]
if kwargs.get("cache_position") is None:
hidden = args[0] if args else kwargs.get("hidden_states")
if hidden is not None:
kwargs["cache_position"] = torch.arange(hidden.shape[1], device=hidden.device)
return orig(self, *args, **kwargs)
T5Attention.forward = wrapped # type: ignore[method-assign]
_patch_t5_cache_position()
SAMPLE_RATE = 16000
FALLBACK_MODEL_ID = os.environ.get("HF_MSD_MODEL_ID", "google/music-spectrogram-diffusion")
class EndpointHandler:
def __init__(self, path: str = "") -> None:
# Inference Endpoints pass the local snapshot (`/repository`), which
# has `model_index.json` + decoder/encoder/melgan weights. Load that,
# not the Hub id — otherwise the replica re-downloads and ignores the
# fork this handler was deployed from.
model_path = path if _has_diffusers_index(path) else FALLBACK_MODEL_ID
if _has_diffusers_index(path):
# model_index.json names encoders as library `spectrogram_diffusion`.
# The shim package lives next to this handler in the snapshot.
sys.path.insert(0, os.path.abspath(path))
device = "cuda" if torch.cuda.is_available() else "cpu"
dtype = torch.float16 if device == "cuda" else torch.float32
self.pipe = SpectrogramDiffusionPipeline.from_pretrained(
model_path,
torch_dtype=dtype,
)
try:
self.pipe.to(device)
except Exception:
# MelGAN is ONNX and may refuse `.to()`. Move the PyTorch modules.
for name in ("notes_encoder", "continuous_encoder", "decoder"):
module = getattr(self.pipe, name, None)
if module is not None:
module.to(device)
self.processor = MidiProcessor()
self.device = device
def __call__(self, data: Any) -> dict[str, Any]:
try:
payload = data if isinstance(data, dict) else {"inputs": data}
midi_bytes = _midi_bytes(payload)
params = payload.get("parameters") if isinstance(payload.get("parameters"), dict) else {}
kwargs: dict[str, Any] = {}
steps = params.get("num_inference_steps")
if steps is not None:
kwargs["num_inference_steps"] = max(1, int(steps))
with tempfile.NamedTemporaryFile(suffix=".mid", delete=True) as tmp:
tmp.write(midi_bytes)
tmp.flush()
tokens = self.processor(tmp.name)
if not tokens:
raise ValueError("MIDI produced no spectrogram tokens (clip too short?)")
audio = self.pipe(tokens, **kwargs)
samples = _to_mono_float(audio)
wav = _wav_bytes(samples, SAMPLE_RATE)
return {
"audio_base64": base64.b64encode(wav).decode("ascii"),
"sample_rate": SAMPLE_RATE,
"format": "wav",
}
except Exception as err:
raise RuntimeError(
f"{type(err).__name__}: {err}; {_payload_preview(data)}\n{traceback.format_exc()}"
) from err
def _has_diffusers_index(path: str) -> bool:
return bool(path) and os.path.isfile(os.path.join(path, "model_index.json"))
def _payload_preview(data: Any) -> str:
if isinstance(data, dict):
keys = ",".join(sorted(str(k) for k in data.keys()))
raw = data.get("inputs", data.get("midi_base64"))
return f"payload_keys={keys} inputs_type={type(raw).__name__}"
return f"payload_type={type(data).__name__}"
def _midi_bytes(payload: dict[str, Any]) -> bytes:
raw = payload.get("inputs")
if raw is None:
raw = payload.get("midi_base64", "")
if isinstance(raw, list) and raw:
raw = raw[0]
if isinstance(raw, dict):
raw = raw.get("midi_base64") or raw.get("inputs") or raw.get("data") or ""
if isinstance(raw, (bytes, bytearray)):
midi = bytes(raw)
elif isinstance(raw, str) and raw:
text = raw.strip()
if text.lower().startswith("data:") and "," in text:
text = text.split(",", 1)[1]
midi = base64.b64decode(text)
else:
raise ValueError("missing base64 MIDI in inputs")
if len(midi) < 8 or midi[:4] != b"MThd":
raise ValueError("inputs is not a MIDI file")
return midi
def _to_mono_float(audio: Any) -> np.ndarray:
if hasattr(audio, "audios") and audio.audios is not None:
arr = np.asarray(audio.audios, dtype=np.float32)
elif isinstance(audio, (list, tuple)) and audio:
arr = np.asarray(audio[0], dtype=np.float32)
else:
arr = np.asarray(audio, dtype=np.float32)
arr = np.squeeze(arr)
if arr.ndim > 1:
arr = arr.reshape(-1)
peak = float(np.max(np.abs(arr))) if arr.size else 0.0
if peak > 1e-6:
arr = arr * (0.89 / peak)
return arr
def _wav_bytes(samples: np.ndarray, rate: int) -> bytes:
pcm = np.clip(samples, -1.0, 1.0)
pcm = (pcm * 32767.0).astype(np.int16)
buf = io.BytesIO()
with wave.open(buf, "wb") as wf:
wf.setnchannels(1)
wf.setsampwidth(2)
wf.setframerate(rate)
wf.writeframes(pcm.tobytes())
return buf.getvalue()
|