aboutsummaryrefslogtreecommitdiff
path: root/backends
diff options
context:
space:
mode:
authorhistoria <historiavg@proton.me>2026-08-24 00:41:52 -0400
committerhistoria <historiavg@proton.me>2026-08-24 00:41:52 -0400
commit194c63e4d11e6de9792a736a7b99788f1db78741 (patch)
tree0c7eca8bee825b8d5c32f4c64e1d87da793caedb /backends
parent5bfbdcb5765fd4eb57d13c67169bb3c2706ead75 (diff)
downloadtts-audiobook-generator-194c63e4d11e6de9792a736a7b99788f1db78741.tar.gz
feat: running process detection, menu gating
Diffstat (limited to 'backends')
-rw-r--r--backends/__init__.py29
-rwxr-xr-xbackends/audiocpp.py19
-rw-r--r--backends/common.py25
-rwxr-xr-xbackends/faster.py17
-rw-r--r--backends/qwen.py17
5 files changed, 73 insertions, 34 deletions
diff --git a/backends/__init__.py b/backends/__init__.py
index 9203143..629551e 100644
--- a/backends/__init__.py
+++ b/backends/__init__.py
@@ -5,11 +5,12 @@ its setup wizard, its status detection, and the launch command it prints
once configured. This package aggregates them into a single registry so
``audiobook.py``'s TUI hub and future tools can iterate backends without
hardcoding their names: ``backends.detect_all()`` reports which are set
-up, and ``backends.REGISTRY`` drives the hub's setup/modify menus.
+up (and whether their server is currently running), and
+``backends.REGISTRY`` drives the hub's setup/configure menus.
Adding a backend: create ``backends/<name>.py`` exposing
``detect() -> BackendStatus``, ``run_tui() -> int`` and
-``modify_actions: list[ModifyAction]``, then append a ``BackendInfo`` in
+``configure_actions: list[ConfigureAction]``, then append a ``BackendInfo`` in
``_build_registry`` below. ``audiobook.py`` and the hub pick it up
automatically.
"""
@@ -25,13 +26,17 @@ class BackendStatus:
INSTALLED means the backend itself is present (a cloned + built
checkout, or a pip package). CONFIGURED means the supporting files are
in place (a server.json / voices.json and a converter/config.py that
- points at the right port). DETAILS are short status lines for the hub.
- LAUNCH_HINT is the exact command the user runs to start the server.
+ points at the right port). RUNNING means an external server is
+ currently accepting connections on the configured port (probed by
+ ``backends.common.server_running``). DETAILS are short status lines for
+ the hub. LAUNCH_HINT is the exact command the user runs to start the
+ server.
"""
key: str
label: str
installed: bool
configured: bool
+ running: bool = False
details: List[str] = field(default_factory=list)
launch_hint: str = ""
@@ -42,20 +47,20 @@ class BackendStatus:
@dataclass
-class ModifyAction:
- """A per-backend "modify" menu entry (e.g. "New server.json")."""
+class ConfigureAction:
+ """A per-backend "configure" menu entry (e.g. "New server.json")."""
label: str
run: Callable[[], int]
@dataclass
class BackendInfo:
- """One registry entry: identity, detector, setup wizard, modify menu."""
+ """One registry entry: identity, detector, setup wizard, configure menu."""
key: str
label: str
detect: Callable[[], BackendStatus]
setup_tui: Callable[[], int]
- modify_actions: List[ModifyAction] = field(default_factory=list)
+ configure_actions: List[ConfigureAction] = field(default_factory=list)
REGISTRY: List[BackendInfo] = []
@@ -73,21 +78,21 @@ def _build_registry() -> None:
label="audio.cpp",
detect=audiocpp.detect,
setup_tui=audiocpp.run_tui,
- modify_actions=audiocpp.modify_actions,
+ configure_actions=audiocpp.configure_actions,
))
REGISTRY.append(BackendInfo(
key="qwen",
- label="Qwen3-TTS (demo server)",
+ label="qwen-tts",
detect=qwen.detect,
setup_tui=qwen.run_tui,
- modify_actions=qwen.modify_actions,
+ configure_actions=qwen.configure_actions,
))
REGISTRY.append(BackendInfo(
key="faster",
label="faster-qwen3-tts",
detect=faster.detect,
setup_tui=faster.run_tui,
- modify_actions=faster.modify_actions,
+ configure_actions=faster.configure_actions,
))
for info in REGISTRY:
_BY_KEY[info.key] = info
diff --git a/backends/audiocpp.py b/backends/audiocpp.py
index b401366..57636a7 100755
--- a/backends/audiocpp.py
+++ b/backends/audiocpp.py
@@ -41,8 +41,8 @@ from typing import Callable, Dict, List, Optional, Set, Tuple
# Allow running directly (python backends/audiocpp.py) from any cwd.
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
-import tui
-from backends import BackendStatus, ModifyAction
+from ui import tui
+from backends import BackendStatus, ConfigureAction
from backends import common
from backends.common import (
CONFIG_PATH,
@@ -1645,11 +1645,14 @@ def build_parser() -> argparse.ArgumentParser:
def detect() -> BackendStatus:
"""Detect how far audio.cpp is set up, plus the command to start it."""
checkout = find_local_checkout()
+ # Probe the server first: it may be running externally even with no
+ # local checkout, and the status table should show that.
+ running = common.server_running(config.AUDIOCPP_API_URL)
details: List[str] = []
launch = ""
if checkout is None:
return BackendStatus("audiocpp", "audio.cpp", installed=False,
- configured=False,
+ configured=False, running=running,
details=["not cloned — run setup to clone "
"./audio.cpp"])
details.append(f"checkout: {checkout}")
@@ -1670,13 +1673,13 @@ def detect() -> BackendStatus:
else:
details.append("no server.json — run setup to configure models")
return BackendStatus("audiocpp", "audio.cpp", installed=built,
- configured=configured, details=details,
- launch_hint=launch)
+ configured=configured, running=running,
+ details=details, launch_hint=launch)
-modify_actions: List[ModifyAction] = [
- ModifyAction("Reconfigure audio.cpp (models, voices, server.json)",
- run_tui),
+configure_actions: List[ConfigureAction] = [
+ ConfigureAction("Reconfigure audio.cpp (models, voices, server.json)",
+ run_tui),
]
diff --git a/backends/common.py b/backends/common.py
index 2c6437f..d707fdc 100644
--- a/backends/common.py
+++ b/backends/common.py
@@ -141,6 +141,31 @@ def url_with_port(url: str, port: int) -> str:
(parts.scheme or "http", f"{host}:{port}", parts.path, "", ""))
+def server_running(url: str, timeout: float = 0.3) -> bool:
+ """True when something accepts TCP connections at URL's host:port.
+
+ A protocol-agnostic socket connect: an HTTP TTS server that is up will
+ accept the connection (we do not need to speak HTTP to know it is
+ listening). Returns False on any parse or connection error, so a
+ misconfigured URL never blocks the hub — it just reports the backend
+ as not running. Used by each backend's ``detect()`` to set
+ ``BackendStatus.running``.
+ """
+ import socket
+ try:
+ parts = urllib.parse.urlsplit(url)
+ host = parts.hostname or "127.0.0.1"
+ port = parts.port or (443 if (parts.scheme or "http") == "https"
+ else 80)
+ except ValueError:
+ return False
+ try:
+ with socket.create_connection((host, port), timeout=timeout):
+ return True
+ except OSError:
+ return False
+
+
def update_config_value(key: str, value: str,
config_path: Optional[Path] = None) -> bool:
"""Rewrite a ``KEY = "value"`` line in converter/config.py.
diff --git a/backends/faster.py b/backends/faster.py
index 4a2cc6f..71be050 100755
--- a/backends/faster.py
+++ b/backends/faster.py
@@ -25,8 +25,8 @@ from typing import List, Optional
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
-import tui
-from backends import BackendStatus, ModifyAction
+from ui import tui
+from backends import BackendStatus, ConfigureAction
from backends import common
from backends.common import TTS_ROOT, find_wav_files, normalize_dir_arg
from converter import config
@@ -335,6 +335,7 @@ def detect() -> BackendStatus:
cloned = _is_cloned()
voices_json = _checkout() / "voices.json"
configured = installed and cloned and voices_json.exists()
+ running = common.server_running(config.FASTER_API_URL)
details: List[str] = []
details.append("pip: installed" if installed else
"not installed — run setup to pip install")
@@ -348,12 +349,12 @@ def detect() -> BackendStatus:
f"--voices {voices_json} --port {_config_port()}")
return BackendStatus("faster", "faster-qwen3-tts",
installed=installed and cloned,
- configured=configured, details=details,
- launch_hint=launch)
+ configured=configured, running=running,
+ details=details, launch_hint=launch)
def _run_voices_only_tui() -> int:
- """Rebuild voices.json via the TUI (the "modify" action).
+ """Rebuild voices.json via the TUI (the "configure" action).
Runs the same wizard but skips the pip/clone prerequisites so it goes
straight to picking the .wav directory and writing voices.json.
@@ -364,9 +365,9 @@ def _run_voices_only_tui() -> int:
return run_tui(args)
-modify_actions: List[ModifyAction] = [
- ModifyAction("Rebuild voices.json", _run_voices_only_tui),
- ModifyAction("Reconfigure faster-qwen3-tts", run_tui),
+configure_actions: List[ConfigureAction] = [
+ ConfigureAction("Rebuild voices.json", _run_voices_only_tui),
+ ConfigureAction("Reconfigure faster-qwen3-tts", run_tui),
]
diff --git a/backends/qwen.py b/backends/qwen.py
index 48e1804..60f3bb6 100644
--- a/backends/qwen.py
+++ b/backends/qwen.py
@@ -22,8 +22,8 @@ from typing import List, Optional
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
-import tui
-from backends import BackendStatus, ModifyAction
+from ui import tui
+from backends import BackendStatus, ConfigureAction
from backends import common
from converter import config
@@ -209,6 +209,10 @@ def detect() -> BackendStatus:
installed = _is_installed()
custom_port = _config_port(config.QWEN_API_URL, DEFAULT_CUSTOM_PORT)
clone_port = _config_port(config.CLONE_API_URL, DEFAULT_CLONE_PORT)
+ # Running when either server is up — CustomVoice (speaker mode) or Base
+ # (voice clone) each suffice for a conversion on their own.
+ running = (common.server_running(config.QWEN_API_URL)
+ or common.server_running(config.CLONE_API_URL))
details: List[str] = []
details.append("pip: installed" if installed else
"not installed — run setup to pip install qwen-tts")
@@ -218,13 +222,14 @@ def detect() -> BackendStatus:
launch = (f"qwen-tts-demo {QWEN_CUSTOMVOICE_MODEL} --ip 127.0.0.1 "
f"--port {custom_port} ; qwen-tts-demo {QWEN_BASE_MODEL} "
f"--ip 127.0.0.1 --port {clone_port}")
- return BackendStatus("qwen", "Qwen3-TTS (demo server)",
+ return BackendStatus("qwen", "qwen-tts",
installed=installed, configured=installed,
- details=details, launch_hint=launch)
+ running=running, details=details,
+ launch_hint=launch)
-modify_actions: List[ModifyAction] = [
- ModifyAction("Reconfigure Qwen3-TTS (ports/speaker)", run_tui),
+configure_actions: List[ConfigureAction] = [
+ ConfigureAction("Reconfigure qwen-tts (ports/speaker)", run_tui),
]