Character Speech (GPT-SoVITS)
One Node, a Whole Script, and the Voice You Actually Trained
- audio
- info
The corner of TTS nobody wired into ComfyUI
Local TTS settled on three names: Kokoro for cheap and fast, F5-TTS for latency, Chatterbox for the best zero-shot clone - all three borrow a voice from a few seconds of audio. GPT-SoVITS is different: you fine-tune it on a few minutes of a character, and afterwards the model isn't imitating the voice, it is that voice. "I've found Sovits to be the best overall quality of local TTS models so far" is the community verdict, and the usual workflow chains a finetune into RVC for polish.
So why isn't it everywhere? The official repo is a self-contained WebUI with its own Python environment; running it beside ComfyUI means two installs fighting over one GPU, and "I might try GPT-SoVITS next but it looks a bit complex." This pack removes both: it vendors a trimmed copy of the inference code and runs it on ComfyUI's own PyTorch. The README is Chinese; the node is English.
How it works
A GPT-SoVITS character is two weight files plus a reference clip: the GPT .ckpt turns phonemes into semantic tokens, the SoVITS .pth turns those into a waveform, and the reference supplies the timbre and prosody the model steers toward. The node parses your script into lines - each with its own voice, language and seed - decodes lines sharing a voice in batches, then stitches one stream with pauses inserted.
Two things matter before you hit Queue. Sentences are cached by content, so editing one line and re-running regenerates only that line. And the reference's tone bleeds into everything - a question as the main reference makes every line end on a rise - so the pack defaults to calm 4–8 s statements.
The inputs that matter
character lists folders under your gpt_sovits models directory; a folder needs at least one .ckpt and one .pth to show up, and subfolders like 日配 / 中配 become separate entries. (no characters found) means folder layout, not a bug.
text is the script, with its own small markup: {happy} swaps to that emotion's reference audio, {main} goes back, [name] switches speaker, [pause:1s] inserts a gap, [take:2] re-rolls just the section after it. A typo'd emotion or character name won't crash the run - it warns and carries on.
seed and speed are what you'll touch on the node face. Behind the advanced section: language (leave it on auto - it decides per sentence), reference_audio / reference_text to override the clip, gpt_weights / sovits_weights when a character has several checkpoints, pause_seconds, the sampling knobs, and batch_size. Two earn their keep: cross_lingual on adjust tightens sampling when a sentence's language differs from its reference - a Japanese model reading Chinese - and volume on normalize levels each sentence to about −20 dBFS so a new seed doesn't suddenly shout.
Outputs are audio (into Save Audio or Preview Audio) and info - the sentence report plus warnings, also readable in the node's /history.
Installing it
ComfyUI Manager, search Anomalous TTS, install, restart. Or by hand:
cd ComfyUI/custom_nodes
git clone https://github.com/DemonGatanjieu/Anomalous_TTS
python_embeded/python.exe -m pip install -r requirements.txt # portable; else your ComfyUI python
Lighter than you'd expect for TTS: einops, soundfile, soxr, transformers, huggingface_hub, tqdm, plus per-language front-ends - pyopenjtalk-plus; pypinyin/jieba/opencc; g2p_en/wordsegment/nltk. No torch, no ffmpeg. onnxruntime is deliberately absent: install it beside onnxruntime-gpu and other nodes break, so Chinese polyphones fall back to pypinyin.
Base models download on first use into models/gpt_sovits/pretrained/: HuBERT (190 MB, always needed), plus Chinese RoBERTa (650 MB) and G2PW (600 MB) for Chinese and an ERes2NetV2 encoder (100 MB) for v2Pro - 1.6 GB worst case. Already have a GPT-SoVITS install? Point pretrained at it in ComfyUI/user/anomalous_tts.json and download nothing.
Where people get burned
An empty or stale character list. Add extra model roots under an anomalous_tts: section in extra_model_paths.yaml, or in ComfyUI/user/anomalous_tts.json. The node's buttons refresh the list, so no restart needed.
A missing language package. The node shows a ⚠ line when a run needs a front-end you haven't installed; click it for the pip command for your Python. It's the same class of failure that made older GPT-SoVITS wrappers miserable (No module named 'jieba_fast'), and the general hazard of audio in ComfyUI, where nltk and pyopenjtalk get shared with other TTS packs.
VRAM. batch_size defaults to 8; lower it when a long script OOMs. The pack hooks ComfyUI's memory freeing, so an image model needing room evicts these weights first - or POST /free with {"unload_models": true} from the API.
Wrong model version. v1, v2, v2Pro and v2ProPlus load; v3 and v4 don't. Trained on the newest GPT-SoVITS? That checkpoint won't work here.
Accents on cross-lingual lines. A Japanese-trained model will mangle Chinese ü sounds and tones, and no setting fixes that. The pack's answer is the replace map in the character settings - respell the hard word - plus cross_lingual left on.
Last thing: the pack ships no voices, and while its code is MIT, the licence on the fine-tunes themselves is murky. Use voices you have the rights to.
Inputs (18)
| Name | Type | Default | Description |
|---|---|---|---|
| character | COMBO | A character in the gpt_sovits models folder | |
| text | STRING | The script. {happy} switches the emotion, {main} switches back; [name] switches the speaker; [pause:1s] inserts a pause. | |
| seedopt | INT | 00–18446744073709550000 | — |
| speedopt | FLOAT | 1.000.5–2 | Speaking speed. Left at its default, the presets decide (Anomalous: GPT-SoVITS Settings, and each character's generation settings) |
| languageopt | COMBO | auto | auto: decided for each sentence from its text |
| reference_audioopt | STRING | Main reference clip: a path inside the character's folder, e.g. refs/xxx.wav. Empty: the one in the character's settings, or a 3–10 s clip with a transcript, picked automatically | |
| reference_textopt | STRING | What the main reference clip says. Empty: looked up automatically | |
| gpt_weightsopt | COMBO | auto: the one in the character's settings, else the most trained | |
| sovits_weightsopt | COMBO | auto: the one in the character's settings, else the most trained | |
| pause_secondsopt | FLOAT | 0.300–5 | Pause between sentences |
| top_kopt | INT | 151–100 | Left at its default, the presets decide (Anomalous: GPT-SoVITS Settings, and each character's generation settings) |
| top_popt | FLOAT | 1.000.05–1 | Left at its default, the presets decide (Anomalous: GPT-SoVITS Settings, and each character's generation settings) |
| temperatureopt | FLOAT | 1.000.05–2 | Left at its default, the presets decide (Anomalous: GPT-SoVITS Settings, and each character's generation settings) |
| repetition_penaltyopt | FLOAT | 1.351–2 | Left at its default, the presets decide (Anomalous: GPT-SoVITS Settings, and each character's generation settings) |
| batch_sizeopt | INT | 81–64 | Sentences generated at once. Lower it when VRAM runs out |
| cross_lingualopt | COMBO | adjust | adjust: a sentence in another language than the reference clip (a Japanese character speaking Chinese, say) uses the preset set for that pair of languages, "stable" unless changed in Anomalous. off: every sentence as if in the reference's language |
| volumeopt | COMBO | normalize | normalize: each sentence's voice is brought to about -20 dBFS (peaks under -1 dBFS), so a new seed or sentence does not jump in loudness. off: the loudness the model gives |
| presetopt | STRING | Empty: the presets decide (Anomalous: GPT-SoVITS Settings, and each character's generation settings). A preset's name (stable / standard / lively, or custom_… made in Anomalous): every sentence uses its numbers; speed is not part of a preset |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| audio | AUDIO | — |
| info | STRING | — |