diff options
| author | historia <historiavg@proton.me> | 2026-08-20 17:13:42 -0400 |
|---|---|---|
| committer | historia <historiavg@proton.me> | 2026-08-20 17:13:42 -0400 |
| commit | b873844f7eb681119542661ef588c5b452f88763 (patch) | |
| tree | 65f31a708da479fc5fdad99db14785d1e45645d4 /converter/tts.py | |
| parent | 1f2142e7f610871a6bbe6498d0709d310fcbebb1 (diff) | |
| download | tts-audiobook-generator-b873844f7eb681119542661ef588c5b452f88763.tar.gz | |
fix: crash when audio.cpp is only serving clone model
Diffstat (limited to 'converter/tts.py')
| -rw-r--r-- | converter/tts.py | 47 |
1 files changed, 37 insertions, 10 deletions
diff --git a/converter/tts.py b/converter/tts.py index a1c0b09..1a5b1a0 100644 --- a/converter/tts.py +++ b/converter/tts.py @@ -719,7 +719,10 @@ class AudioCppTTSClient(_BaseTTSClient): startup because an unresolvable name would silently fall back to plain TTS on the Base model instead of failing. When AUDIOCPP_CLONE_MODEL_ID names a second server entry (typically the - Base model), preset requests are routed to it. + Base model), preset requests are routed to it. Only the entry + actually used needs to exist on the server: a clone-only (Base) + server works for --voice runs, while speaker mode on such a server + fails with a hint to pass --voice. Each response is a complete WAV file, so sub-request audio is concatenated with the same lossless path used for the Gradio client. @@ -738,13 +741,17 @@ class AudioCppTTSClient(_BaseTTSClient): self.preset_mode = bool(voice) self.voice = voice or speaker_display_name() self._check_health() - model_ids = self._check_model() + model_ids = self._list_model_ids() if self.preset_mode: + # Resolve the model before validating so the check covers the + # id actually used; a clone-only server works for --voice runs. self._select_model(model_ids) + self._require_model_id(model_ids) self._check_voice() print(f"[OK] Connected to audio.cpp server at {self.api_url} " f"(model '{self.model_id}', voice '{self.voice}')") else: + self._require_model_id(model_ids) print(f"[OK] Connected to audio.cpp server at {self.api_url} " f"(model '{self.model_id}', speaker '{self.voice}')") print("[INFO] Speaker mode expects the server to be configured with the " @@ -787,8 +794,8 @@ class AudioCppTTSClient(_BaseTTSClient): f"The audio.cpp server at {self.api_url} reports status " f"{payload.get('status')!r} instead of 'ok'") - def _check_model(self) -> List[str]: - """Verify the configured model id exists; return all server model ids.""" + def _list_model_ids(self) -> List[str]: + """Fetch the model ids reported by the server.""" try: payload = self._get_json("/v1/models") except Exception as exc: @@ -797,15 +804,35 @@ class AudioCppTTSClient(_BaseTTSClient): f"/v1/models: {exc}") from exc entries = payload.get("data") or [] model_ids = [entry.get("id") for entry in entries if isinstance(entry, dict)] - if self.model_id not in model_ids: - configured = ", ".join(str(mid) for mid in model_ids if mid) or "none" + return [mid for mid in model_ids if mid] + + def _require_model_id(self, model_ids: List[str]) -> None: + """Verify the model id chosen for this run exists on the server. + + Speaker mode needs AUDIOCPP_MODEL_ID (the CustomVoice entry). + Preset mode validates whichever id _select_model resolved, so a + server hosting only the Base (cloning) model works for --voice. + """ + if self.model_id in model_ids: + return + configured = ", ".join(model_ids) or "none" + if self.preset_mode: raise RuntimeError( f"The audio.cpp server at {self.api_url} has no model id " - f"'{self.model_id}' (configured: {configured}). Add a qwen3_tts " - "model entry to the server config and match AUDIOCPP_MODEL_ID " - "in converter/config.py to its id (see README)." + f"'{self.model_id}' or clone model id " + f"'{config.AUDIOCPP_CLONE_MODEL_ID}' (configured: {configured}). " + "Add a qwen3_tts model entry to the server config and match " + "AUDIOCPP_MODEL_ID / AUDIOCPP_CLONE_MODEL_ID in " + "converter/config.py to its id (see README)." ) - return [mid for mid in model_ids if mid] + raise RuntimeError( + f"The audio.cpp server at {self.api_url} has no model id " + f"'{self.model_id}' (configured: {configured}). Speaker mode needs " + "the CustomVoice model: add a qwen3_tts model entry to the server " + "config and match AUDIOCPP_MODEL_ID in converter/config.py to its " + "id, or rerun with --voice to use a cloning preset on the Base " + "model (see README)." + ) def _select_model(self, model_ids: List[str]) -> None: """Pick the model for preset (cloning) requests. |
