aboutsummaryrefslogtreecommitdiff
path: root/app/converter/converter.py
diff options
context:
space:
mode:
authorhistoria <historiavg@proton.me>2026-09-02 22:53:07 -0400
committerhistoria <historiavg@proton.me>2026-09-02 22:53:07 -0400
commitd04a2c53b926ccde0d582dbf4a7360dc0f072205 (patch)
treed817622c7d32039d293c6b7d3141d40028533b96 /app/converter/converter.py
parent7a7dca313750ee75e0f8a2a5442ca5d78e743294 (diff)
downloadtts-audiobook-generator-d04a2c53b926ccde0d582dbf4a7360dc0f072205.tar.gz
fix: warn before using a likely too-big chunk size for sglang-omni models
Diffstat (limited to 'app/converter/converter.py')
-rw-r--r--app/converter/converter.py75
1 files changed, 74 insertions, 1 deletions
diff --git a/app/converter/converter.py b/app/converter/converter.py
index 0769258..3b878c8 100644
--- a/app/converter/converter.py
+++ b/app/converter/converter.py
@@ -220,6 +220,73 @@ def prompt_overwrite(existing: List[Path], output_name: str,
print("Please answer 'y' or 'n' (or press Enter for yes).")
+class ChunkClampCancelled(Exception):
+ """The chunk-cap popup's Cancel answer: the run stops unstarted."""
+
+
+def chunk_clamp_needed(entry) -> bool:
+ """True when ENTRY cannot narrate a full CHUNK_SIZE sub-request.
+
+ A catalog entry declares ``chunk_words`` when its engine's admission
+ window caps one request below what the configured CHUNK_SIZE can
+ narrate (Higgs: the server pins each request's prompt plus
+ generation at 4096 tokens).
+ """
+ return (entry is not None and entry.chunk_words is not None
+ and entry.chunk_words < config.CHUNK_SIZE)
+
+
+def chunk_clamp_message(entry) -> List[str]:
+ """The popup text for a chunk_words-capped ENTRY (short lines)."""
+ return [
+ f"{entry.label} can narrate at most ~{entry.chunk_words} words "
+ "per request: the server caps",
+ "each request's prompt plus generation at a fixed window. "
+ "Longer sub-chunks",
+ "may cut off mid-sentence.",
+ ]
+
+
+def prompt_chunk_clamp(entry, ask=None) -> Optional[int]:
+ """The per-run sub-request word cap for ENTRY (None = no clamp).
+
+ Models whose catalog entry declares ``chunk_words`` below CHUNK_SIZE
+ (Higgs) cannot narrate a full sub-chunk in one request; the popup
+ offers to clamp CHUNK_SIZE for this run, keep it ("try anyway",
+ risking mid-chunk truncation), or cancel the run. ASK, when given,
+ replaces the console prompt: it is called with (message lines, words)
+ and returns "clamp" or "anyway" (Cancel raises inside the callback —
+ the hub maps it to returning to the Generate form). A closed stdin
+ (non-interactive run) clamps: unattended runs keep working and never
+ produce silently truncated audio.
+ """
+ if not chunk_clamp_needed(entry):
+ return None
+ lines = chunk_clamp_message(entry)
+ if ask is not None:
+ answer = ask(lines, entry.chunk_words)
+ return entry.chunk_words if answer == "clamp" else None
+ for line in lines:
+ print(line)
+ while True:
+ try:
+ answer = input(
+ f"Set Chunk to {entry.chunk_words} for this run, try "
+ "anyway, or cancel? [S/t/c]: ").strip().lower()
+ except EOFError:
+ print(f"\n[WARNING] No interactive input available; clamping "
+ f"sub-requests to {entry.chunk_words} words for this run")
+ return entry.chunk_words
+ if answer in ("", "s", "set"):
+ return entry.chunk_words
+ if answer in ("t", "try"):
+ return None
+ if answer in ("c", "cancel"):
+ raise ChunkClampCancelled(
+ "Conversion cancelled at the chunk-size prompt")
+ print("Please answer 's', 't', or 'c'.")
+
+
class AudiobookConverter:
"""Audiobook converter using a local TTS API."""
@@ -236,6 +303,7 @@ class AudiobookConverter:
instructions: Optional[str] = None,
request_options: Optional[Dict[str, str]] = None,
api_url: Optional[str] = None,
+ chunk_size: Optional[int] = None,
unload_models: Optional[bool] = None,
progress: Optional[Callable[[dict], None]] = None,
cancel=None):
@@ -324,13 +392,18 @@ class AudiobookConverter:
"catalog key for --model (see the backend docs).")
model_id = entry.key
self.model_id = model_id
+ # CHUNK_SIZE carries a per-run override (the pre-flight chunk
+ # popup's clamp for models whose engine caps one request below
+ # a full sub-chunk); None follows the config.CHUNK_SIZE setting.
+ self.chunk_size = chunk_size
self.tts = SgOmniTTSClient(
chunks_dir=CHUNKS_FOLDER, model=model_id, voice=voice,
ref_audio=voice_clone_ref_audio,
ref_text=voice_clone_ref_text,
skip_transcription=skip_transcription,
instructions=instructions, language=self.language,
- api_url=api_url, quiet=quiet, cancel=cancel)
+ api_url=api_url, chunk_size=self.chunk_size,
+ quiet=quiet, cancel=cancel)
else:
# Qwen: the voice mode picks the request shape (built-in
# speaker, clone from a reference .wav, or a designed voice);