# music3_worker.py - persistent MiniMax-Music3 job worker for the # makepad-ai-content `music` domain backend (music3_backend.rs). Line protocol # (same shape as fw_worker.py): # stdin : one JSON object per line: {"prompt": str, "lyrics": str, # "duration_s": float, "seed": int, "out_wav": str} or {"exit": true} # stdout: events prefixed "@EV " (everything else is ignored by the parent): # {"ev":"stage","stage":name[,"k":i,"n":total]} # {"ev":"ready"} after model load # {"ev":"done","wav":path} job finished # {"ev":"error","message":text} load/job failed (worker lives # on after job errors) # # Runtime: the official diffusers ModularPipeline integration # (MiniMaxAI/MiniMax-Music3 modular_model_index.json; diffusers PR #14456, # pinned venv commit dafe3733fcfdbf3c48915fe77be3aef65b5d6a2d per the model # card). Weights come from the service cache (registry-managed download), so # the worker runs fully hub-offline against --model-dir. # # The recorded component specs in modular_model_index.json point at the hub # repo id ("MiniMaxAI/MiniMax-Music3"); to guarantee offline loads we build a # hardlinked view of the model dir with those specs rewritten to the local # view path, then from_pretrained() the view. import argparse import json import os import shutil import sys os.environ.setdefault("HF_HUB_OFFLINE", "1") os.environ.setdefault("TRANSFORMERS_OFFLINE", "1") os.environ.setdefault("HF_HUB_DISABLE_TELEMETRY", "1") def ev(**kw): sys.stdout.write("@EV " + json.dumps(kw) + "\n") sys.stdout.flush() def build_local_view(model_dir, view_dir): """Hardlink (fallback copy) the cached model into view_dir, rewriting the modular_model_index.json component sources to the view path so every component loads from local disk regardless of how diffusers resolves the recorded hub repo id.""" for root, _dirs, files in os.walk(model_dir): rel = os.path.relpath(root, model_dir) dst_root = os.path.join(view_dir, rel) if rel != "." else view_dir os.makedirs(dst_root, exist_ok=True) for name in files: src = os.path.join(root, name) dst = os.path.join(dst_root, name) if os.path.exists(dst): if os.path.getsize(dst) == os.path.getsize(src): continue os.remove(dst) if name == "modular_model_index.json": with open(src, "r", encoding="utf-8") as f: index = json.load(f) for value in index.values(): if isinstance(value, list) and len(value) == 3 and isinstance(value[2], dict): if "pretrained_model_name_or_path" in value[2]: value[2]["pretrained_model_name_or_path"] = view_dir with open(dst, "w", encoding="utf-8") as f: json.dump(index, f, indent=1) continue try: os.link(src, dst) except OSError: shutil.copyfile(src, dst) def to_audio_array(audio): """Normalize the pipeline output (torch tensor OR numpy array, any of (T,), (ch, T), (T, ch), (batch, ch, T)) to float32 (channels, samples).""" try: import torch if isinstance(audio, torch.Tensor): audio = audio.detach().float().cpu().numpy() except ImportError: pass import numpy as np data = np.asarray(audio, dtype=np.float32) if data.ndim == 3: data = data[0] if data.ndim == 1: data = data[None, :] # Channels are the small axis (stereo); samples the long one. if data.shape[0] > data.shape[1]: data = data.T return data def write_wav_i16(path, data, sample_rate): """data: float32 array shaped (channels, samples) in [-1, 1].""" import wave import numpy as np pcm = (np.clip(data.T, -1.0, 1.0) * 32767.0).astype("