arkennemasis fal · ElevenLabs Text to Dialogue Eleven v3 · $0.10/1k chars
Two voices, one take, no editing job afterwards
- audio
- audio_path
- seed
- info
The normal way to make a two-person conversation with TTS is ugly: generate line A, generate line B, generate line A's reply, then stitch, level and crossfade, and hope the pacing doesn't sound like two robots alternating in a vacuum. Text to Dialogue skips all of it. You describe the exchange as a list, and the model performs it as one continuous scene - interruptions, reactions, the little breath before someone answers.
This is the node for podcast-style content, a two-hander advert, or that dialogue you were going to write around because generating it properly was too much work. If you've ever cut a caption track for a conversation, you know why the pacing matters.
How it works
It's an ElevenLabs endpoint (Eleven v3) hosted on fal and wrapped by a generated node. Your JSON goes up, one billed call comes back with a single render of the whole exchange, and the audio lands in output/fal/fal-ai_elevenlabs_text-to-dialogue_eleven-v3/, logged like every other fal run in output/fal/_requests.jsonl. No local model, no VRAM, no per-line stitching on your side.
The input that matters
inputs is required and it's a JSON list. Each item is an object with a text and a voice. The node ships with a working example as its default, which is worth reading before you write your own, because it shows the two things the model responds to:
[
{"text": "[applause] Thank you all for coming tonight!", "voice": "Aria"},
{"text": "[gulps] ... [strong canadian accent] [excited] Hello everyone!", "voice": "Charlotte"}
]
Audio tags in square brackets, stacked, inline. That's where the performance comes from - not from the words alone. The voices are plain name strings, so you're typing them, not picking from a dropdown.
The rest of the widgets
stability- defaults to-1(let the model decide), and the tooltip carries a detail you can't skip: it must be0.0,0.5or1.0, or it gets rounded to the nearest of those. Lower introduces broader emotional range, higher trends monotonous. For dialogue you generally want the range.use_speaker_boost- a three-state setting:(not set),true,false. Boosts similarity to the original speaker at the cost of a little more latency.pronunciation_dictionary_locators- JSON, up to three locators per request, applied in order. For a recurring name or brand across many episodes, this is how you stop fixing the same word every time.seed- reproducibility,-1for a fresh one.language_code- enforced, not hinted. The tooltip is explicit that an unsupported code returns an error rather than quietly falling back.
Outputs: audio, audio_path, seed, info. Note there's no timestamps output here, so if you're captioning an exchange you'll want a transcription node on the rendered audio rather than an alignment pass against your script.
Money
$0.10 per 1,000 characters of input. Characters, not minutes - so the cost tracks how much dialogue you wrote, and a tight 400-word two-person scene (roughly 2,200 characters) is about 22 cents. The max_cost_usd cap at its $20 default is about 200,000 characters, i.e. not your problem. What is worth doing is spending two hundred of those characters iterating on tags instead of rewriting the whole exchange each time.
Install
Manager → search comfyui-arkennemasis, or:
cd ComfyUI/custom_nodes
git clone https://github.com/Hishamahmer/comfyui-arkennemasis
Restart, then FAL_KEY=your-key-from-fal.ai/dashboard/keys in the .env in the folder holding run_nvidia_gpu.bat. Nothing to download; the fal nodes use the standard library, and httpx in the requirements is for the Codex nodes only.
Common issues & troubleshooting
"Invalid JSON" or an input error before anything runs. inputs is parsed, so a trailing comma or a smart quote pasted in from a document will stop it. This failure is free.
Both parts sound like the same person. Check the voice values in the list - a typo silently falls back rather than erroring, so ask the model once with a name you know works, then vary.
Stability set to 0.3 and it behaved like 0.5. Documented: values snap to 0.0, 0.5 or 1.0.
An unsupported language_code errored the run. Intentional. Clear the field to let the model infer instead.
The take was perfect and you can't reproduce it. Same seed, different take is normal for this class of model - which is exactly why reuse_identical_run exists, and why you should leave it on once you have an audio file you like. The next identical run returns the paid file instead of rolling again.
Inputs (10)
| Name | Type | Default | Description |
|---|---|---|---|
| inputs | STRING | [{"text": "[applause] Thank you all for coming tonight! Today we have a very special guest with us.", "voice": "Aria"}, {"text": "[gulps] ... [strong canadian accent] [excited] Hello everyone! Thank you all for having me tonight on this special day.", "voice": "Charlotte"}] | A list of dialogue inputs, each containing text and a voice ID which will be converted into speech. JSON. Required. |
| stability | FLOAT | -1.00-1–1 | Determines how stable the voice is and the randomness between each generation. Lower values introduce broader emotional range for the voice. Higher values can result in a monotonous voice with limited emotion. Must be one of 0.0, 0.5, 1.0, else it will be rounded to the nearest value. -1 leaves it to the model. |
| use_speaker_boost | COMBO | (not set) | This setting boosts the similarity to the original speaker. Using this setting requires a slightly higher computational load, which in turn increases latency. |
| pronunciation_dictionary_locators | STRING | A list of pronunciation dictionary locators (id, version_id) to be applied to the text. They will be applied in order. You may have up to 3 locators per request JSON. Empty leaves it out. | |
| seed | INT | -1-1–2147483647 | Random seed for reproducibility. -1 leaves it to the model. |
| language_code | STRING | Language code (ISO 639-1) used to enforce a language for the model. An error will be returned if language code is not supported by the model. Empty leaves it out. | |
| max_cost_usdopt | FLOAT | 20.00–100000 | Safety cap. If the estimated cost of this run is above this many US dollars the node stops BEFORE sending anything to fal. 0 = no cap. |
| reuse_identical_runopt | BOOLEAN | true | If these exact inputs (the settings AND the same pictures/clips/audio) were already paid for and the files are still in output/fal, return them instead of paying again - also after a ComfyUI restart. Turn off for a fresh take with the same settings. |
| extra_jsonopt | STRING | Advanced: a JSON object merged into the request last, for any fal field this node has no box for. Example: {"seed": 7}. Leave empty normally. | |
| max_concurrentopt | INT | 11–32 | How many paid fal calls may run at the same time in one run, across every fal node. 1 (default) = one after another. This node starts its paid part only while fewer than this many fal calls are running. Once any fal node fails, the ones still waiting are not started, so nothing more is billed. |
Outputs (4)
| Name | Type | Description |
|---|---|---|
| audio | AUDIO | — |
| audio_path | STRING | — |
| seed | INT | — |
| info | STRING | JSON: request id, cost estimate, saved files and fal's full answer. |