From db38085d07ce75f8961eecdc1919e98748254c53 Mon Sep 17 00:00:00 2001 From: historia Date: Fri, 28 Aug 2026 14:57:48 -0400 Subject: refactor: overhaul config.py, remove cli default options --- app/backends/audiocpp/__init__.py | 6 ++--- app/backends/audiocpp/configsync.py | 42 ----------------------------------- app/backends/audiocpp/wizard.py | 35 ++++------------------------- app/backends/faster.py | 18 +++++---------- app/backends/qwen.py | 44 ++++++++++++++++++++----------------- 5 files changed, 35 insertions(+), 110 deletions(-) (limited to 'app/backends') diff --git a/app/backends/audiocpp/__init__.py b/app/backends/audiocpp/__init__.py index f55cf35..02fe967 100644 --- a/app/backends/audiocpp/__init__.py +++ b/app/backends/audiocpp/__init__.py @@ -9,7 +9,7 @@ Modules: catalog the model_specs catalog + server.json building/selections models install state on disk, missing-model guidance, downloads voices reference-.wav transcription planning and execution - configsync app/converter/config.py + server.json port/id/backend sync + configsync app/converter/config.py + server.json port/backend sync build checkout lifecycle: ggml patches, binary build, update, uninstall remote querying a running server for its models/voices @@ -56,7 +56,6 @@ from .voices import ( from .configsync import ( config_port, update_config_api_url_port, - update_config_model_ids, update_server_backend, update_server_config_port, ) @@ -97,8 +96,7 @@ __all__ = [ "transcribe_wav_dir", "print_empty_transcript_warning", # configsync "config_port", "update_config_api_url_port", - "update_config_model_ids", "update_server_config_port", - "update_server_backend", + "update_server_config_port", "update_server_backend", # build "find_local_checkout", "find_audiocpp_server_bin", "find_build_script", "apply_ggml_patches", "build_audiocpp", "uninstall", "update", diff --git a/app/backends/audiocpp/configsync.py b/app/backends/audiocpp/configsync.py index 0a161c0..bce8142 100644 --- a/app/backends/audiocpp/configsync.py +++ b/app/backends/audiocpp/configsync.py @@ -75,25 +75,6 @@ def update_server_config_port(port: int) -> bool: return True -def update_config_model_ids(model_id: str, - clone_model_id: Optional[str] = None, - config_path: Optional[Path] = None) -> bool: - """Rewrite AUDIOCPP_MODEL_ID (and AUDIOCPP_CLONE_MODEL_ID when given). - - Goes through ``common.update_config_value`` so the imported config - module mirrors the change immediately. Returns True when every named - key now holds its value in the file. - """ - path = Path(config_path) if config_path is not None else CONFIG_PATH - ok = common.update_config_value("AUDIOCPP_MODEL_ID", model_id, - config_path=path) - if clone_model_id is not None: - ok = common.update_config_value("AUDIOCPP_CLONE_MODEL_ID", - clone_model_id, - config_path=path) and ok - return ok - - def _apply_port_sync(port: int, accepted: bool) -> None: """Write the port into app/converter/config.py, or report when declined.""" if accepted: @@ -106,29 +87,6 @@ def _apply_port_sync(port: int, accepted: bool) -> None: f"will still use port {config_port()}") -def _offer_config_model_id_sync(model_id: str, accepted: Optional[bool]) -> None: - """Point app/converter/config.py at a single hosted model entry. - - The converter requests the model id configured in AUDIOCPP_MODEL_ID, - and single-model servers use the same id for the clone entry, so both - ids are rewritten together. ACCEPTED is True/False (apply/skip the - rewrite) or None when no single-entry sync applies (nothing to do). - """ - if config.AUDIOCPP_MODEL_ID == model_id \ - and config.AUDIOCPP_CLONE_MODEL_ID == model_id: - return - if accepted is None: - return - if accepted: - if not update_config_model_ids(model_id, model_id): - print(f"[WARNING] Could not update {CONFIG_PATH}; edit " - "AUDIOCPP_MODEL_ID and AUDIOCPP_CLONE_MODEL_ID by hand so " - "audiobook.py uses this model") - else: - print("[WARNING] Left the model ids unchanged; audiobook.py will " - f"still request model '{config.AUDIOCPP_MODEL_ID}'") - - def update_server_backend(backend: str) -> bool: """Rewrite the 'backend' in the checkout's server.json, or True when none. diff --git a/app/backends/audiocpp/wizard.py b/app/backends/audiocpp/wizard.py index d85076a..6d2d33a 100644 --- a/app/backends/audiocpp/wizard.py +++ b/app/backends/audiocpp/wizard.py @@ -20,7 +20,6 @@ from backends.common import ( resolve_wav_dir_arg, write_prompt_text, ) -from converter import config from ui import taskview, tui from . import build as _build from . import configsync as _configsync @@ -288,7 +287,6 @@ def _wizard(stdscr, args: argparse.Namespace, parser: argparse.ArgumentParser "build": build, "lazy_load": True, "sync_port": None, - "sync_model_ids": s["sync_model_ids"], "wav_dir": s["wav_dir"], "plan": s["plan"], "download": s["download"], @@ -385,7 +383,6 @@ def _wizard(stdscr, args: argparse.Namespace, parser: argparse.ArgumentParser s["download"] = bool(result.get("download")) and ( _models.download_applicable(s["audiocpp_dir"], s["model_entries"])) - s["sync_model_ids"] = result.get("sync_model_ids") s["delete_unused"] = bool(result.get("delete_unused")) \ and bool(s["unused_entries"]) return _finalize() @@ -395,9 +392,9 @@ def _wizard(stdscr, args: argparse.Namespace, parser: argparse.ArgumentParser The Generate-audiobooks-style form replaces the old one-question- per-screen chain (host, port, port sync, backend, build offer, - wav directory, transcription plan, model-id sync, delete unused, - download). Rows whose question does not apply are hidden rather - than skipped silently. Esc or Cancel pops back to the model tree. + wav directory, transcription plan, delete unused, download). Rows + whose question does not apply are hidden rather than skipped + silently. Esc or Cancel pops back to the model tree. """ try: _compute_entries() @@ -487,16 +484,6 @@ def _wizard(stdscr, args: argparse.Namespace, parser: argparse.ArgumentParser "kind": "bool", "value": True, }) - model_sync_relevant = len(s["entry_ids"]) == 1 and not ( - config.AUDIOCPP_MODEL_ID == s["entry_ids"][0] - and config.AUDIOCPP_CLONE_MODEL_ID == s["entry_ids"][0]) - if model_sync_relevant: - fields.append({ - "key": "sync_model_ids", - "label": f"Make '{s['entry_ids'][0]}' the default model?", - "kind": "bool", "value": True, - }) - new_paths = {entry["path"] for entry in s["model_entries"]} s["unused_entries"] = _models.unused_installed_entries( s["output_path"], new_paths) \ @@ -636,9 +623,6 @@ def _execute_lanes(settings: dict, print(f"[OK] Deleted {removed} unused model " f"{'entry' if removed == 1 else 'entries'} from disk.") - if len(settings["entry_ids"]) == 1: - _configsync._offer_config_model_id_sync(settings["entry_ids"][0], - settings["sync_model_ids"]) _voices.print_empty_transcript_warning(state["transcripts"]) return 0 @@ -927,13 +911,6 @@ def _collect_from_flags(args: argparse.Namespace, print("[INFO] Aborted; existing server.json kept") return None - # Config sync decisions (auto-apply unless explicitly declined). - sync_model_ids: Optional[bool] = None - if len(entry_ids) == 1 and not ( - config.AUDIOCPP_MODEL_ID == entry_ids[0] - and config.AUDIOCPP_CLONE_MODEL_ID == entry_ids[0]): - sync_model_ids = not args.no_sync_model_ids - # Wav dir + transcription plan (defaults to the project's voices/ dir). wav_dir = args.input_dir if args.input_dir is not None else VOICES_DIR plan: Optional[dict] = None @@ -961,7 +938,6 @@ def _collect_from_flags(args: argparse.Namespace, "build": build, "lazy_load": lazy_load, "sync_port": None, - "sync_model_ids": sync_model_ids, "wav_dir": wav_dir, "plan": plan, "download": args.download, @@ -972,7 +948,7 @@ def build_parser() -> argparse.ArgumentParser: """The audio.cpp setup CLI (also used to build a default namespace).""" parser = argparse.ArgumentParser( description="Set up the audio.cpp TTS backend: clone/build, pick " - "models, write server.json, and sync app/converter/config.py.") + "models, write server.json, and sync the configured port.") parser.add_argument("--wavs", type=resolve_wav_dir_arg, default=None, dest="input_dir", metavar="WAV_DIR", help="Directory with .wav reference files to publish as " @@ -1017,9 +993,6 @@ def build_parser() -> argparse.ArgumentParser: help="Run model_manager_v2.py install for each hosted " "model automatically (default: print the commands " "only)") - parser.add_argument("--no-sync-model-ids", action="store_true", - help="Do not rewrite AUDIOCPP_MODEL_ID/" - "AUDIOCPP_CLONE_MODEL_ID for a single-entry server") return parser diff --git a/app/backends/faster.py b/app/backends/faster.py index 285d865..2d0cad3 100755 --- a/app/backends/faster.py +++ b/app/backends/faster.py @@ -337,27 +337,19 @@ def _execute_steps(settings: dict) -> List[taskview.TaskStep]: if voices is None: return 1 - # Sync app/converter/config.py default voice. (The server port is - # not touched here: it lives in FASTER_API_URL, edited in the - # Settings screen.) - default_voice = next(iter(voices)) - if default_voice != config.FASTER_VOICE: - if common.update_config_value("FASTER_VOICE", default_voice): - print(f"[OK] Updated FASTER_VOICE to {default_voice}") - else: - print("[WARNING] Could not update FASTER_VOICE; edit " - "app/converter/config.py by hand") - + # (The server port is not touched here: it lives in + # FASTER_API_URL, edited in the Settings screen. Voices are picked + # per run with --voice / on the Generate-audiobooks screen.) _print_launch_hint(settings["output_path"]) return 0 steps.append(taskview.TaskStep( - "Write voices.json & sync config", write)) + "Write voices.json", write)) return steps def _execute(settings: dict) -> int: - """Console tail: install, clone, write voices.json, sync, advise.""" + """Console tail: install, clone, write voices.json, advise.""" return taskview.run_steps_inline(_execute_steps(settings)) diff --git a/app/backends/qwen.py b/app/backends/qwen.py index d6dd2e0..a4cddb5 100644 --- a/app/backends/qwen.py +++ b/app/backends/qwen.py @@ -6,9 +6,10 @@ ONE Qwen3-TTS model per process — CustomVoice (built-in speakers), Base (voice cloning) or VoiceDesign (described voice). This module sets it up end-to-end: pip-install the package into its own managed venv (``app/envs/qwen`` — the app venv and the faster backend's never receive -it). There are no questions to ask — the port and which model to run live in -``app/converter/config.py`` (the model is chosen per run on the hub's -Generate-audiobooks screen), and only one server runs at a time. +it). There are no questions to ask — the port lives in +``app/converter/config.py`` (the hub's Settings screen) and the model is +chosen per run on the hub's Generate-audiobooks screen; only one server +runs at a time. Model weights are not part of the install: each demo lazily fetches its ~4GB snapshot from HuggingFace into the standard hub cache the first time a @@ -52,9 +53,9 @@ QWEN_PIP_PKG = "qwen-tts" QWEN_ENV = envs.QWEN_ENV_DIR DEFAULT_PORT = 7860 -# The models a single demo server can host, by config.QWEN_MODEL name. -# A running server identifies itself via its probe identity (backends.probe), -# so "which model is up" is always read off the server, never assumed. +# The models a single demo server can host. A running server identifies +# itself via its probe identity (backends.probe), so "which model is up" is +# always read off the server, never assumed. MODEL_REPOS = { "CustomVoice": "Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice", "Base": "Qwen/Qwen3-TTS-12Hz-1.7B-Base", @@ -66,11 +67,13 @@ IDENTITY_TO_MODEL = { probe.IDENTITY_QWEN_CLONE: "Base", probe.IDENTITY_QWEN_DESIGN: "VoiceDesign", } +# The model a fresh managed start (Start/Stop Backend Servers menu) hosts; +# Generate-audiobooks runs needing another model restart the server. DEFAULT_MODEL = "CustomVoice" -# Built-in CustomVoice speakers (see app/converter/config.py SPEAKER). The -# canonical list lives in converter.clients.speakers (shared with the -# audio.cpp backend's Convert-form Speaker picker). +# Built-in CustomVoice speakers. The canonical list lives in +# converter.clients.speakers (shared with the audio.cpp backend's +# Convert-form Speaker picker); a run's speaker is picked per run. QWEN_SPEAKERS = QWEN3_TTS_SPEAKERS @@ -179,8 +182,8 @@ def _config_port(url: str, fallback: int) -> int: def current_model() -> str: - """The configured model to host (a MODEL_REPOS key; DEFAULT_MODEL on typos).""" - return config.QWEN_MODEL if config.QWEN_MODEL in MODEL_REPOS else DEFAULT_MODEL + """The model a fresh managed start hosts (DEFAULT_MODEL).""" + return DEFAULT_MODEL def model_for_identity(identity: Optional[str]) -> Optional[str]: @@ -202,8 +205,9 @@ def _wizard(stdscr, args: argparse.Namespace) -> dict: The qwen backend has no per-install choices: install happens when the package is missing (and not skipped by flag), and every other value — - port, speaker, which model runs — lives in app/converter/config.py / - the hub's Settings and Generate-audiobooks screens. + port — lives in app/converter/config.py (the hub's Settings screen); + the model and voice are picked per run on the Generate-audiobooks + screen. """ return { "do_install": (not _is_installed()) and not args.skip_install, @@ -306,11 +310,12 @@ def _managed_running_model() -> Optional[str]: def detect() -> BackendStatus: """Detect whether qwen-tts is installed, plus the launch command. - One managed spec exists, hosting ``config.QWEN_MODEL`` on the single - configured port. Which model currently answers there is read via the - probe (local pid alive => check our own URL; otherwise the remote URL) - so the status names the *running* model even when it differs from the - configured one. + One managed spec exists, hosting the default model on the single + configured port (Generate-audiobooks runs needing another model boot + it via their own spec). Which model currently answers there is read + via the probe (local pid alive => check our own URL; otherwise the + remote URL) so the status names the *running* model even when it + differs from the default one. """ installed = _is_installed() model = current_model() @@ -319,8 +324,7 @@ def detect() -> BackendStatus: details.append("pip: installed" if installed else "not installed — run setup to pip install qwen-tts") details.append(f"port: {_config_port(url, DEFAULT_PORT)}") - details.append(f"model: {model}") - details.append(f"speaker: {config.SPEAKER}") + details.append(f"default model: {model}") specs = [_build_spec(model)] managed = servers.manages(specs) # A locally-managed server names its running model via the probe of the -- cgit v1.2.3