Spaces:
Running on Zero
Running on Zero
Harden composer + share video against the failure modes in production logs
#8
by multimodalart HF Staff - opened
app.py
CHANGED
|
@@ -496,7 +496,7 @@ Given a sound instruction and/or lyrics, produce global_metadata, vocal_details
|
|
| 496 |
Answer with ONLY a JSON object with keys: global_metadata, vocal_details, arrangement."""
|
| 497 |
|
| 498 |
|
| 499 |
-
def _llm_json(system, user):
|
| 500 |
import json as _json
|
| 501 |
|
| 502 |
from openai import OpenAI
|
|
@@ -506,25 +506,35 @@ def _llm_json(system, user):
|
|
| 506 |
last_error = None
|
| 507 |
# Three DISTINCT providers, all verified enabled for this account (bare/":fastest" can route to
|
| 508 |
# together, which 403s here and killed the fallbacks). Timeouts sized to measured composer latency.
|
| 509 |
-
|
| 510 |
-
|
| 511 |
-
|
| 512 |
-
|
| 513 |
-
|
| 514 |
-
|
| 515 |
-
|
| 516 |
-
|
| 517 |
-
|
| 518 |
-
|
| 519 |
-
|
| 520 |
-
|
| 521 |
-
|
| 522 |
-
|
| 523 |
-
|
| 524 |
-
|
| 525 |
-
|
| 526 |
-
|
| 527 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 528 |
raise gr.Error(
|
| 529 |
"The composer model is overloaded right now — try again in a moment, "
|
| 530 |
"or write the lyrics and structured prompt directly in the Studio tab."
|
|
@@ -534,7 +544,11 @@ def _llm_json(system, user):
|
|
| 534 |
def compose_song(description, duration):
|
| 535 |
if not description.strip():
|
| 536 |
raise gr.Error("Describe the song you want first.")
|
| 537 |
-
data = _llm_json(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 538 |
return data["lyrics"], data["global_metadata"], data["vocal_details"], data["arrangement"]
|
| 539 |
|
| 540 |
|
|
@@ -977,7 +991,11 @@ def render_video(wav_path, title):
|
|
| 977 |
|
| 978 |
import scipy.io.wavfile
|
| 979 |
|
| 980 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 981 |
mono = wave.astype(np.float32).mean(axis=1) / 32768.0
|
| 982 |
fps, size, bars = 24, 720, 56
|
| 983 |
total_frames = int(len(mono) / sr * fps)
|
|
@@ -1053,7 +1071,10 @@ def render_video(wav_path, title):
|
|
| 1053 |
if len(chunk) < window:
|
| 1054 |
chunk = np.pad(chunk, (0, window - len(chunk)))
|
| 1055 |
spectrum = np.abs(np.fft.rfft(chunk * np.hanning(window)))
|
| 1056 |
-
levels = np.array([
|
|
|
|
|
|
|
|
|
|
| 1057 |
levels = np.log1p(12 * np.nan_to_num(levels))
|
| 1058 |
smooth = np.maximum(levels, smooth * 0.85)
|
| 1059 |
frame = base.copy()
|
|
@@ -1524,6 +1545,7 @@ def compose_assist(raw_state, duration):
|
|
| 1524 |
f"Current structured prompt, keep the lyrics coherent with it:\n"
|
| 1525 |
f"Global metadata: {state['global_meta']}\nVocal details: {state['vocals']}\n"
|
| 1526 |
f"Arrangement: {state['arrangement']}\nTarget duration: {int(duration)} seconds.",
|
|
|
|
| 1527 |
)
|
| 1528 |
state["lyrics"] = data["lyrics"]
|
| 1529 |
return state, "Lyrics written — tweak them, or press Generate."
|
|
@@ -1532,6 +1554,7 @@ def compose_assist(raw_state, duration):
|
|
| 1532 |
_PROMPT_SYSTEM,
|
| 1533 |
f"Sound instruction: {instruction or description or '(none — describe a sound that fits the lyrics)'}\n"
|
| 1534 |
f"Current lyrics, keep the structured prompt coherent with them:\n{state['lyrics']}",
|
|
|
|
| 1535 |
)
|
| 1536 |
state.update(global_meta=data["global_metadata"], vocals=data["vocal_details"], arrangement=data["arrangement"])
|
| 1537 |
return state, "Structured prompt written — tweak it, or press Generate."
|
|
|
|
| 496 |
Answer with ONLY a JSON object with keys: global_metadata, vocal_details, arrangement."""
|
| 497 |
|
| 498 |
|
| 499 |
+
def _llm_json(system, user, required=()):
|
| 500 |
import json as _json
|
| 501 |
|
| 502 |
from openai import OpenAI
|
|
|
|
| 506 |
last_error = None
|
| 507 |
# Three DISTINCT providers, all verified enabled for this account (bare/":fastest" can route to
|
| 508 |
# together, which 403s here and killed the fallbacks). Timeouts sized to measured composer latency.
|
| 509 |
+
# Two passes over the chain: under load every provider can 429 transiently, and a second pass a few
|
| 510 |
+
# seconds later usually lands.
|
| 511 |
+
for attempt in range(2):
|
| 512 |
+
for model, timeout in (
|
| 513 |
+
("deepseek-ai/DeepSeek-V4-Flash-0731:baseten", 45),
|
| 514 |
+
("deepseek-ai/DeepSeek-V4-Flash-0731:deepinfra", 75),
|
| 515 |
+
("deepseek-ai/DeepSeek-V4-Flash-0731:novita", 100),
|
| 516 |
+
):
|
| 517 |
+
try:
|
| 518 |
+
completion = client.with_options(timeout=timeout).chat.completions.create(
|
| 519 |
+
model=model,
|
| 520 |
+
messages=[{"role": "system", "content": system}, {"role": "user", "content": user}],
|
| 521 |
+
)
|
| 522 |
+
text = completion.choices[0].message.content or ""
|
| 523 |
+
# Tolerate fences/preambles and reject truncated replies: parse the outermost {...} span.
|
| 524 |
+
start, end = text.find("{"), text.rfind("}")
|
| 525 |
+
if start == -1 or end <= start:
|
| 526 |
+
raise ValueError(f"no JSON object in composer reply (finish_reason={completion.choices[0].finish_reason})")
|
| 527 |
+
data = _json.loads(text[start : end + 1])
|
| 528 |
+
# Valid JSON with the wrong shape must retry too, not KeyError later.
|
| 529 |
+
missing = [key for key in required if key not in data]
|
| 530 |
+
if missing:
|
| 531 |
+
raise ValueError(f"composer reply missing keys: {missing}")
|
| 532 |
+
return data
|
| 533 |
+
except Exception as e:
|
| 534 |
+
print(f"composer attempt failed ({model}, pass {attempt + 1}): {type(e).__name__}: {e}", flush=True)
|
| 535 |
+
last_error = e
|
| 536 |
+
if attempt == 0:
|
| 537 |
+
time.sleep(3)
|
| 538 |
raise gr.Error(
|
| 539 |
"The composer model is overloaded right now — try again in a moment, "
|
| 540 |
"or write the lyrics and structured prompt directly in the Studio tab."
|
|
|
|
| 544 |
def compose_song(description, duration):
|
| 545 |
if not description.strip():
|
| 546 |
raise gr.Error("Describe the song you want first.")
|
| 547 |
+
data = _llm_json(
|
| 548 |
+
_COMPOSER_SYSTEM,
|
| 549 |
+
f"Song description: {description}\nTarget duration: {int(duration)} seconds.",
|
| 550 |
+
required=("lyrics", "global_metadata", "vocal_details", "arrangement"),
|
| 551 |
+
)
|
| 552 |
return data["lyrics"], data["global_metadata"], data["vocal_details"], data["arrangement"]
|
| 553 |
|
| 554 |
|
|
|
|
| 991 |
|
| 992 |
import scipy.io.wavfile
|
| 993 |
|
| 994 |
+
title = " ".join((title or "").split())[:96] or "MiniMax Music 3"
|
| 995 |
+
try:
|
| 996 |
+
sr, wave = scipy.io.wavfile.read(wav_path)
|
| 997 |
+
except (FileNotFoundError, OSError):
|
| 998 |
+
return gr.skip() # the visitor left and gradio cleaned the cached wav
|
| 999 |
mono = wave.astype(np.float32).mean(axis=1) / 32768.0
|
| 1000 |
fps, size, bars = 24, 720, 56
|
| 1001 |
total_frames = int(len(mono) / sr * fps)
|
|
|
|
| 1071 |
if len(chunk) < window:
|
| 1072 |
chunk = np.pad(chunk, (0, window - len(chunk)))
|
| 1073 |
spectrum = np.abs(np.fft.rfft(chunk * np.hanning(window)))
|
| 1074 |
+
levels = np.array([
|
| 1075 |
+
spectrum[m].mean() if (m := (freqs >= band_edges[b]) & (freqs < band_edges[b + 1])).any() else 0.0
|
| 1076 |
+
for b in range(bars)
|
| 1077 |
+
])
|
| 1078 |
levels = np.log1p(12 * np.nan_to_num(levels))
|
| 1079 |
smooth = np.maximum(levels, smooth * 0.85)
|
| 1080 |
frame = base.copy()
|
|
|
|
| 1545 |
f"Current structured prompt, keep the lyrics coherent with it:\n"
|
| 1546 |
f"Global metadata: {state['global_meta']}\nVocal details: {state['vocals']}\n"
|
| 1547 |
f"Arrangement: {state['arrangement']}\nTarget duration: {int(duration)} seconds.",
|
| 1548 |
+
required=("lyrics",),
|
| 1549 |
)
|
| 1550 |
state["lyrics"] = data["lyrics"]
|
| 1551 |
return state, "Lyrics written — tweak them, or press Generate."
|
|
|
|
| 1554 |
_PROMPT_SYSTEM,
|
| 1555 |
f"Sound instruction: {instruction or description or '(none — describe a sound that fits the lyrics)'}\n"
|
| 1556 |
f"Current lyrics, keep the structured prompt coherent with them:\n{state['lyrics']}",
|
| 1557 |
+
required=("global_metadata", "vocal_details", "arrangement"),
|
| 1558 |
)
|
| 1559 |
state.update(global_meta=data["global_metadata"], vocals=data["vocal_details"], arrangement=data["arrangement"])
|
| 1560 |
return state, "Structured prompt written — tweak it, or press Generate."
|