speaker_helper.chunking module

Long-text chunking: cut a text into synthesis-sized pieces, LLM-assisted.

Module summary

Every neural TTS engine has a comfort zone, and a long text read in one call leaves it: Chatterbox audibly degrades past roughly half a minute of speech, and autoregressive models drift. The fix is old and simple — synthesise sentence groups and concatenate — and the only interesting question is where to cut. A cut between two sentences of the same thought is audible; a cut at a paragraph or topic boundary is not.

That placement is what the local LLM contributes here, under one invariant that makes the feature safe: the model never touches the text. Sentences are split locally (split_sentences(), deterministic); the LLM is only asked how many consecutive sentences go in each chunk, its answer is validated structurally (positive integers, summing to the sentence count), and anything else — a chatty answer, a wrong sum, no LLM at all — falls back to a deterministic greedy packer. Chunks always concatenate back to exactly the sentences the splitter produced.

The model is resolved through best-engine-ai-helper (the suite rule) and served by Ollama at temperature 0, like the pronunciation lexicon’s respelling tier (speaker_helper.lexicon).

Usage example

>>> from speaker_helper.chunking import greedy_group_sizes
>>> greedy_group_sizes(["aa.", "bb.", "cc."], max_chars=7)
[2, 1]

Author

Warith HARCHAOUI — https://linkedin.com/in/warith-harchaoui

speaker_helper.chunking.DEFAULT_MAX_CHARS: int = 500

Default per-chunk budget, in characters. ~500 characters is ~30 seconds of speech — inside every backend’s comfort zone (Chatterbox degrades past that; Kyutai and Kokoro are comfortable well below it).

speaker_helper.chunking.chunk_long_text(text, language, settings)[source]

Cut text into synthesis-sized chunks at the best boundaries known.

Short texts pass through untouched (one chunk); long ones are split into sentence groups — grouped by the local LLM when available, greedily otherwise. Either way the chunks are exactly the local splitter’s sentences, in order: the LLM chooses boundaries, never words.

Parameters:
  • text (str) – The text to synthesise (already respelled, when a lexicon applies).

  • language (str) – Language of the text — context for the LLM prompt only.

  • settings (Settings) – Its chunking mapping drives this: enabled (default True), llm (default True), max_chars (default DEFAULT_MAX_CHARS).

Returns:

Chunks in reading order; [text] when no split is needed (the common case, costing one length check).

Return type:

list of str

speaker_helper.chunking.greedy_group_sizes(sentences, max_chars)[source]

Pack consecutive sentences into groups under max_chars each.

The deterministic fallback: no model, no surprise. A single sentence longer than the budget forms its own group — splitting inside a sentence is never an option.

Parameters:
  • sentences (list of str) – The sentences, in order.

  • max_chars (int) – Character budget per group.

Returns:

Group sizes, positive, summing to len(sentences).

Return type:

list of int

Examples

>>> greedy_group_sizes(["aa.", "bb.", "cc."], max_chars=7)
[2, 1]