From 0a0d023517911712d61f5e825ab1d7e3afa728d0 Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Mon, 26 Jan 2026 19:41:44 -0500 Subject: [PATCH] Fix sentence splitter to split on every sentence boundary Previous version accumulated sentences until 500 chars, defeating the purpose of streaming. Now splits on every sentence, only combining very short sentences (<50 chars) with the next. --- speech.py | 57 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/speech.py b/speech.py index 0274ea2..bfbd5e8 100755 --- a/speech.py +++ b/speech.py @@ -517,38 +517,61 @@ def preprocess(raw_input): return raw_input def simple_sentence_split(text: str, max_length: int = 500) -> list[str]: - """Split text into sentences for better TTS processing. + """Split text into sentences for streaming TTS. - Simple sentence splitter that breaks on sentence boundaries (.!?) - and ensures no sentence exceeds max_length characters. + Splits on sentence boundaries (.!?) for immediate streaming. + Only combines very short sentences (< 50 chars) with the next one. + Long sentences exceeding max_length are split at word boundaries. """ - # Split on sentence boundaries - sentences = re.split(r'([.!?]+[\s\n]+)', text) + # Split on sentence boundaries, keeping the punctuation + parts = re.split(r'([.!?]+\s*)', text) - # Recombine sentences with their punctuation result = [] current = "" - for i in range(0, len(sentences), 2): - sentence = sentences[i] - punct = sentences[i+1] if i+1 < len(sentences) else "" + for i in range(0, len(parts), 2): + sentence = parts[i].strip() + punct = parts[i+1] if i+1 < len(parts) else "" - # If adding this sentence would exceed max_length, save current and start new - if current and len(current) + len(sentence) + len(punct) > max_length: + if not sentence: + continue + + full_sentence = sentence + punct + + # If sentence is very short, combine with current buffer + if len(full_sentence) < 50 and current: + current += " " + full_sentence + elif current: + # Save buffered content and start fresh result.append(current.strip()) - current = sentence + punct + current = full_sentence else: - current += sentence + punct + current = full_sentence # Add remaining text if current.strip(): result.append(current.strip()) - # If we got nothing (no sentence boundaries), split on max_length - if not result and text: - result = [text[i:i+max_length] for i in range(0, len(text), max_length)] + # Split any sentences that exceed max_length at word boundaries + final_result = [] + for sentence in result: + if len(sentence) <= max_length: + final_result.append(sentence) + else: + # Split at word boundaries + words = sentence.split() + chunk = "" + for word in words: + if len(chunk) + len(word) + 1 > max_length: + if chunk: + final_result.append(chunk.strip()) + chunk = word + else: + chunk += " " + word if chunk else word + if chunk: + final_result.append(chunk.strip()) - return result if result else [text] + return final_result if final_result else [text] # Auto-detect which model a voice belongs to (uses cached mapping) def detect_model_from_voice(voice: str) -> str: