diff options
| author | historia <historiavg@proton.me> | 2026-08-31 19:45:57 -0400 |
|---|---|---|
| committer | historia <historiavg@proton.me> | 2026-08-31 19:45:57 -0400 |
| commit | 10e72d4960e865acf5346ab8cf518ed5844fe45c (patch) | |
| tree | adf8c10386b9da6280c247f1fed137ef1a514157 /app/converter | |
| parent | 4bd0282da65db9f118ef5250582ab67079fad538 (diff) | |
| download | tts-audiobook-generator-10e72d4960e865acf5346ab8cf518ed5844fe45c.tar.gz | |
feat: generate a book with all installed models to compare
Diffstat (limited to 'app/converter')
| -rw-r--r-- | app/converter/clients/audiocpp.py | 17 | ||||
| -rw-r--r-- | app/converter/converter.py | 33 |
2 files changed, 42 insertions, 8 deletions
diff --git a/app/converter/clients/audiocpp.py b/app/converter/clients/audiocpp.py index a1888bc..df1884d 100644 --- a/app/converter/clients/audiocpp.py +++ b/app/converter/clients/audiocpp.py @@ -406,7 +406,8 @@ class AudioCppTTSClient(BaseTTSClient): model_id: Optional[str] = None, instructions: Optional[str] = None, request_options: Optional[Dict[str, str]] = None, - quiet: bool = False): + quiet: bool = False, + unload_models: Optional[bool] = None): super().__init__(chunks_dir, quiet=quiet) self.api_url = (api_url or config.AUDIOCPP_API_URL).rstrip("/") # Per-run model selection: the --model CLI flag (or the Generate @@ -415,6 +416,12 @@ class AudioCppTTSClient(BaseTTSClient): # don't require --model. self.model_id = (model_id or "").strip() self._model_id_explicit = bool(self.model_id) + # Unload previously-loaded server models at connect time: None + # follows the AUDIOCPP_UNLOAD_MODELS setting (read at connect, so + # a Settings change this session is honored); True/False force it + # regardless of the setting ("All (multiple generation)" runs pass + # True so each per-model conversion starts with a clean VRAM). + self._unload_models_override = unload_models # Validate before connecting so bad values fail fast without a server. self.language = normalize_language( language if language is not None else config.LANGUAGE) @@ -570,7 +577,10 @@ class AudioCppTTSClient(BaseTTSClient): self._report(f"[INFO] Sending instruction with every request: {self.instructions}") self._report("[INFO] Its effect (style, emotion, delivery) depends on the " "model family; models without instruction support ignore it.") - if config.AUDIOCPP_UNLOAD_MODELS: + unload = (config.AUDIOCPP_UNLOAD_MODELS + if self._unload_models_override is None + else self._unload_models_override) + if unload: self._unload_server_models() def _require_synthesis_task(self, models: List[Dict[str, str]]) -> None: @@ -595,7 +605,8 @@ class AudioCppTTSClient(BaseTTSClient): on its first request. Failures only warn: an older server without the endpoint, or a busy one, must not block a working setup. Controlled by config.AUDIOCPP_UNLOAD_MODELS (the TUI Settings - "Unload models" option). + "Unload models" option), or forced per run via the unload_models + override ("All (multiple generation)" runs unload between models). """ request = urllib.request.Request( f"{self.api_url}/v1/tasks/unload_all_models", data=b"", diff --git a/app/converter/converter.py b/app/converter/converter.py index bd5e477..a10a57d 100644 --- a/app/converter/converter.py +++ b/app/converter/converter.py @@ -212,6 +212,7 @@ class AudiobookConverter: instructions: Optional[str] = None, request_options: Optional[Dict[str, str]] = None, api_url: Optional[str] = None, + unload_models: Optional[bool] = None, progress: Optional[Callable[[dict], None]] = None, cancel=None): if speed <= 0: @@ -243,6 +244,9 @@ class AudiobookConverter: # them against the server-hosted model at connect time. self.instructions = instructions self.request_options = dict(request_options or {}) + # audio.cpp only: force unloading previously-loaded server models + # at connect time (None follows the AUDIOCPP_UNLOAD_MODELS setting). + self.unload_models = unload_models self._validate_configuration() # Interactive reporting (the TUI run view): PROGRESS receives an # event dict per state change and turns the clients' console prints @@ -261,13 +265,15 @@ class AudiobookConverter: # elsewhere. model_id picks the server entry per run # (auto-selected on single-entry servers); instructions # describe or style the voice, request_options pass - # per-model controls through to the server. + # per-model controls through to the server. unload_models + # forces a pre-run model unload when not None ("All" runs). self.tts = AudioCppTTSClient(chunks_dir=CHUNKS_FOLDER, voice=voice, language=self.language, model_id=model_id, instructions=instructions, request_options=self.request_options, - api_url=api_url, quiet=quiet) + api_url=api_url, quiet=quiet, + unload_models=unload_models) else: # Qwen: the voice mode picks the request shape (built-in # speaker, clone from a reference .wav, or a designed voice); @@ -355,7 +361,6 @@ class AudiobookConverter: voice_clone_ref_audio: Optional[str], instructions: Optional[str] = None) -> str: """Narrator name used in output file names, without a server connection. - Custom voice mode uses the built-in speaker's display name; voice clone mode uses the reference audio file's stem; the faster and audiocpp backends use the server-side voice name (for audiocpp's @@ -392,6 +397,19 @@ class AudiobookConverter: return AudiobookConverter._sanitize_filename( narrator, fallback="narrator").replace(" ", "_") + @staticmethod + def compute_model_tag(model_id: Optional[str]) -> str: + """Model id used in output file names, without a server connection. + + "All (multiple generation)" runs name every output with the + generating model's id so the per-model files never collide + (e.g. ``dune_qwen3_tts_1_7b_base_q8_0_Vivian.m4b``). Pure (no I/O, + no server) so the pre-flight can compute the exact output names a + run would produce before spending time connecting to a TTS server. + """ + return AudiobookConverter._sanitize_filename( + model_id or "", fallback="model").replace(" ", "_") + # ------------------------------------------------------------------ # Debug dumps (--debug) # ------------------------------------------------------------------ @@ -804,6 +822,7 @@ class AudiobookConverter: confirm: Optional[Callable[[str, bool], bool]] = None, book_files: Optional[List[Path]] = None, output_name: Optional[str] = None, + name_tag: Optional[str] = None, ) -> Tuple[List[Path], List[Tuple[Path, str]]]: """Discover books and ask every overwrite question up front. @@ -822,7 +841,10 @@ class AudiobookConverter: (a single --input-file book; still filtered to supported formats), and OUTPUT_NAME overrides the computed output name with a verbatim base name (--output-file's stem, no narrator tag or stem-collision - suffix). Both default to the directory-scan behavior. + suffix). Both default to the directory-scan behavior. NAME_TAG, when + given, is inserted between the book stem and the narrator tag + ("All (multiple generation)" runs pass the sanitized model id, so + each model's outputs are named and planned separately). """ if book_files is None: book_files = sorted( @@ -854,7 +876,8 @@ class AudiobookConverter: name = book_file.stem if stem_counts[book_file.stem] > 1: name = f"{book_file.stem}_{book_file.suffix.lstrip('.')}" - names.append((book_file, f"{name}_{narrator_tag}")) + tag = f"{name_tag}_{narrator_tag}" if name_tag else narrator_tag + names.append((book_file, f"{name}_{tag}")) # Ask every overwrite question up front, before any conversion # starts, so the rest of the run is unattended. |
