- Expanded package inclusion in `pyproject.toml` to support new modules. - Introduced new `adapters` and `protocol` packages for better organization. - Added backend adapter implementations for control plane integration. - Updated main application imports to reflect new package structure. - Removed deprecated core components and adjusted documentation accordingly. - Enhanced architecture documentation to clarify the new runtime and integration layers.
87 lines
2.6 KiB
Python
87 lines
2.6 KiB
Python
"""Shared text chunking helpers for streaming TTS."""
|
|
|
|
from typing import Optional
|
|
|
|
|
|
def is_non_sentence_period(text: str, idx: int) -> bool:
|
|
"""Check whether '.' should NOT be treated as a sentence delimiter."""
|
|
if idx < 0 or idx >= len(text) or text[idx] != ".":
|
|
return False
|
|
|
|
# Decimal/version segment: 1.2, v1.2.3
|
|
if idx > 0 and idx < len(text) - 1 and text[idx - 1].isdigit() and text[idx + 1].isdigit():
|
|
return True
|
|
|
|
# Number abbreviations: No.1 / No. 1
|
|
left_start = idx - 1
|
|
while left_start >= 0 and text[left_start].isalpha():
|
|
left_start -= 1
|
|
left_token = text[left_start + 1:idx].lower()
|
|
if left_token == "no":
|
|
j = idx + 1
|
|
while j < len(text) and text[j].isspace():
|
|
j += 1
|
|
if j < len(text) and text[j].isdigit():
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def has_spoken_content(text: str) -> bool:
|
|
"""Check whether text contains pronounceable content (not punctuation-only)."""
|
|
return any(char.isalnum() for char in text)
|
|
|
|
|
|
def extract_tts_sentence(
|
|
text_buffer: str,
|
|
*,
|
|
end_chars: frozenset[str],
|
|
trailing_chars: frozenset[str],
|
|
closers: frozenset[str],
|
|
min_split_spoken_chars: int = 0,
|
|
hold_trailing_at_buffer_end: bool = False,
|
|
force: bool = False,
|
|
) -> Optional[tuple[str, str]]:
|
|
"""Extract one TTS sentence from text buffer."""
|
|
if not text_buffer:
|
|
return None
|
|
|
|
search_start = 0
|
|
while True:
|
|
split_idx = -1
|
|
for idx in range(search_start, len(text_buffer)):
|
|
char = text_buffer[idx]
|
|
if char == "." and is_non_sentence_period(text_buffer, idx):
|
|
continue
|
|
if char in end_chars:
|
|
split_idx = idx
|
|
break
|
|
|
|
if split_idx == -1:
|
|
return None
|
|
|
|
end_idx = split_idx + 1
|
|
while end_idx < len(text_buffer) and text_buffer[end_idx] in trailing_chars:
|
|
end_idx += 1
|
|
|
|
while end_idx < len(text_buffer) and text_buffer[end_idx] in closers:
|
|
end_idx += 1
|
|
|
|
if hold_trailing_at_buffer_end and not force and end_idx >= len(text_buffer):
|
|
return None
|
|
|
|
sentence = text_buffer[:end_idx].strip()
|
|
spoken_chars = sum(1 for ch in sentence if ch.isalnum())
|
|
|
|
if (
|
|
not force
|
|
and min_split_spoken_chars > 0
|
|
and 0 < spoken_chars < min_split_spoken_chars
|
|
and end_idx < len(text_buffer)
|
|
):
|
|
search_start = end_idx
|
|
continue
|
|
|
|
remainder = text_buffer[end_idx:]
|
|
return sentence, remainder
|