Batch Character Voices (v2)
OTR_BatchCharacterVoices
- character_audio
- render_log
- done
If OTR_AnnouncerVoice is the narrator, this is the cast. OTR_BatchCharacterVoices takes the frozen script and voices every character line in one go - dialogue, not bookends. It's the busiest node on the audio side of the OldTimeRadio pipeline, and the one that eats the most wall-clock time, because it's rendering a whole ensemble, line by line.
The thing to internalize: two characters never share a voice. The pack enforces it. So when you set the cast size upstream, you're committing to that many distinct voices coming out of whichever engine you pick here - and running out of unique voices is a fail-closed error, not a "close enough" reuse.
How it works
Same dispatch core as the announcer node, different role. It reads the frozen v2 ledger from OTR_LedgerFreezeCascade, filters to character lines, and sends them through the engine you chose. The default is indextts2 - that's the byte-identical legacy path, which delegates the raw script_json verbatim (zero transform) to the batch generator. chatterbox and dia are the opt-in per-line path, each line getting a frozen ResolvedVoiceRequest, prepared text, and an adapter call packed into an AUDIO batch. bark, kokoro, elevenlabs, and google_tts round out the roster; all the opt-ins are flag-gated and fail closed if they can't run.
The engine libraries are lazy-imported inside the node, so the pack stays cold until you actually render.
The inputs that matter
script_json (the frozen ledger) and engine are required; leave the engine on indextts2 unless you have a reason. The optional ledger_json from OTR_CastLock carries voice_ref_id and the episode seed - that's what lets the cast-lock node hand specific voice references to specific characters rather than hoping the writer's assignments survive. gate_in is the ordering signal for the node chain.
Outputs: character_audio (AUDIO - feeds OTR_SceneSequencer's tts_audio_clips), render_log, and done for chaining.
Install and gotchas
Pack-level install (ComfyUI Manager → ComfyUI-OldTimeRadio, or clone + git checkout v2.0-alpha, restart). Voice engines need their weights on disk - IndexTTS2 is the default character engine, and the pack fails loud with the exact expected path if it's missing. No silent substitution, ever: if the engine can't run, the render stops and tells you why.
Two practical notes. First, this is where episode runtime really lives - a 3-act episode can mean minutes of TTS, and an hour-long render for a full episode isn't unusual on a 16 GB laptop. Second, the pack's own known limitation: character drift. Occasionally a line lands with the wrong character - rare, doesn't break the render, but if you hear someone claim a job that belongs to another character, it's a known, documented edge of the writing stage, not a bug in this node's voice routing.
Inputs (4)
| Name | Type | Default | Description |
|---|---|---|---|
| script_json | STRING | [] | Frozen v2 ledger JSON from OTR_LedgerFreezeCascade (node 62 slot 1). Passed VERBATIM to the legacy engine on the byte-identical batch path. |
| engine | COMBO | indextts2 | Voice engine for this role. The legacy engine is the byte-identical default; opt-in engines are flag-gated until the GPU dependency pilot promotes them. Unusable selections fail closed with a named error at queue time. |
| ledger_jsonopt | STRING | Cast-locked ledger from OTR_CastLock (carries voice_ref_id / episode_seed). Read on the per-line path; the batch path ignores it and delegates the raw script_json. No episode_seed widget (I-4). | |
| gate_inopt | STRING | Optional ordering signal. Wire an upstream 'done' here to force this node to run after it. Optional on the first chain node; the audio chain binds it node->node. |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| character_audio | AUDIO | — |
| render_log | STRING | — |
| done | STRING | — |