ElevenLabs Pro - Text to Dialogue with Timestamps
Two Voices, One Take — Multi-Speaker Dialogue, With Timestamps Attached
- audio
- timestamps_json
- voice_segments_json
Run two separate TTS calls and stitch them together and you get two separate TTS calls stitched together - the second speaker starts on a fresh breath, the pacing doesn't answer the first line, and it sounds exactly like what it is. This node avoids that by handing ElevenLabs the whole conversation at once: up to ten speakers, each with their own voice, generated as one continuous performance. And then it gives you back the timing data that makes the result usable in a video edit instead of just audible.
That second part is the difference between this node and its sibling Text to Dialogue in the same pack. Same request, different endpoint - this one goes to the /with-timestamps route, so you get the audio and character-level alignment and a per-voice segment breakdown.
Why you'd reach for it
Local TTS has come a long way - the open models closed the cloning gap - but multi-speaker dialogue in a single generation is still a job the API does cleanly, and ElevenLabs is the community's reliability and language bar for exactly this. Two real use cases: a talking-head or animation pipeline, where two characters on screen need to sound like they're actually in a conversation and you need timings to cut the shot at the right frame; and long-form narration - an audiobook chapter, a podcast bit, a comic dub - where you want an SRT out the other end without hand-timing anything.
How it works
The node POSTs to ElevenLabs' /v1/text-to-dialogue/with-timestamps endpoint with an inputs array, one {text, voice_id} object per speaker, plus your model and settings. The response carries the audio base64-encoded, an alignment block, and a voice_segments block. The node decodes the audio into a ComfyUI AUDIO dict, pretty-prints the two JSON blocks into strings, and hands all three back. Empty audio from the API becomes a second of silence rather than an exception - a choice you'll appreciate at 2am and resent if you don't notice it happened.
Two guards, both from the shipped code. A speaker pair only gets sent if both the text and the voice ID are filled in. And the concatenated length of every speaker's text is validated against the model's character limit before the request goes out, so you get a local error instead of a 422 after paying for nothing.
The inputs that matter
text1+voice_id1are required and must be a matched pair.text2/voice_id2through10are optional, same rule. There's no "SPEAKER 1:" formatting inside one field - one field per speaker is the interface.modeliseleven_v3(default) oreleven_v4. That's the whole list; the dialogue endpoint doesn't take the turbo/flash models. v3 caps at 5,000 characters total, v4 at 10,000.stabilitystarts at 0.5 - Creative below 0.5, Natural at 0.5, Robust above. Push it up if a character keeps drifting in delivery.apply_text_normalizationdefaults tooffand should stay there; eleven_v3 only acceptsoffand the node will quietly downgrade you if you fight it.output_formatdefaults tomp3_44100_192, which needs Creator tier or above. On a lower tier, drop tomp3_44100_128before you queue, or you'll get a billing-tier error instead of audio.enable_loggingset to false requests zero-retention - the audio and text aren't stored on ElevenLabs' side. Flip it if you're doing anything you'd rather not have in a vendor log.previous_text/future_textare up to 100 characters of context on either side, for continuity across calls; not every model honours them.pronunciation_dictionary_locatorstakes a JSON array of{"pronunciation_dictionary_id": ..., "version_id": ...}objects, up to three - the escape hatch for names your characters keep mangling.
Wiring the outputs
audio goes to Save Audio, Audio Normalize, or Audio Concat if you're building a longer piece. timestamps_json is the character-level alignment - hand it straight to Subtitle Export (SRT/VTT), which is the intended path and turns a generated conversation into a caption track. voice_segments_json is the per-voice breakdown; nothing in the pack consumes it, so it's for you to parse if you want to drive scene cuts from the speaker changes.
Install
Search ElevenLabs Pro in ComfyUI Manager, or do it by hand:
cd ComfyUI/custom_nodes
git clone https://github.com/IxMxAMAR/ComfyUI-ElevenLabs-Pro.git
pip install -r ComfyUI-ElevenLabs-Pro/requirements.txt
The dependency list is genuinely small - requests and soundfile. No weights, no torch upgrade, no 8GB download. Restart ComfyUI and the nodes appear under ElevenLabs Pro/.
For the key, set ELEVENLABS_API_KEY in your environment or paste it into the node's api_key field (password-masked in the UI), or wire in the ElevenLabs Pro - API Key node once for the whole workflow. Keys are scrubbed out of any error the node raises, which matters more than it sounds - a requests connection error is exactly the kind of trace that used to leak a key into your console and your saved workflow PNG.
Common issues
"At least one text + voice_id pair is required" - you filled text1 but left voice_id1 empty. The pair has to be complete. Voice IDs come from Voice Selector, Fetch Voices, or Get Voice By Name in the same pack.
Nothing runs at all. This node isn't marked as an output node, so ComfyUI only executes it if something downstream consumes an output. A dangling audio output means the branch never fires - no audio, no error, just a suspiciously fast queue. Wire it into Save Audio or Subtitle Export and it'll run.
Credits disappearing faster than the audio suggests. Every paid node in this pack caches by input hash, so re-queuing an unchanged graph does not re-bill you. Change one character of speaker 3's line, or a seed, and it's a fresh paid call.
Inputs (32)
| Name | Type | Default | Description |
|---|---|---|---|
| api_key | STRING | — | |
| text1 | STRING | Speaker 1 text. | |
| voice_id1 | STRING | Speaker 1 voice ID. | |
| model | COMBO | eleven_v3 | 2 options: eleven_v3, eleven_v4 |
| text2opt | STRING | — | |
| voice_id2opt | STRING | — | |
| text3opt | STRING | — | |
| voice_id3opt | STRING | — | |
| text4opt | STRING | — | |
| voice_id4opt | STRING | — | |
| text5opt | STRING | — | |
| voice_id5opt | STRING | — | |
| text6opt | STRING | — | |
| voice_id6opt | STRING | — | |
| text7opt | STRING | — | |
| voice_id7opt | STRING | — | |
| text8opt | STRING | — | |
| voice_id8opt | STRING | — | |
| text9opt | STRING | — | |
| voice_id9opt | STRING | — | |
| text10opt | STRING | — | |
| voice_id10opt | STRING | — | |
| stabilityopt | FLOAT | 0.500–1 | Voice stability. Lower = more expressive/emotional, Higher = more consistent/monotone. Creative(<0.5), Natural(0.5), Robust(>0.5). |
| apply_text_normalizationopt | COMBO | off | eleven_v3 requires 'off' — keeping default. |
| languageopt | COMBO | Auto Detect | 33 options: Auto Detect, English (en), Arabic (ar), Bulgarian (bg), Chinese (zh), Croatian (hr), +27 |
| output_formatopt | COMBO | mp3_44100_192 | Audio output format. mp3_44100_192 and opus require Creator tier+. |
| seedopt | INT | 00–4294967295 | Seed for reproducibility. 0 = random. Determinism not guaranteed. |
| enable_loggingopt | BOOLEAN | true | If False, requests zero-retention mode (audio + text not stored by ElevenLabs). Required for HIPAA / privacy-sensitive content. |
| previous_textopt | STRING | Context only — up to 100 characters that come right BEFORE this dialogue, for continuity. Not supported by every model. | |
| future_textopt | STRING | Context only — up to 100 characters that come right AFTER this dialogue, for continuity. Not supported by every model. | |
| use_pvc_as_ivcopt | BOOLEAN | false | Use the IVC version of a Professional Voice Clone. |
| pronunciation_dictionary_locatorsopt | STRING | JSON array of {"pronunciation_dictionary_id": ..., "version_id": ...} objects (up to 3). |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| audio | AUDIO | — |
| timestamps_json | STRING | — |
| voice_segments_json | STRING | — |