From 1f2142e7f610871a6bbe6498d0709d310fcbebb1 Mon Sep 17 00:00:00 2001 From: historia Date: Thu, 20 Aug 2026 17:05:21 -0400 Subject: feat: tool scripts for server.json and voices.json --- converter/chunking.py | 30 +++++++++--------------------- converter/config.py | 9 +++++++-- converter/tts.py | 12 ++++++------ 3 files changed, 22 insertions(+), 29 deletions(-) (limited to 'converter') diff --git a/converter/chunking.py b/converter/chunking.py index 1ce69a3..800b76a 100644 --- a/converter/chunking.py +++ b/converter/chunking.py @@ -2,28 +2,21 @@ import logging import re -from typing import List +from typing import List, Optional from . import config logger = logging.getLogger(__name__) -# Hard ceiling on words per request, regardless of the configured chunk -# size. Both TTS servers silently truncate audio when a single generation -# exceeds its cap (~2.5 min for the faster backend's static KV cache, -# ~11 min for the Gradio demo) without reporting any error, so larger -# requests are always split client-side. Keep a margin below ~300 words -# to survive slow narration on the faster backend. -MAX_REQUEST_WORDS = 250 - -def split_into_chunks(text: str, max_words: int = config.CHUNK_SIZE) -> List[str]: +def split_into_chunks(text: str, max_words: Optional[int] = None) -> List[str]: """Split text into chunks of at most ``max_words`` words. - ``max_words`` is clamped to ``MAX_REQUEST_WORDS``: requests - beyond that ceiling are silently truncated by the TTS servers (no - error is reported), so chunks larger than the ceiling are never - produced regardless of configuration. + ``max_words`` defaults to ``config.CHUNK_SIZE`` (read at call time). + There is no ceiling beyond that setting, but note that the TTS + servers silently truncate audio when a single generation runs too + long without reporting an error, so very large values are at your + own risk (see CHUNK_SIZE in converter/config.py). Splits on sentence boundaries. Sentences longer than the limit are split further at clause punctuation (which is kept attached for TTS @@ -33,13 +26,8 @@ def split_into_chunks(text: str, max_words: int = config.CHUNK_SIZE) -> List[str at word boundaries as a last resort: individual tokens stay intact, but whitespace between them is normalized. """ - if max_words > MAX_REQUEST_WORDS: - logger.warning( - "Requested chunk size of %d words exceeds the %d-word request ceiling; " - "larger requests are silently truncated by the TTS servers, so the " - "size is clamped to %d words (see MAX_REQUEST_WORDS in converter/chunking.py)", - max_words, MAX_REQUEST_WORDS, MAX_REQUEST_WORDS) - max_words = MAX_REQUEST_WORDS + if max_words is None: + max_words = config.CHUNK_SIZE if max_words < 1: max_words = 1 diff --git a/converter/config.py b/converter/config.py index 4cb5ab8..ed49f12 100644 --- a/converter/config.py +++ b/converter/config.py @@ -8,8 +8,13 @@ MAX_RETRIES = 3 # Attempts per chunk request HEARTBEAT_INTERVAL_SECONDS = 30 # Print "still working" in console logs every N seconds # Words per TTS generation request. -# Note that qwen-tts-demo does no chunking at all, but faster-qwen-tts and +# Note that qwen-tts-demo does no chunking at all, but faster-qwen3-tts and # audio.cpp may do chunking as well, so you may be needlessly double-chunking. +# This is the only size limit: there is no hard ceiling. However, servers +# silently truncate audio when a single generation runs too long (roughly +# ~2.5 min on the faster backend's static KV cache, ~11 min on the Gradio +# demo) without reporting an error, so raising this is at your own risk. +# The client-side truncation check still catches and retries gross cases. CHUNK_SIZE = 250 # Default TTS backend. @@ -54,5 +59,5 @@ FASTER_VOICE = "default" AUDIOCPP_API_URL = "http://127.0.0.1:8080" # audio.cpp audiocpp_server # Model ids in the audio.cpp server.json config. -AUDIOCPP_MODEL_ID = "qwen"https://github.com/0xShug0/audio.cpp +AUDIOCPP_MODEL_ID = "qwen" AUDIOCPP_CLONE_MODEL_ID = "qwen-clone" diff --git a/converter/tts.py b/converter/tts.py index 741d9d3..a1c0b09 100644 --- a/converter/tts.py +++ b/converter/tts.py @@ -28,7 +28,7 @@ from typing import Any, Dict, List, Optional, Tuple from . import config from .audio import concat_audio_files, probe_duration_ms -from .chunking import MAX_REQUEST_WORDS, split_into_chunks +from .chunking import split_into_chunks logger = logging.getLogger(__name__) @@ -440,14 +440,14 @@ class QwenTTSClient(_BaseTTSClient): """Generate one audio chunk; returns its path in the chunks folder. The text is split into sub-requests of at most - ``MAX_REQUEST_WORDS`` words each (the book-level chunker + ``config.CHUNK_SIZE`` words each (the book-level chunker normally guarantees this already; the split is defense in depth against pathological input such as a punctuation-free run of text), and the audio files returned for the sub-requests are concatenated into one chunk file. """ try: - sub_texts = split_into_chunks(text, max_words=MAX_REQUEST_WORDS) + sub_texts = split_into_chunks(text, max_words=config.CHUNK_SIZE) if not sub_texts: raise RuntimeError("No text to synthesize") @@ -673,7 +673,7 @@ class FasterTTSClient(_BaseTTSClient): def generate_chunk(self, text: str, chunk_num: int) -> Optional[str]: """Generate one audio chunk; returns its path in the chunks folder.""" try: - sub_chunks = split_into_chunks(text, max_words=MAX_REQUEST_WORDS) + sub_chunks = split_into_chunks(text, max_words=config.CHUNK_SIZE) if not sub_chunks: raise RuntimeError("No text to synthesize") @@ -914,13 +914,13 @@ class AudioCppTTSClient(_BaseTTSClient): """Generate one audio chunk; returns its path in the chunks folder. The text is split into sub-requests of at most - ``MAX_REQUEST_WORDS`` words each (defense in depth against + ``config.CHUNK_SIZE`` words each (defense in depth against pathological input, matching the Gradio client), each sub-request returns a complete WAV file, and the parts are concatenated into one chunk file. """ try: - sub_texts = split_into_chunks(text, max_words=MAX_REQUEST_WORDS) + sub_texts = split_into_chunks(text, max_words=config.CHUNK_SIZE) if not sub_texts: raise RuntimeError("No text to synthesize") -- cgit v1.2.3