babysor/MockingBird · warning · RuntimeError

/emotion-enhance returned no data: {resp.text}

Error message

/emotion-enhance returned no data: {resp.text}

What it means

Raised when /emotion-enhance answers 200 but data.emotion_enhance is missing or falsy in the JSON — a response-shape mismatch during timeline rendering.

Source

Thrown at skills/speak/scripts/render_timeline.py:216

def _noiz_emotion_enhance(
    base_url: str, api_key: str, text: str, timeout: int
) -> str:
    import requests  # noqa: delayed import so kokoro path doesn't need requests

    resp = requests.post(
        f"{base_url.rstrip('/')}/emotion-enhance",
        headers={"Authorization": api_key, "Content-Type": "application/json"},
        json={"text": text},
        timeout=timeout,
    )
    if resp.status_code != 200:
        raise RuntimeError(
            f"/emotion-enhance failed: status={resp.status_code}, body={resp.text}"
        )
    enhanced = resp.json().get("data", {}).get("emotion_enhance")
    if not enhanced:
        raise RuntimeError(f"/emotion-enhance returned no data: {resp.text}")
    return enhanced


def _bool_form(v: Any) -> str:
    return "true" if bool(v) else "false"


def _resolve_reference_audio(ref: str, timeout: int) -> Tuple[Path, Optional[Path]]:
    """Resolve reference_audio to a path. If ref is a URL, download to temp file.
    Returns (path_to_use, temp_path_to_cleanup_or_None)."""
    if ref.startswith("http://") or ref.startswith("https://"):
        import requests
        tmp = tempfile.NamedTemporaryFile(suffix=".wav", delete=False)
        tmp.close()
        r = requests.get(ref, timeout=timeout)
        r.raise_for_status()
        Path(tmp.name).write_bytes(r.content)
        return Path(tmp.name), Path(tmp.name)

View on GitHub (pinned to 28dc5e14f1)

Solutions

  1. Log resp.text to inspect the actual payload
  2. Skip enhancement for that cue and use the raw cue text
  3. Confirm the API schema version matches data.emotion_enhance
  4. Strip/skip empty-text cues before rendering

Example fix

# before
enhanced = resp.json().get("data", {}).get("emotion_enhance")
if not enhanced:
    raise RuntimeError(...)
# after
enhanced = resp.json().get("data", {}).get("emotion_enhance") or cue.text
Defensive patterns

Strategy: fallback

Validate before calling

if not cue.text.strip():
    continue  # skip empty cues before enhancement

Try / catch

try:
    enhanced = _noiz_emotion_enhance(...)
except RuntimeError as e:
    if 'no data' in str(e): enhanced = cue.text
    else: raise

Prevention

When it happens

Trigger: Server returns 200 with an empty envelope, an HTML page, or a schema from a different API version during a multi-cue render.

Common situations: API version drift, gateway interference returning empty JSON, or blank cue text (whitespace-only subtitle lines).

Related errors


AI-assisted analysis of babysor/MockingBird@28dc5e14f1 (2026-08-27). Data as JSON: /api/errors/65a30725eb96b417. Report an issue: GitHub.