aboutsummaryrefslogtreecommitdiff
path: root/app/backends/common.py
diff options
context:
space:
mode:
authorhistoria <historiavg@proton.me>2026-08-26 00:21:28 -0400
committerhistoria <historiavg@proton.me>2026-08-26 00:21:28 -0400
commit7cf7d2f7849936e3c84ee431433c2e8bcb6706ac (patch)
tree80d753cbd35929187ffc717a45f7bc3e9cbdbb1e /app/backends/common.py
parent4c3e78c39c81d2997e88b84e1b5a25b244e870e4 (diff)
downloadtts-audiobook-generator-7cf7d2f7849936e3c84ee431433c2e8bcb6706ac.tar.gz
fix: harden server start/stop guards
Diffstat (limited to 'app/backends/common.py')
-rw-r--r--app/backends/common.py68
1 files changed, 43 insertions, 25 deletions
diff --git a/app/backends/common.py b/app/backends/common.py
index 10b4ccf..9b6232a 100644
--- a/app/backends/common.py
+++ b/app/backends/common.py
@@ -102,12 +102,19 @@ def resolve_wav_dir_arg(value: str) -> Path:
def find_wav_files(input_dir: Path) -> List[Path]:
- """Return the .wav files in INPUT_DIR, sorted alphabetically by name."""
- return sorted(
- (path for path in input_dir.iterdir()
- if path.is_file() and path.suffix.lower() == ".wav"),
- key=lambda path: path.name.lower(),
- )
+ """Return the .wav files in INPUT_DIR, sorted alphabetically by name.
+
+ A missing or unreadable directory yields [] so callers can treat it
+ like an empty directory (matching ``count_wavs``).
+ """
+ try:
+ return sorted(
+ (path for path in input_dir.iterdir()
+ if path.is_file() and path.suffix.lower() == ".wav"),
+ key=lambda path: path.name.lower(),
+ )
+ except OSError:
+ return []
def count_wavs(directory: Path) -> int:
@@ -248,29 +255,38 @@ def server_running(url: str, timeout: float = 0.3) -> bool:
return False
-def update_config_value(key: str, value: str,
+def update_config_value(key: str, value,
config_path: Optional[Path] = None) -> bool:
- """Rewrite a ``KEY = "value"`` line in app/converter/config.py.
-
- Only the quoted literal is replaced; surrounding lines and the trailing
- comment are preserved. Returns True when the file was changed. Used by
- the qwen and faster wizards to keep their API URL / voice / speaker
- settings in sync with the converter.
+ """Set ``KEY`` to VALUE in app/converter/config.py and in memory.
+
+ Only the value of the named assignment changes: indentation and any
+ trailing comment are preserved. Strings render double-quoted; other
+ literals (ints, booleans) render bare. After a successful write (or
+ when the file already holds VALUE) the new value is mirrored onto the
+ imported ``converter.config`` module, so a wizard's change takes
+ effect immediately instead of only after the next process start.
+ Returns True when the file now holds VALUE, False when it could not
+ be read or written (or KEY has no line in it).
"""
path = Path(config_path) if config_path is not None else CONFIG_PATH
+ rendered = f'"{value}"' if isinstance(value, str) else str(value)
try:
text = path.read_text(encoding="utf-8")
+ match = re.search(
+ rf'(?m)^(\s*{re.escape(key)}\s*=\s*)("[^"]*"|\S+)(\s*(?:#.*)?)$',
+ text)
+ if match is None:
+ return False
+ if match.group(2) != rendered:
+ text = text[:match.start(2)] + rendered + text[match.end(2):]
+ path.write_text(text, encoding="utf-8")
except OSError:
return False
- match = re.search(r'(?m)^(\s*' + re.escape(key) + r'\s*=\s*")([^"]*)(")',
- text)
- if not match or match.group(2) == value:
- return False
- text = text[:match.start(2)] + value + text[match.end(2):]
- try:
- path.write_text(text, encoding="utf-8")
- except OSError:
- return False
+ # Local import: this module must stay importable before the venv
+ # exists (backends.envs bootstraps from it), and converter.config is
+ # stdlib-only constants, safe to load whenever a wizard runs.
+ from converter import config as _config
+ setattr(_config, key, value)
return True
@@ -458,16 +474,18 @@ def git_clone(url: str, target: Path, *, emit=None, cancel=None) -> int:
emit=emit, cancel=cancel)
-def pip_install(packages: List[str]) -> int:
+def pip_install(packages: List[str], *, emit=None, cancel=None) -> int:
"""pip install PACKAGES into the managed venv (``envs/tts``). Returns exit code.
Delegates to ``backends.envs.pip_install`` so backend TTS packages are
installed alongside the app requirements in the tool-managed environment
rather than into whatever interpreter happens to be running the wizard.
- The import is local to avoid a circular import (envs imports this module).
+ With EMIT given (the in-TUI task view) pip runs with its output streamed
+ into EMIT; CANCEL aborts it. The import is local to avoid a circular
+ import (envs imports this module).
"""
from backends import envs
- return envs.pip_install(packages)
+ return envs.pip_install(packages, emit=emit, cancel=cancel)
def pip_uninstall(packages: List[str], *, emit=None) -> int: