aboutsummaryrefslogtreecommitdiff
path: root/converter/tts.py
diff options
context:
space:
mode:
authorhistoria <historiavg@proton.me>2026-08-21 02:31:54 -0400
committerhistoria <historiavg@proton.me>2026-08-21 02:31:54 -0400
commitf7021704b6b26ee747558d9ad701c2b25baedd2a (patch)
treefeda79e97ad0d6601558123bfe9093cfc4ac66e9 /converter/tts.py
parentfea9222740da007f1d7befcd7dee035265c0e5d1 (diff)
downloadtts-audiobook-generator-f7021704b6b26ee747558d9ad701c2b25baedd2a.tar.gz
feat: catalog-driven audio.cpp server.json creation
Diffstat (limited to 'converter/tts.py')
-rw-r--r--converter/tts.py51
1 files changed, 45 insertions, 6 deletions
diff --git a/converter/tts.py b/converter/tts.py
index 0803bb9..83284a7 100644
--- a/converter/tts.py
+++ b/converter/tts.py
@@ -783,9 +783,15 @@ class AudioCppTTSClient(_BaseTTSClient):
"""
def __init__(self, voice: Optional[str] = None, language: Optional[str] = None,
- api_url: Optional[str] = None, chunk_text: bool = False):
+ api_url: Optional[str] = None, chunk_text: bool = False,
+ model_id: Optional[str] = None):
self.api_url = (api_url or config.AUDIOCPP_API_URL).rstrip("/")
- self.model_id = config.AUDIOCPP_MODEL_ID
+ # Per-run model selection: the --model CLI flag overrides config; an
+ # empty value is resolved at connect time when the server hosts exactly
+ # one entry, so multi-model servers don't require editing config.py.
+ self.model_id = (model_id if model_id is not None
+ else config.AUDIOCPP_MODEL_ID) or ""
+ self._model_id_explicit = bool(self.model_id)
# Validate before connecting so bad values fail fast without a server.
self.language = normalize_language(
language if language is not None else config.LANGUAGE)
@@ -820,6 +826,7 @@ class AudioCppTTSClient(_BaseTTSClient):
"""
self._check_health()
models = self._list_models()
+ self._auto_pick_model_id(models)
if self.preset_mode:
self._select_model(models)
self._require_model_id(models)
@@ -894,6 +901,29 @@ class AudioCppTTSClient(_BaseTTSClient):
})
return models
+ def _auto_pick_model_id(self, models: List[Dict[str, str]]) -> None:
+ """Resolve an empty model id when the server hosts exactly one entry.
+
+ Multi-model servers generated with several lazily-loaded entries can
+ be used without editing converter/config.py: leave AUDIOCPP_MODEL_ID
+ (and ``--model``) unset, and the single hosted entry is chosen
+ automatically. With more than one entry an explicit choice is required
+ (via ``--model`` or AUDIOCPP_MODEL_ID), since guessing would risk
+ synthesizing a whole book with the wrong family.
+ """
+ if self.model_id:
+ return
+ if len(models) == 1:
+ self.model_id = models[0]["id"]
+ logger.info(
+ "AUDIOCPP_MODEL_ID is unset; using the only server entry '%s'",
+ self.model_id)
+ else:
+ logger.debug(
+ "AUDIOCPP_MODEL_ID is unset and the server hosts %d entries; "
+ "an explicit --model or config id is required",
+ len(models))
+
def _require_model_id(self, models: List[Dict[str, str]]) -> None:
"""Verify the model id chosen for this run exists on the server.
@@ -902,9 +932,17 @@ class AudioCppTTSClient(_BaseTTSClient):
server hosting only a cloning model works for --voice.
"""
model_ids = [model["id"] for model in models]
- if self.model_id in model_ids:
+ if self.model_id and self.model_id in model_ids:
return
configured = ", ".join(model_ids) or "none"
+ if not self.model_id:
+ raise RuntimeError(
+ f"The audio.cpp server at {self.api_url} hosts {len(model_ids)} "
+ f"model entries ({configured}); audiobook.py needs to know which "
+ "one to use. Pass --model <id> when converting, or set "
+ "AUDIOCPP_MODEL_ID in converter/config.py to one of them "
+ "(see README)."
+ )
if self.preset_mode:
raise RuntimeError(
f"The audio.cpp server at {self.api_url} has no model id "
@@ -912,15 +950,16 @@ class AudioCppTTSClient(_BaseTTSClient):
f"'{config.AUDIOCPP_CLONE_MODEL_ID}' (configured: {configured}). "
"Add a TTS model entry for the family you want to the server "
"config and match AUDIOCPP_MODEL_ID / AUDIOCPP_CLONE_MODEL_ID "
- "in converter/config.py to its id (see README)."
+ "in converter/config.py to its id, or select it per run with "
+ "--model (see README)."
)
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 Qwen3-TTS 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 voice preset on any TTS "
- "model (see README)."
+ "id (or pass --model), or rerun with --voice to use a voice preset "
+ "on any TTS model (see README)."
)
def _select_model(self, models: List[Dict[str, str]]) -> None: