aboutsummaryrefslogtreecommitdiff
path: root/app/backends/audiocpp/remote.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/remote.py
parentacbd9ff2c91182d96c57ffb57bee6e9b3fcbcbd4 (diff)
downloadtts-audiobook-generator-8b5c8697740ff415cf7f1d03c9fb5a8c8851d420.tar.gz
refactor: audiocpp.py setup flow
Diffstat (limited to 'app/backends/audiocpp/remote.py')
-rw-r--r--app/backends/audiocpp/remote.py59
1 files changed, 59 insertions, 0 deletions
diff --git a/app/backends/audiocpp/remote.py b/app/backends/audiocpp/remote.py
new file mode 100644
index 0000000..42b3872
--- /dev/null
+++ b/app/backends/audiocpp/remote.py
@@ -0,0 +1,59 @@
+"""Query a running audiocpp_server for its models and voices."""
+
+import json
+import urllib.request
+from typing import Dict, List, Optional
+
+from .constants import FALLBACK_PORT
+
+def fetch_server_models(api_url: str) -> Optional[List[Dict[str, str]]]:
+ """List a running audiocpp_server's model entries via GET /v1/models.
+
+ Returns ``[{id, family, task}, ...]`` — the same shape the converter's
+ client resolves at startup — or None when URL does not answer with a
+ valid document (wrong server, still starting, older audio.cpp). Used by
+ the hub to drive the convert menus against a remote server that has no
+ local server.json describing it.
+ """
+ try:
+ with urllib.request.urlopen(
+ f"{api_url.rstrip('/')}/v1/models", timeout=10) as response:
+ payload = json.loads(response.read().decode("utf-8"))
+ except (OSError, ValueError):
+ # URLError/HTTPError/socket errors are OSErrors; a non-JSON body is
+ # a ValueError. Anything else means "not an audiocpp_server".
+ return None
+ entries = payload.get("data") if isinstance(payload, dict) else None
+ models: List[Dict[str, str]] = []
+ for entry in entries or []:
+ if isinstance(entry, dict) and entry.get("id"):
+ models.append({
+ "id": str(entry["id"]),
+ "family": str(entry.get("family") or ""),
+ "task": str(entry.get("task") or ""),
+ })
+ return models
+
+
+def fetch_server_voices(api_url: str, model_id: str) -> Optional[List[str]]:
+ """List a running audiocpp_server's voices for MODEL_ID.
+
+ Queries ``GET /v1/audio/voices?model=<id>`` — the endpoint the converter
+ validates ``--voice`` against — and returns its voice-name list, or None
+ when the server cannot be queried. Lets the hub offer a remote server's
+ voices without reading its configuration locally.
+ """
+ query = urllib.parse.urlencode({"model": model_id})
+ try:
+ with urllib.request.urlopen(
+ f"{api_url.rstrip('/')}/v1/audio/voices?{query}",
+ timeout=10) as response:
+ payload = json.loads(response.read().decode("utf-8"))
+ except (OSError, ValueError):
+ return None
+ voices = payload.get("voices") if isinstance(payload, dict) else None
+ if not isinstance(voices, list):
+ return None
+ return [str(voice) for voice in voices]
+
+