Gemini 2.5 Pro Text to Speech
Two characters arguing, straight out of a JSON field
- audio
- url
- all_urls_json
- task_id
- raw_json
- credits_consumed
- credits_left
Most text-to-speech in ComfyUI is one voice reading one block of text. This node is the other thing: a multi-speaker dialogue generator. You define a cast - each with a voice name, an accent, a speaking style, a pace, a one-line character brief - then hand it a script as a list of turns, and it returns a single AUDIO object with the whole scene performed in order.
That's genuinely useful for the thing people keep trying to do with video models: a two-hander scene with dialogue, where you need the acting to be reacting rather than two monologues stitched together. It also pairs naturally with the video side of this hobby - generate the audio, drop the file into an audio-driven video node, and you have a talking scene without a microphone.
How the node works
It's a KIE Market job: the node submits {"speakers": [...], "dialogue_turns": [...], ...} with the model google/gemini-2-5-pro-tts, polls the task until it completes, downloads the audio, and returns it as a native ComfyUI AUDIO. speakers, dialogue_turns and their JSON structure are text widgets, not structured inputs - so everything you know about JSON validity applies, and the pack's local preflight will reject malformed JSON before it spends anything.
The two big fields are the whole interface:
- speakers - a JSON list of cast entries. Each has a
speaker_idthat must be in theSpeaker Nformat, avoice_namefrom Google's 30-voice list (Fenrir,Puck,Zephyr,Kore,Aoede, and so on), plus descriptive strings foraudio_profile,accent,styleandpace. The descriptions are the interesting part - they're direction, and the model uses them. - dialogue_turns - a JSON list of
{"speaker_id": ..., "text": ...}in the order they should be spoken. Square-bracket cues like[shouting]or[whispers]inside the text act as per-line direction. That's the trick that makes it sound like acting instead of narration.
Optional: scene (where the conversation happens), sample_context (overall tone - "audiobook narration, gentle and inviting" style framing), temperature (default 1, 0–2, sampling warmth), timeout_seconds, and callback_url which you should leave empty.
Outputs: audio, url, all_urls_json, task_id, raw_json, credits_consumed, credits_left.
Wiring the output
audio is a normal ComfyUI AUDIO, so SaveAudio works, as do lip-sync and audio-driven video nodes. If your clip is going into a video workflow, remember the source-video audio gets replaced when you mix - connect dialogue explicitly rather than assuming it survives. And this node isn't a terminal node: if audio goes nowhere, the node never runs and never bills you.
Installing
cd ComfyUI/custom_nodes
git clone https://github.com/felipederosilva/ComfyUI-KIE-Nodes-Next
cd ComfyUI-KIE-Nodes-Next && pip install -r requirements.txt
# restart ComfyUI
Manager: search "KIE.ai Nodes Next". The whole pack's dependency list is requests, Pillow, numpy, PyYAML - no TTS model to download, no GPU voice cloning, nothing heavy. Set the key once at Settings → KIE.ai Nodes Next → Connection; it's validated before it replaces an existing key, stored outside workflow JSON, and there's no environment-variable option, by design.
Where it goes wrong
Editing those JSON blobs by hand is where 90% of the pain lives. A trailing comma, a speaker_id of "Narrator" instead of "Speaker 1", a voice_name that isn't on the list - the preflight catches the syntax, but the provider catches semantic mismatches, and a turn referencing a speaker that doesn't exist will fail there rather than locally.
Also worth internalising: the output is one continuous file. There's no per-turn export, so if you need an individual line isolated for a video take, you get to cut it yourself.
And the taste check - this is cloud TTS with per-character billing, and the local alternative is real: the open TTS stack in ComfyUI is now good enough for narration, and free per call. Where this node wins is the multi-speaker performance with direction, which the local options still don't do cleanly. Use it for scenes, not for reading a paragraph.
Inputs (7)
| Name | Type | Default | Description |
|---|---|---|---|
| speakers | STRING | [ { "speaker_id": "Speaker 1", "voice_name": "Fenrir", "audio_profile": "A stern and weary gatekeeper", "accent": "British (RP)", "style": "Deadpan", "pace": "Natural" }, { "speaker_id": "Speaker 2", "voice_name": "Puck", "audio_profile": "A determined and courageous traveler seeking answers.", "accent": "American (Gen)", "style": "Empathetic", "pace": "Staccato" } ] | List of speaker configurations |
| dialogue_turns | STRING | [ { "speaker_id": "Speaker 1", "text": "[shouting] Halt, traveler! The northern pass is sealed by order of the council." }, { "speaker_id": "Speaker 2", "text": "[determination] I carry a message for the elder. Step aside, or I will force my way through." }, { "speaker_id": "Speaker 1", "text": "[caution] No one passes. [pensive] The elder is... he's no longer receiving visitors." }, { "speaker_id": "Speaker 2", "text": "It's too late. [whispers] The shadow... it reached him first. [urgency] You need to leave. [shouting] Now." } ] | List of dialogue turns, output in sequential order |
| temperatureopt | FLOAT | 1.000–2 | Sampling temperature, e.g., 1 |
| sceneopt | STRING | A dark, crumbling dungeon... | Scene description, e.g., "A quiet, warm room with a fireplace crackling softly." |
| sample_contextopt | STRING | Fantasy RPG style... | Sample context/overall tone, e.g., "Audiobook style narration. Tone is gentle and inviting." |
| timeout_secondsopt | INT | 120030–7200 | — |
| callback_urlopt | STRING | Optional KIE callback URL. Leave empty for normal ComfyUI use. |
Outputs (7)
| Name | Type | Description |
|---|---|---|
| audio | AUDIO | — |
| url | STRING | — |
| all_urls_json | STRING | — |
| task_id | STRING | — |
| raw_json | STRING | — |
| credits_consumed | FLOAT | — |
| credits_left | FLOAT | — |