aboutsummaryrefslogtreecommitdiff
path: root/app/backends/audiocpp/models.py
diff options
context:
space:
mode:
Diffstat (limited to 'app/backends/audiocpp/models.py')
-rw-r--r--app/backends/audiocpp/models.py128
1 files changed, 93 insertions, 35 deletions
diff --git a/app/backends/audiocpp/models.py b/app/backends/audiocpp/models.py
index 4e6b8bb..75bc06a 100644
--- a/app/backends/audiocpp/models.py
+++ b/app/backends/audiocpp/models.py
@@ -6,22 +6,79 @@ import shutil
import sys
import tempfile
from pathlib import Path
-from typing import Callable, Dict, List, Optional, Set, Tuple
+from typing import Dict, List, Optional, Set, Tuple
from backends import common
from . import catalog as _catalog
+def _installed_display_names(audiocpp_dir: Path,
+ model_entries: Optional[List[dict]],
+ install_guidance: List[Tuple[str, str]]
+ ) -> Set[str]:
+ """Display names from INSTALL_GUIDANCE whose model files are on disk.
+
+ MODEL_ENTRIES and INSTALL_GUIDANCE are built in lockstep by
+ ``_build_entries`` (one guidance pair per entry), so the pairs resolve
+ positionally: each entry's ``path`` is checked against the checkout
+ exactly like ``_all_models_present`` resolves it. Returns an empty set
+ when ENTRIES is None or does not line up with the guidance (no
+ filtering — every model counts as not installed).
+ """
+ if model_entries is None or len(model_entries) != len(install_guidance):
+ return set()
+ installed: Set[str] = set()
+ for entry, (name, _install_id) in zip(model_entries, install_guidance):
+ rel = entry.get("path")
+ if not isinstance(rel, str) or not rel:
+ continue
+ path = Path(rel) if Path(rel).is_absolute() else audiocpp_dir / rel
+ if _model_path_present(path):
+ installed.add(name)
+ return installed
+
+
+def _split_pending_and_installed(
+ install_guidance: List[Tuple[str, str]],
+ installed_names: Set[str]) -> Tuple[List[Tuple[str, str]], List[str]]:
+ """Partition guidance into (pending installs, installed display names).
+
+ PENDING keeps only models whose display name is not INSTALLED_NAMES,
+ de-duped by install id (the same package may host several entries) in
+ first-occurrence order. INSTALLED lists each installed display name
+ once, also in first-occurrence order.
+ """
+ seen: Set[str] = set()
+ pending: List[Tuple[str, str]] = []
+ noted: List[str] = []
+ for name, install_id in install_guidance:
+ if name in installed_names:
+ if name not in noted:
+ noted.append(name)
+ continue
+ if install_id in seen:
+ continue
+ seen.add(install_id)
+ pending.append((name, install_id))
+ return pending, noted
+
+
def _install_models(audiocpp_dir: Path,
install_guidance: List[Tuple[str, str]],
- download: bool, emit=None, cancel=None) -> int:
- """Print and optionally run the model install commands.
-
- One ``python <manager> install <id>`` command per hosted model (de-duped
- by install id). When DOWNLOAD is True each command is run in the audio.cpp
+ download: bool, emit=None, cancel=None,
+ model_entries: Optional[List[dict]] = None) -> int:
+ """Report and optionally run the model install commands.
+
+ When MODEL_ENTRIES (built in lockstep with INSTALL_GUIDANCE by
+ ``_build_entries``) is given, models already on disk are reported as
+ installed and never re-downloaded or printed as commands; when every
+ selected model is present nothing runs at all. The remaining models
+ get one ``python <manager> install <id>`` command each (de-duped by
+ install id). When DOWNLOAD is True each command is run in the audio.cpp
checkout via ``subprocess`` so the models are downloaded automatically;
a failing install is reported as a warning and does not abort the
remaining downloads. When DOWNLOAD is False (or the model manager is
- missing) the commands are only printed, copy-pasteable as before.
+ missing) the commands are only printed after a note that setup downloads
+ them automatically — copy-pasteable for a manual install.
With EMIT given (the in-TUI task view) each download streams its output
to EMIT and — when the checkout's ``model_manager_v2.py`` supports it —
@@ -31,13 +88,13 @@ def _install_models(audiocpp_dir: Path,
Returns 0 when every command succeeded (or nothing needed running),
130 when cancelled, 1 when any download failed.
"""
+ if not install_guidance:
+ return 0
manager = audiocpp_dir / "tools" / "model_manager_v2.py"
- seen: Set[str] = set()
- install_ids: List[str] = []
- for _, install_id in install_guidance:
- if install_id not in seen:
- seen.add(install_id)
- install_ids.append(install_id)
+ installed_names = _installed_display_names(
+ audiocpp_dir, model_entries, install_guidance)
+ pending, installed_noted = _split_pending_and_installed(
+ install_guidance, installed_names)
supports_progress = emit is not None and _manager_supports_progress(manager)
@@ -46,12 +103,21 @@ def _install_models(audiocpp_dir: Path,
"instead of running them")
download = False
+ for name in installed_noted:
+ print(f"[OK] {name} is already installed.")
+ if not pending:
+ print("[OK] All selected models are already installed.")
+ return 0
+
+ if not download:
+ print("[INFO] Models are downloaded automatically by this tool's "
+ "setup — to download them manually instead, run:")
+ for _, install_id in pending:
+ print(f"python {manager} install {install_id}")
+ return 0
+
failed = False
- for install_id in install_ids:
- command = f"python {manager} install {install_id}"
- if not download:
- print(command)
- continue
+ for _, install_id in pending:
print(f"[INFO] Downloading {install_id}...")
argv = [sys.executable, str(manager), "install", install_id]
cancel_file: Optional[Path] = None
@@ -69,7 +135,8 @@ def _install_models(audiocpp_dir: Path,
argv, cwd=str(audiocpp_dir), emit=emit, cancel=cancel,
on_cancel=on_cancel)
except OSError as exc:
- print(f"[WARNING] Could not run {command}: {exc}")
+ print(f"[WARNING] Could not run python {manager} install "
+ f"{install_id}: {exc}")
rc = 1
finally:
if cancel_file is not None:
@@ -101,27 +168,18 @@ def _manager_supports_progress(manager: Path) -> bool:
return "AUDIOCPP_PROGRESS" in text and "--cancel-file" in text
-def _decide_download(audiocpp_dir: Path,
- model_entries: List[dict],
- confirm: Callable[[str, bool], bool]) -> bool:
- """Ask whether to download the selected models now.
+def download_applicable(audiocpp_dir: Path, model_entries: List[dict]) -> bool:
+ """True when the wizard's "download models automatically?" row applies.
- CONFIRM asks the yes/no question (ask_bool for the line prompts, a TUI
- confirm for the wizard). When the audio.cpp model manager is missing the
- prompt is skipped and False is returned, so the install commands are only
- printed rather than offered to run. The prompt is also skipped (False)
- when every selected model is already on disk (see ``_all_models_present``),
- so an already-configured checkout is not asked to re-download models it
- already has.
+ The audio.cpp model manager must be present (otherwise the install
+ commands can only be printed), and at least one selected model must be
+ missing from disk (see ``_all_models_present``), so an already-configured
+ checkout is not asked to re-download models it already has.
"""
manager = audiocpp_dir / "tools" / "model_manager_v2.py"
if not manager.is_file():
return False
- if _all_models_present(audiocpp_dir, model_entries):
- return False
- return confirm(
- "Automatically download the selected models with model_manager_v2.py "
- "now?", True)
+ return not _all_models_present(audiocpp_dir, model_entries)
def _build_tree_families(catalog: List[dict]) -> List[dict]: