diff options
| author | historia <historiavg@proton.me> | 2026-08-30 20:42:02 -0400 |
|---|---|---|
| committer | historia <historiavg@proton.me> | 2026-08-30 20:42:02 -0400 |
| commit | a0e3050c6e1e43df3941077afa4ade9a1c4d6ce4 (patch) | |
| tree | d8492bbcbbf6850afc127bae862abe68e1198c0c /app/backends/audiocpp/catalog.py | |
| parent | 93f106aac2d6411c80a911adac62cd12f80e58be (diff) | |
| download | tts-audiobook-generator-a0e3050c6e1e43df3941077afa4ade9a1c4d6ce4.tar.gz | |
fix: non-clone models correctly supported in tui, restart server when needed
Diffstat (limited to 'app/backends/audiocpp/catalog.py')
| -rw-r--r-- | app/backends/audiocpp/catalog.py | 75 |
1 files changed, 74 insertions, 1 deletions
diff --git a/app/backends/audiocpp/catalog.py b/app/backends/audiocpp/catalog.py index 65b525b..78908a3 100644 --- a/app/backends/audiocpp/catalog.py +++ b/app/backends/audiocpp/catalog.py @@ -6,7 +6,9 @@ import sys from pathlib import Path from typing import Dict, List, Optional, Set, Tuple -from .constants import TASK_TTS +from converter.clients import AUDIOCPP_CLONE_ONLY_FAMILIES, audiocpp_family_spec_tasks + +from .constants import TASK_CLON, TASK_TTS DESIGN_PACKAGE_RE = re.compile(r"voice[\s_\-]?design", re.IGNORECASE) @@ -220,6 +222,77 @@ def is_design_package(package: dict) -> bool: return bool(DESIGN_PACKAGE_RE.search(text)) +def is_clone_only_family(family: str, tasks: Optional[Set[str]] = None + ) -> bool: + """True when FAMILY's audio.cpp implementation rejects plain TTS. + + Such families can only synthesize by cloning a reference voice, so + their server entries must be hosted with task "clon" — hosting them + with "tts" fails every speech request at session-creation time. + Families are classified from the explicit known-clone-only set + (AUDIOCPP_CLONE_ONLY_FAMILIES, which also covers specs that wrongly + claim "tts" — Chatterbox) or from a spec task list that names only + "clone" (TASKS, when the caller has it; without one the specs are + read best-effort). + """ + if family in AUDIOCPP_CLONE_ONLY_FAMILIES: + return True + if tasks is None: + tasks = audiocpp_family_spec_tasks(family) + return bool(tasks) and set(tasks) == {"clone"} + + +def hosting_task(entry: dict) -> str: + """The server.json task a family's non-design packages are hosted with. + + Clone-only families (see ``is_clone_only_family``) get "clon" so their + cloning sessions can be created at all; every other family keeps + "tts", which serves plain TTS and — where the family supports it — + cloning through the request's voice field alike. + """ + if is_clone_only_family(str(entry.get("family") or ""), + tasks=set(entry.get("tasks") or []) or None): + return TASK_CLON + return TASK_TTS + + +def rehost_clone_only_entries(server_json: Path, data: dict) -> List[str]: + """Re-host "tts"-tasked clone-only entries in DATA as "clon", in place. + + Server.json files written before clone-only hosting existed carry + task "tts" for families whose audio.cpp implementation rejects plain + TTS sessions (e.g. Chatterbox), so every speech request fails with + HTTP 500. Each such entry's task is rewritten to "clon"; when anything + changed, the document is written back to SERVER_JSON (same layout the + wizard writes). Returns the repaired entries' ids, in order — empty + when nothing needed changing (or the document is unusable). + """ + models = data.get("models") + if not isinstance(models, list): + return [] + repaired: List[str] = [] + for entry in models: + if not isinstance(entry, dict): + continue + if str(entry.get("task") or "") != TASK_TTS: + continue + family = str(entry.get("family") or "") + if not family or not is_clone_only_family(family): + continue + entry["task"] = TASK_CLON + repaired.append(str(entry.get("id") or family)) + if repaired: + try: + with server_json.open("w", encoding="utf-8") as handle: + json.dump(data, handle, indent=2, ensure_ascii=False) + handle.write("\n") + except OSError: + # The in-memory document is fixed either way; a failed write + # only means the fix does not survive the process. + pass + return repaired + + def package_dir_options(entry: dict) -> List[dict]: """Return one option per distinct target_directory of a family's packages. |
