aboutsummaryrefslogtreecommitdiff
path: root/app/backends/qwen.py
diff options
context:
space:
mode:
authorhistoria <historiavg@proton.me>2026-08-27 00:04:16 -0400
committerhistoria <historiavg@proton.me>2026-08-27 00:04:16 -0400
commit5b98993b13dafe9a85495e4c68ad9b42863ef2bf (patch)
tree57db1566deb5351ec6bfb8c70de4126bbc5fc2da /app/backends/qwen.py
parentf18f421d9180ae0e3bff9496b1fdaf53d3624a75 (diff)
downloadtts-audiobook-generator-5b98993b13dafe9a85495e4c68ad9b42863ef2bf.tar.gz
feat: separate venvs for qwen-tts and faster, manage (un)installs
Diffstat (limited to 'app/backends/qwen.py')
-rw-r--r--app/backends/qwen.py43
1 files changed, 25 insertions, 18 deletions
diff --git a/app/backends/qwen.py b/app/backends/qwen.py
index 3309c69..b2a1df4 100644
--- a/app/backends/qwen.py
+++ b/app/backends/qwen.py
@@ -4,8 +4,9 @@
qwen-tts is a pip package providing the ``qwen-tts-demo`` server, which hosts
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 the managed venv. There are no
-questions to ask — the port and which model to run live in
+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.
@@ -46,6 +47,9 @@ from converter.clients import QWEN3_TTS_SPEAKERS
from ui import taskview, tui
QWEN_PIP_PKG = "qwen-tts"
+# The dedicated venv qwen-tts is installed into (never the app env or the
+# faster backend's).
+QWEN_ENV = envs.QWEN_ENV_DIR
DEFAULT_PORT = 7860
# The models a single demo server can host, by config.QWEN_MODEL name.
@@ -161,9 +165,9 @@ def delete_model_weights(models: Optional[List[str]] = None) -> int:
def _is_installed() -> bool:
- if envs.env_script("qwen-tts-demo").is_file():
+ if envs.env_script("qwen-tts-demo", QWEN_ENV).is_file():
return True
- return envs.module_available("qwen_tts")
+ return envs.module_available("qwen_tts", QWEN_ENV)
def _config_port(url: str, fallback: int) -> int:
@@ -218,7 +222,8 @@ def _execute_steps(settings: dict) -> List[taskview.TaskStep]:
return steps
def install(emit, cancel):
- rc = common.pip_install([QWEN_PIP_PKG], emit=emit, cancel=cancel)
+ rc = common.pip_install([QWEN_PIP_PKG], emit=emit, cancel=cancel,
+ env_dir=QWEN_ENV)
if rc != 0:
print(f"[WARNING] pip install failed (exit {rc}); install "
f"{QWEN_PIP_PKG} manually")
@@ -268,7 +273,7 @@ def _collect_from_flags(args: argparse.Namespace,
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description="Set up the Qwen3-TTS demo backend: pip install "
- "qwen-tts into the managed venv.")
+ "qwen-tts into its managed venv (app/envs/qwen).")
parser.add_argument("--skip-install", action="store_true",
help="Do not pip install qwen-tts")
return parser
@@ -279,7 +284,7 @@ def _build_spec(model: str) -> ServerSpec:
url = config.QWEN_API_URL
return ServerSpec(
"qwen", url,
- [str(envs.env_script("qwen-tts-demo")), MODEL_REPOS[model],
+ [str(envs.env_script("qwen-tts-demo", QWEN_ENV)), MODEL_REPOS[model],
"--ip", "127.0.0.1",
"--port", str(_config_port(url, DEFAULT_PORT))],
identity=desired_identity(model))
@@ -363,9 +368,9 @@ def _detect_remote(managed: bool = False):
def _hf_download_prefix() -> Optional[List[str]]:
- """The venv's hf CLI argv prefix (None when neither script is present)."""
+ """The qwen env's hf CLI argv prefix (None when neither script is present)."""
for name in ("hf", "huggingface-cli"):
- candidate = envs.env_script(name)
+ candidate = envs.env_script(name, QWEN_ENV)
if candidate.is_file():
return [str(candidate)]
return None
@@ -381,7 +386,7 @@ def install_model(model: str, *, emit=None, cancel=None) -> int:
"""
prefix = _hf_download_prefix()
if prefix is None:
- print("[ERROR] No hf CLI found in the managed venv; pip install "
+ print("[ERROR] No hf CLI found in the qwen venv; pip install "
f"{QWEN_PIP_PKG} first")
return 1
repo = MODEL_REPOS[model]
@@ -415,12 +420,14 @@ def uninstall(*, emit=None, cancel=None) -> int:
then delete every downloaded model.
qwen-tts is a pip package (``qwen_tts`` + the ``qwen-tts-demo`` script)
- installed into the managed venv, so uninstalling it removes the backend.
- Any server this tool started is stopped first (best-effort). The three
- models' weight snapshots — multi-GB directories lazily fetched into the
- HuggingFace cache (~/.cache/huggingface/hub) — are deleted too, matching
- the hub's confirmation dialog; only those three directories are removed,
- never the shared cache itself.
+ installed into its own managed venv (``app/envs/qwen``, QWEN_ENV), so
+ uninstalling it removes the backend — the app venv and the faster
+ backend's env are never touched. Any server this tool started is stopped
+ first (best-effort). The three models' weight snapshots — multi-GB
+ directories lazily fetched into the HuggingFace cache
+ (~/.cache/huggingface/hub) — are deleted too, matching the hub's
+ confirmation dialog; only those three directories are removed, never the
+ shared cache itself.
With EMIT given (the in-TUI task view) pip runs piped, streaming into
EMIT, so its output never touches the terminal behind curses. CANCEL is
@@ -436,10 +443,10 @@ def uninstall(*, emit=None, cancel=None) -> int:
servers.stop("qwen")
if common.cancel_requested(cancel):
return 130
- rc = common.pip_uninstall([QWEN_PIP_PKG], emit=emit)
+ rc = common.pip_uninstall([QWEN_PIP_PKG], emit=emit, env_dir=QWEN_ENV)
if rc != 0:
print(f"[WARNING] pip uninstall failed (exit {rc}); remove "
- f"{QWEN_PIP_PKG} from the managed venv manually")
+ f"{QWEN_PIP_PKG} from {QWEN_ENV} manually")
else:
print(f"[OK] {QWEN_PIP_PKG} removed.")
# Weights go even when the pip step failed: the package can be