Comfy-Org/ComfyUI · error · ValueError
A voice is required when driving the video with a text scrip
Error message
A voice is required when driving the video with a text script.
What it means
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.
Source
Thrown at comfy_api_nodes/nodes_heygen.py:63
}
async def _apply_speech_source(cls: type[IO.ComfyNode], payload: dict, speech: dict, require_voice: bool) -> None:
"""Fill script/audio speech fields of a /v3/videos payload from the DynamicCombo dict."""
if speech["speech"] == "audio":
payload["audio_url"] = await upload_audio_to_comfyapi(
cls, speech["audio"], container_format="mp3", codec_name="libmp3lame", mime_type="audio/mpeg"
)
elif speech["speech"] == "script":
validate_string(speech["text"], strip_whitespace=True, min_length=1, max_length=5000)
payload["script"] = speech["text"]
voice_id = speech.get("custom_voice_id", "").strip()
if not voice_id and speech["voice"] != _DEFAULT_VOICE_OPTION:
voice_id = HEYGEN_VOICE_GENERAL_MAP[speech["voice"]]
if voice_id:
payload["voice_id"] = voice_id
elif require_voice:
raise ValueError("A voice is required when driving the video with a text script.")
speed = speech.get("voice_speed", 1.0)
if speed != 1.0:
payload["voice_settings"] = {"speed": round(speed, 2)}
async def _create_and_poll_video(cls: type[IO.ComfyNode], payload: dict) -> dict:
"""POST a /v3/videos payload, poll until terminal, and return the final video data."""
created = await sync_op_raw(
cls,
ApiEndpoint(path=_VIDEOS_PATH, method="POST", headers={"Idempotency-Key": uuid.uuid4().hex}),
data=payload,
)
video_id = (created.get("data") or {}).get("video_id")
if not video_id:
raise ValueError(f"HeyGen did not return a video_id: {created}")
final = await poll_op_raw(
cls,
ApiEndpoint(path=f"{_VIDEOS_PATH}/{video_id}"),View on GitHub (pinned to 1c6d8d45b3)
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
Example fix
# before
speech = {"speech": "script", "text": "Hello", "voice": _DEFAULT_VOICE_OPTION}
# after
speech = {"speech": "script", "text": "Hello", "voice": "Daisy-Attentive"} # any non-default mapped voice Defensive patterns
Strategy: validation
Validate before calling
def script_voice_ok(speech: dict, default_voice: str) -> bool:
if speech['speech'] != 'script':
return True
return bool(speech.get('custom_voice_id', '').strip()) or speech['voice'] != default_voice Prevention
- Treat voice as a required field whenever speech mode is script
- Prefer explicit custom_voice_id over the dropdown default
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- Avatar '{avatar_label}' does not support the {engine} engine
- background_color must be a hex color code like '#00ff00'.
- HeyGen accepts at most 3 reference images; got {n_images}.
- HeyGen did not return an audio_url: {response}
- Unsupported process_res_method: {method}
AI-assisted analysis of Comfy-Org/ComfyUI@1c6d8d45b3 (2026-08-14).
Data as JSON: /api/errors/c29bac8dc8b142ad.
Report an issue: GitHub.