Nodes/ComfyUI-GGUF-Loader/OmniVoice Longform TTS ⚡
ComfyUI Node

OmniVoice Longform TTS ⚡

Why your ten-minute narration doesn't turn into five narrators

By ChrisColeTech·Created 2 months ago·Updated 3 days ago· 26
OmniVoice Longform TTS ⚡
  • omnivoice_model
  • ref_audio
  • whisper_model
  • audio
◄textHello! This is a test of OmniVoice text to speech synthesis.►
◄ref_text►
◄steps32►
◄guidance_scale2.0►
◄t_shift0.10►
◄speed1.0►
◄duration0.0►
◄seed0►
◄position_temperature5.0►
◄class_temperature0.0►
◄layer_penalty_factor5.0►
◄denoisetrue►
◄preprocess_prompttrue►
◄postprocess_outputtrue►
◄instruct►
◄words_per_chunk100►

If you've tried to get more than a paragraph out of a local TTS model, you know the failure mode. You feed it 800 words, it either truncates, or it generates a single unbroken take that wanders off the script around word 300, or you split the text yourself into five clips and discover that clip one and clip five are somehow different people. That last one is the real problem, and it's the problem this node exists to solve.

OmniVoice Longform TTS is the same engine as the short-form cloning node, with two additions: it chunks your text at sentence boundaries and pins one voice across every chunk. Voice consistency across a long read is the thing every hobbyist pipeline gets wrong.

How the chunking works

The text gets split before anything is generated - one diffusion pass per chunk, then everything gets concatenated into a single mono AUDIO at 24 kHz. words_per_chunk = 0 turns chunking off and generates the whole text in one pass.

For space-delimited languages it splits by words, words_per_chunk at a time (default 100), then walks backwards from that boundary to the last sentence terminator it can find. So a 100-word limit doesn't actually cut mid-sentence if there's a full stop available. For CJK, Thai, Lao, Myanmar and Khmer it flips to character-aware chunking instead - the ratio of those codepoints is tested first, and past about 30% it chunks by characters with the same boundary backup. For those languages words_per_chunk = 100 means a hundred characters, a much shorter chunk than it sounds.

The trick that keeps one voice

Here's the part that's actually clever, and it depends on whether you connected a reference clip.

With ref_audio connected: every chunk is generated against that same reference audio and transcript. Same voice, start to finish, and that's the mode to use when you have a specific voice to hit.

Without ref_audio: the node generates the first chunk with no reference at all, then takes the first 25 seconds of that generated audio plus the first chunk's own text and uses that as the reference for every remaining chunk. Which is a neat solution and also a warning. Your opening chunk is unconditional - the model just picks a voice - and everything after it inherits whatever that voice was, artifact and all. Write a clean, representative opening sentence. If you want a specific voice, supply ref_audio rather than letting the model improvise one.

With a reference connected, transcription happens once before the loop, not per chunk: connect a WHISPER_ASR pipeline to whisper_model, drop a Whisper folder into models/audio_encoders/, or leave ref_text empty and let OmniVoice's internal ASR do it. With no reference at all, the first chunk's own text is the transcript for every later chunk, so nothing needs transcribing.

The inputs to touch

text and words_per_chunk are the two that define the job. Then ref_audio (optional here, required in the short-form node - this is the main shape difference between them) and ref_text, whose tooltip says it all: reference transcript, empty falls back to connected/local/internal Whisper.

steps, guidance_scale, speed, seed, instruct, denoise, preprocess_prompt, postprocess_output and the temperature/penalty dials are identical to the short-form node and behave the same way. Two things specific to long-form:

seed 0 means random, and that random seed is drawn once per run and applied before generation begins. So a given seed plus a given text gives you a reproducible chunk sequence - which matters far more here than in short-form, because a seed change reshuffles ten takes, not one.

duration is applied per chunk. It defaults to 0, which is what you want. Set it to 8.0 and you aren't asking for an eight-second clip; you're asking each chunk to be eight seconds, which mangles everything past the first. Leave it at zero and use speed if the read drags.

Output is a single audio socket - the chunks are concatenated for you. Wire it to SaveAudio, or into CreateVideo if you're narrating a clip.

Installing

cd ComfyUI/custom_nodes
git clone https://github.com/ChrisColeTech/ComfyUI-GGUF-Loader
cd ComfyUI-GGUF-Loader
python -m pip install omnivoice==0.2.1 "transformers>=5.3.0" soxr

ComfyUI Manager will find the pack as ComfyUI-GGUF-Loader (publisher chriscoletech) - it's a big multi-purpose pack, a fork of city96's ComfyUI-GGUF, and its requirements.txt pulls in the optional dependency set for everything it does. Installing just omnivoice, transformers and soxr is enough for the three nodes under 🤖 CCTech/OmniVoice. The model downloads itself into models/omnivoice/ the first time the loader runs, and the OmniVoice Models Loader sits upstream of this node.

Where it goes wrong

The node registers even without the omnivoice package installed, so the menu looks healthy and the failure shows up only at queue time. If the import died on HiggsAudioV2Tokenizer, the message tells you to get onto transformers>=5.3.0 - and that pin is the usual collateral-damage suspect if other packs break afterwards.

Runtime is the other reality check. Cost is multiplicative: a 1,200-word script at 100 words per chunk is twelve diffusion passes at 32 steps each, with a progress bar advancing per chunk and ComfyUI's interrupt honoured between them. Raise words_per_chunk before you raise steps.

And if a chunk audibly cuts in the middle of a clause, that's the sentence-boundary backup finding no terminator in range - raise words_per_chunk, or split the text yourself with words_per_chunk = 0. Feeding it text that already ends its lines on punctuation is the cheapest fix and the one that keeps the voice most stable.

Category🤖 CCTech/OmniVoice

Inputs (19)

NameTypeDefaultDescription
omnivoice_modelOMNIVOICE_MODEL—
textSTRINGHello! This is a test of OmniVoice text to speech synthesis.—
ref_textSTRINGReference transcript. Empty uses connected/local/internal Whisper.
stepsINT324–64—
guidance_scaleFLOAT2.00–10—
t_shiftFLOAT0.100–1—
speedFLOAT1.00.5–2—
durationFLOAT0.00–60—
seedINT00–2147483647Random seed. 0 = random.
position_temperatureFLOAT5.00–20—
class_temperatureFLOAT0.00–5—
layer_penalty_factorFLOAT5.00–20—
denoiseBOOLEANtrue—
preprocess_promptBOOLEANtrue—
postprocess_outputBOOLEANtrue—
instructSTRINGA model-supported comma-separated dialect/style instruction.
words_per_chunkINT1000–500—
ref_audiooptAUDIO—
whisper_modeloptWHISPER_ASR—

Outputs (1)

NameTypeDescription
audioAUDIO—