aboutsummaryrefslogtreecommitdiff
path: root/app/backends/audiocpp/configsync.py
diff options
context:
space:
mode:
authorhistoria <historiavg@proton.me>2026-08-26 02:25:55 -0400
committerhistoria <historiavg@proton.me>2026-08-26 02:25:55 -0400
commit8b5c8697740ff415cf7f1d03c9fb5a8c8851d420 (patch)
tree28c0323c54c896af5f89fb34b89a62e0fe0df291 /app/backends/audiocpp/configsync.py
parentacbd9ff2c91182d96c57ffb57bee6e9b3fcbcbd4 (diff)
downloadtts-audiobook-generator-8b5c8697740ff415cf7f1d03c9fb5a8c8851d420.tar.gz
refactor: audiocpp.py setup flow
Diffstat (limited to 'app/backends/audiocpp/configsync.py')
-rw-r--r--app/backends/audiocpp/configsync.py163
1 files changed, 163 insertions, 0 deletions
diff --git a/app/backends/audiocpp/configsync.py b/app/backends/audiocpp/configsync.py
new file mode 100644
index 0000000..0a161c0
--- /dev/null
+++ b/app/backends/audiocpp/configsync.py
@@ -0,0 +1,163 @@
+"""Keep app/converter/config.py and server.json in sync with setup choices."""
+
+import json
+import re
+import urllib.parse
+from pathlib import Path
+from typing import Optional
+
+from backends import common
+from backends.common import CONFIG_PATH, url_with_port
+from converter import config
+from . import build
+from .constants import FALLBACK_PORT
+
+def config_port() -> int:
+ """Return the port of AUDIOCPP_API_URL in app/converter/config.py."""
+ try:
+ return urllib.parse.urlsplit(config.AUDIOCPP_API_URL).port or FALLBACK_PORT
+ except ValueError:
+ return FALLBACK_PORT
+
+
+def update_config_api_url_port(port: int, config_path: Optional[Path] = None) -> bool:
+ """Rewrite the port inside AUDIOCPP_API_URL in app/converter/config.py.
+
+ Reads the configured URL from the file (not from the imported module,
+ which a long hub session can leave behind), swaps its port for PORT,
+ and writes it back through ``common.update_config_value`` so the
+ imported module mirrors the change immediately. Returns True when the
+ file now holds the new URL.
+ """
+ path = Path(config_path) if config_path is not None else CONFIG_PATH
+ try:
+ text = path.read_text(encoding="utf-8")
+ except OSError:
+ return False
+ match = re.search(r'(?m)^\s*AUDIOCPP_API_URL\s*=\s*"([^"]*)"', text)
+ if not match:
+ return False
+ return common.update_config_value("AUDIOCPP_API_URL",
+ url_with_port(match.group(1), port),
+ config_path=path)
+
+
+def update_server_config_port(port: int) -> bool:
+ """Rewrite the 'port' in the audio.cpp checkout's server.json.
+
+ Loads ``<checkout>/server.json``, sets its ``port`` to PORT, and
+ rewrites it with the same ``json.dump`` formatting the wizard uses.
+ Returns True when the file now carries PORT (a no-op when it already
+ does), and False when there is no checkout/server.json or the file
+ cannot be read or written.
+ """
+ checkout = build.find_local_checkout()
+ if checkout is None:
+ return False
+ server_json = checkout / "server.json"
+ if not server_json.exists():
+ return False
+ try:
+ data = json.loads(server_json.read_text(encoding="utf-8"))
+ except (OSError, ValueError):
+ return False
+ if not isinstance(data, dict):
+ return False
+ if data.get("port") == port:
+ return True
+ data["port"] = port
+ 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:
+ return False
+ 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:
+ if not update_config_api_url_port(port):
+ print(f"[WARNING] Could not update {CONFIG_PATH}; edit "
+ "AUDIOCPP_API_URL by hand so audiobook.py uses the "
+ "new port")
+ else:
+ print("[WARNING] Left AUDIOCPP_API_URL unchanged; audiobook.py "
+ 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.
+
+ Sets ``backend`` to BACKEND in ``<checkout>/server.json`` (same
+ ``json.dump`` formatting as the wizard). Returns True when the file now
+ carries BACKEND, when there is no server.json (nothing to sync), or when
+ it already does; False when the file exists but cannot be read/written.
+ """
+ checkout = build.find_local_checkout()
+ if checkout is None:
+ return True
+ server_json = checkout / "server.json"
+ if not server_json.exists():
+ return True
+ try:
+ data = json.loads(server_json.read_text(encoding="utf-8"))
+ except (OSError, ValueError):
+ return False
+ if not isinstance(data, dict):
+ return False
+ if data.get("backend") == backend:
+ return True
+ data["backend"] = backend
+ 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:
+ return False
+ return True
+
+