{"record":{"id":"1a12782b6eee91b6","repo":"babysor/MockingBird","slug":"cue-cue-index-either-voice-id-or-reference-audi","errorCode":null,"errorMessage":"Cue {cue.index}: either voice_id or reference_audio required.","messagePattern":"Cue (.+?): either voice_id or reference_audio required\\.","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/speak/scripts/render_timeline.py","lineNumber":282,"sourceCode":"        payload[\"save_voice\"] = _bool_form(cfg[\"save_voice\"])\n    if \"emo\" in cfg and cfg[\"emo\"] is not None:\n        emo = cfg[\"emo\"]\n        payload[\"emo\"] = emo if isinstance(emo, str) else json.dumps(emo)\n\n    files = None\n    ref_cleanup: Optional[Path] = None\n    ref = cfg.get(\"reference_audio\")\n    if ref:\n        ref_path, ref_cleanup = _resolve_reference_audio(ref, timeout)\n        files = {\n            \"file\": (\n                ref_path.name,\n                ref_path.open(\"rb\"),\n                \"application/octet-stream\",\n            )\n        }\n    elif not cfg.get(\"voice_id\"):\n        raise ValueError(\n            f\"Cue {cue.index}: either voice_id or reference_audio required.\"\n        )\n\n    try:\n        resp = requests.post(\n            url, headers={\"Authorization\": api_key},\n            data=payload, files=files, timeout=timeout,\n        )\n    finally:\n        if files and files[\"file\"][1]:\n            files[\"file\"][1].close()\n        if ref_cleanup is not None:\n            ref_cleanup.unlink(missing_ok=True)\n\n    if resp.status_code != 200:\n        raise RuntimeError(\n            f\"/text-to-speech cue {cue.index}: \"\n            f\"status={resp.status_code}, body={resp.text}\"","sourceCodeStart":264,"sourceCodeEnd":300,"githubUrl":"https://github.com/babysor/MockingBird/blob/28dc5e14f12d7c754612af2fde8e78a4b03f8616/skills/speak/scripts/render_timeline.py#L264-L300","documentation":"ValueError raised in _noiz_tts when a cue's voice-map entry provides neither reference_audio nor voice_id. Each cue must resolve to exactly one voice source for the Noiz backend.","triggerScenarios":"A voice_map entry containing only options like speed or emotion but no voice identifiers, or a cue referencing a missing voice-map key that falls through to an empty config.","commonSituations":"Typo in the voice-map key so the cue's entry is an empty dict, partially written config files, or entries copied from another backend's schema.","solutions":["Add voice_id or reference_audio to the voice-map entry named in the error","Check the cue's voice key matches a defined voice-map entry","Validate the voice map at startup: every referenced entry has voice_id or reference_audio"],"exampleFix":"# before\n{\"evil\": {\"speed\": 1.1}}\n# after\n{\"evil\": {\"voice_id\": \"villain-01\", \"speed\": 1.1}}","handlingStrategy":"validation","validationCode":"for name, cfg in voice_map.items():\n    assert cfg.get('voice_id') or cfg.get('reference_audio'), f'voice {name!r} lacks voice source'","typeGuard":"def cue_has_voice_source(cfg: dict) -> bool:\n    return bool(cfg.get('voice_id') or cfg.get('reference_audio'))","tryCatchPattern":"try:\n    _noiz_tts(cue, cfg, fmt, ...)\nexcept ValueError as e:\n    sys.exit(f'fix voice map: {e}')","preventionTips":["Validate voice map at startup","Require a default voice entry","Test configs with a 1-cue SRT first"],"tags":["config","voice","validation","noiz-tts"],"backgroundTag":"missing-required-parameter","analyzedSha":"28dc5e14f12d7c754612af2fde8e78a4b03f8616","analyzedAt":"2026-08-27T02:26:53.589Z","schemaVersion":2},"datasetVersion":"2026-08-27T03:17:27.898Z"}