{"record":{"id":"c29bac8dc8b142ad","repo":"Comfy-Org/ComfyUI","slug":"a-voice-is-required-when-driving-the-video-with-a","errorCode":null,"errorMessage":"A voice is required when driving the video with a text script.","messagePattern":"A voice is required when driving the video with a text script\\.","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"comfy_api_nodes/nodes_heygen.py","lineNumber":63,"sourceCode":"}\n\n\nasync def _apply_speech_source(cls: type[IO.ComfyNode], payload: dict, speech: dict, require_voice: bool) -> None:\n    \"\"\"Fill script/audio speech fields of a /v3/videos payload from the DynamicCombo dict.\"\"\"\n    if speech[\"speech\"] == \"audio\":\n        payload[\"audio_url\"] = await upload_audio_to_comfyapi(\n            cls, speech[\"audio\"], container_format=\"mp3\", codec_name=\"libmp3lame\", mime_type=\"audio/mpeg\"\n        )\n    elif speech[\"speech\"] == \"script\":\n        validate_string(speech[\"text\"], strip_whitespace=True, min_length=1, max_length=5000)\n        payload[\"script\"] = speech[\"text\"]\n        voice_id = speech.get(\"custom_voice_id\", \"\").strip()\n        if not voice_id and speech[\"voice\"] != _DEFAULT_VOICE_OPTION:\n            voice_id = HEYGEN_VOICE_GENERAL_MAP[speech[\"voice\"]]\n        if voice_id:\n            payload[\"voice_id\"] = voice_id\n        elif require_voice:\n            raise ValueError(\"A voice is required when driving the video with a text script.\")\n        speed = speech.get(\"voice_speed\", 1.0)\n        if speed != 1.0:\n            payload[\"voice_settings\"] = {\"speed\": round(speed, 2)}\n\n\nasync def _create_and_poll_video(cls: type[IO.ComfyNode], payload: dict) -> dict:\n    \"\"\"POST a /v3/videos payload, poll until terminal, and return the final video data.\"\"\"\n    created = await sync_op_raw(\n        cls,\n        ApiEndpoint(path=_VIDEOS_PATH, method=\"POST\", headers={\"Idempotency-Key\": uuid.uuid4().hex}),\n        data=payload,\n    )\n    video_id = (created.get(\"data\") or {}).get(\"video_id\")\n    if not video_id:\n        raise ValueError(f\"HeyGen did not return a video_id: {created}\")\n    final = await poll_op_raw(\n        cls,\n        ApiEndpoint(path=f\"{_VIDEOS_PATH}/{video_id}\"),","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/Comfy-Org/ComfyUI/blob/1c6d8d45b3693bfbb32385b410d813a7fd6be216/comfy_api_nodes/nodes_heygen.py#L45-L81","documentation":"Thrown by HeyGen avatar-video flows when the speech source is a text script but no voice could be resolved. Voice resolution order: explicit custom_voice_id, then the voice combo mapped through HEYGEN_VOICE_GENERAL_MAP; if both are empty/default and the caller passed require_voice=True, the node refuses to build the payload because HeyGen cannot synthesize script audio without a voice_id.","triggerScenarios":"Selecting speech mode 'script' with a non-empty text, leaving voice at the default option, providing no custom_voice_id, in a node that calls _apply_speech_source(..., require_voice=True).","commonSituations":"User switches from audio-driven to script-driven generation and forgets the voice widget is still on its default sentinel value; or the custom_voice_id field contains only whitespace.","solutions":["Pick a voice from the voice dropdown when using script speech","Or paste a valid HeyGen voice_id into custom_voice_id (it takes precedence over the dropdown)","Or drive the video with uploaded audio instead of a script"],"exampleFix":"# before\nspeech = {\"speech\": \"script\", \"text\": \"Hello\", \"voice\": _DEFAULT_VOICE_OPTION}\n# after\nspeech = {\"speech\": \"script\", \"text\": \"Hello\", \"voice\": \"Daisy-Attentive\"}  # any non-default mapped voice","handlingStrategy":"validation","validationCode":"def script_voice_ok(speech: dict, default_voice: str) -> bool:\n    if speech['speech'] != 'script':\n        return True\n    return bool(speech.get('custom_voice_id', '').strip()) or speech['voice'] != default_voice","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Treat voice as a required field whenever speech mode is script","Prefer explicit custom_voice_id over the dropdown default"],"tags":["heygen","avatar-video","tts","voice","input-validation"],"backgroundTag":null,"analyzedSha":"1c6d8d45b3693bfbb32385b410d813a7fd6be216","analyzedAt":"2026-08-14T19:37:18.893Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}