Nodes/ComfyUI-OldTimeRadio/ Batch Character Voices (v2)
ComfyUI Node

Batch Character Voices (v2)

OTR_BatchCharacterVoices

By jbrick2070·Created 5 months ago·Updated about 16 hours ago· 7
Batch Character Voices (v2)
    • character_audio
    • render_log
    • done
    script_json[]
    engineindextts2
    ledger_json
    gate_in

    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.

    CategoryOldTimeRadio/v2/audio

    Inputs (4)

    NameTypeDefaultDescription
    script_jsonSTRING[]Frozen v2 ledger JSON from OTR_LedgerFreezeCascade (node 62 slot 1). Passed VERBATIM to the legacy engine on the byte-identical batch path.
    engineCOMBOindextts2Voice 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_jsonoptSTRINGCast-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_inoptSTRINGOptional 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)

    NameTypeDescription
    character_audioAUDIO
    render_logSTRING
    doneSTRING