EPUB Chapter Splitter
Feeding a Novel to Your TTS One Bite-Size Chunk at a Time
- epub_data
- text_parts
- filenames
- filename
The name is the plan: this is the node that turns one chapter of your EPUB into a list of text chunks your TTS can actually chew on. It's the workhorse of the comfyui_epub_tts pack, and the only node in the set with any search traffic to speak of. If you're converting a book to an audiobook in ComfyUI, you'll spend most of your time staring at this one.
Why you need it
TTS nodes choke on a 10,000-word chapter. Most local TTS models are trained on speech-length utterances, so feed them a wall of text and you get dropped words, runaway pacing, or an outright crash. This node fixes that by splitting each chapter at sentence boundaries into chunks of roughly chunk_size characters - 1,500 by default, which is a comfortable mouthful for most engines. Each chunk comes out as a separate string in text_parts, ready to feed one-at-a-time to your TTS node.
It's the middle of a three-node pipeline: LoadEPUB reads the book and hands you a SPINEWALKER object, this node carves a chapter into pieces, and then an audio path (TTS → AddSilenceToAudioBatch → CombineAudioToSingle) turns those pieces into a single file. The pack stops at text and audio plumbing - the actual voice is whatever TTS node you already run, which is the point. It's model-agnostic.
How the chunking works
The mechanism is refreshingly simple and worth knowing because it explains two settings. The node takes the chapter text, and while it's longer than chunk_size, it hunts backward from that window for the last ., !, or ? and splits there, punctuation included. If a chapter has a 2,000-character sentence with no punctuation break, it just hard-cuts at 1,500 - no clever NLP, just a fallback. That's the whole algorithm.
Three inputs matter:
- index - which chapter in the spine to split. Default 0. If it's past the end of the book,
stop_at_end(default on) raises anIndexErrorso a loop doesn't silently wrap; flip it off and it wraps modulo instead. - chunk_size - target chunk length in characters (100–10,000). Bigger = fewer, longer TTS clips. If your engine handles long input fine, push it up; if you hear pacing drift mid-sentence, drop it.
- max_char_per_part - a hard cap per chunk, 0 = no cap. Set it when a TTS model has a token limit smaller than your chunks.
The outputs are what you wire onward: text_parts (list of strings) goes to your TTS, and the node also hands you filenames (a list like audio/Author_-_Title_ch001_part_01) and a single base filename - handy if you're naming per-part audio files for a later combine. The index is the only input you'll be actively driving; everything else is set-and-forget.
Install and gotchas
Install via ComfyUI Manager (search comfyui_epub_tts), or manually:
cd ComfyUI/custom_nodes
git clone https://github.com/kallama/comfyui_epub_tts
Then restart ComfyUI. Manager pulls the three pure-Python deps (ebooklib, bs4, trafilatura) automatically - no model downloads, no GPU requirements, nothing heavy. The README is still cookiecutter boilerplate (the features list is a placeholder), so don't go looking for docs there; the source is small and readable if you hit a question.
The one failure mode you'll actually meet is the End of EPUB reached error, which is exactly the stop_at_end behavior - you told it to split chapter 40 of a 39-chapter book. Wire index from a counter node, or accept the wrap. And know that chunk boundaries are where long audiobook runs go wrong: people who let TTS grind through whole novels report the voice drifting or pacing changing between chunks, because context is lost at each split. It's not your fault and not really this node's - it's the nature of chunked TTS.
Inputs (6)
| Name | Type | Default | Description |
|---|---|---|---|
| epub_data | SPINEWALKER | — | |
| index | INT | 00–18446744073709550000 | — |
| chunk_size | INT | 1500100–10000 | — |
| total_chaptersopt | INT | 00–10000 | — |
| max_char_per_partopt | INT | 00–10000 | — |
| stop_at_endopt | BOOLEAN | true | — |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| text_parts | STRING | — |
| filenames | STRING | — |
| filename | STRING | — |