diff options
| author | historia <historiavg@proton.me> | 2026-08-25 13:38:33 -0400 |
|---|---|---|
| committer | historia <historiavg@proton.me> | 2026-08-25 13:38:33 -0400 |
| commit | 0badc06550ed2e46c7c4b9f83db755737ffc0412 (patch) | |
| tree | 139adf75a73f6673a8cad5cb8fb4466364140185 /app | |
| parent | d4dbc1a158d1dd6babcba7333a4ed7d719b65d3e (diff) | |
| download | tts-audiobook-generator-0badc06550ed2e46c7c4b9f83db755737ffc0412.tar.gz | |
fix: detect if all audio.cpp models already downloaded
Diffstat (limited to 'app')
| -rwxr-xr-x | app/backends/audiocpp.py | 31 | ||||
| -rw-r--r-- | app/tests/test_backends_audiocpp.py | 72 |
2 files changed, 91 insertions, 12 deletions
diff --git a/app/backends/audiocpp.py b/app/backends/audiocpp.py index 0b82a4e..b950ac6 100755 --- a/app/backends/audiocpp.py +++ b/app/backends/audiocpp.py @@ -859,17 +859,23 @@ def _manager_supports_progress(manager: Path) -> bool: def _decide_download(audiocpp_dir: Path, + model_entries: List[dict], confirm: Callable[[str, bool], bool]) -> bool: """Ask whether to download the selected models now. 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. + 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. """ 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) @@ -1319,7 +1325,8 @@ def _wizard(stdscr, args: argparse.Namespace, parser: argparse.ArgumentParser def screen_download(): # Automatic model download (or print the install commands). try: - s["download"] = _decide_download(s["audiocpp_dir"], ask_confirm) + s["download"] = _decide_download( + s["audiocpp_dir"], s["model_entries"], ask_confirm) except _GoBack: return tui.Wizard.BACK return _finalize() @@ -1428,6 +1435,26 @@ def _model_path_present(path: Path) -> bool: return False +def _all_models_present(audiocpp_dir: Path, model_entries: List[dict]) -> bool: + """True when every selected model entry's path already holds files on disk. + + Paths resolve against AUDIOCPP_DIR (where model_manager_v2.py installs + them), honoring absolute paths. Used by the wizard to skip the + "Automatically download the selected models" prompt when nothing is + actually missing. An empty selection is treated as not-present. + """ + if not model_entries: + return False + for entry in model_entries: + rel = entry.get("path") + if not isinstance(rel, str) or not rel: + return False + path = Path(rel) if Path(rel).is_absolute() else audiocpp_dir / rel + if not _model_path_present(path): + return False + return True + + def missing_model_entries(server_json: Path) -> List[dict]: """Return the server.json model entries whose files are not on disk. diff --git a/app/tests/test_backends_audiocpp.py b/app/tests/test_backends_audiocpp.py index be8fead..7704ff4 100644 --- a/app/tests/test_backends_audiocpp.py +++ b/app/tests/test_backends_audiocpp.py @@ -579,7 +579,8 @@ class InstallModelsTests(unittest.TestCase): def test_declined_download_prints_commands_deduped(self): buf = io.StringIO() with redirect_stdout(buf), \ - patch.object(make_server.subprocess, "run") as run: + patch.object(make_server.common, + "run_console_subprocess") as run: make_server._install_models(self.checkout, self.guidance, download=False) out = buf.getvalue() @@ -588,8 +589,8 @@ class InstallModelsTests(unittest.TestCase): run.assert_not_called() def test_accepted_download_runs_each_command(self): - with patch.object(make_server.subprocess, "run", - return_value=MagicMock(returncode=0)) as run: + with patch.object(make_server.common, + "run_console_subprocess", return_value=0) as run: make_server._install_models(self.checkout, self.guidance, download=True) self.assertEqual(run.call_count, 2) @@ -607,17 +608,18 @@ class InstallModelsTests(unittest.TestCase): self.manager.unlink() buf = io.StringIO() with redirect_stdout(buf), \ - patch.object(make_server.subprocess, "run") as run: + patch.object(make_server.common, + "run_console_subprocess") as run: make_server._install_models(self.checkout, self.guidance, download=True) self.assertIn("install higgs_audio_tts_4b_q8_0", buf.getvalue()) run.assert_not_called() def test_failed_install_reports_warning_and_continues(self): - results = iter([MagicMock(returncode=1), MagicMock(returncode=0)]) + results = iter([1, 0]) buf = io.StringIO() with redirect_stdout(buf), \ - patch.object(make_server.subprocess, "run", + patch.object(make_server.common, "run_console_subprocess", side_effect=lambda *a, **k: next(results)) as run: make_server._install_models(self.checkout, self.guidance, download=True) @@ -627,19 +629,69 @@ class InstallModelsTests(unittest.TestCase): def test_decide_download_skips_prompt_without_manager(self): self.manager.unlink() confirm = MagicMock() - self.assertFalse(make_server._decide_download(self.checkout, confirm)) + self.assertFalse(make_server._decide_download(self.checkout, [], confirm)) confirm.assert_not_called() def test_decide_download_asks_when_manager_present(self): confirm = MagicMock(return_value=True) - self.assertTrue(make_server._decide_download(self.checkout, confirm)) + self.assertTrue(make_server._decide_download(self.checkout, [], confirm)) confirm.assert_called_once() def test_decide_download_defaults_to_yes(self): confirm = MagicMock(return_value=True) - make_server._decide_download(self.checkout, confirm) + make_server._decide_download(self.checkout, [], confirm) self.assertIs(confirm.call_args[0][1], True) + def test_decide_download_skips_prompt_when_all_models_present(self): + target = self.checkout / "models" / "higgs" + target.mkdir(parents=True) + (target / "model.gguf").write_bytes(b"x") + confirm = MagicMock() + self.assertFalse(make_server._decide_download( + self.checkout, [{"path": "models/higgs"}], confirm)) + confirm.assert_not_called() + + def test_decide_download_prompts_when_a_model_is_missing(self): + target = self.checkout / "models" / "higgs" + target.mkdir(parents=True) + (target / "model.gguf").write_bytes(b"x") + confirm = MagicMock(return_value=True) + self.assertTrue(make_server._decide_download( + self.checkout, + [{"path": "models/higgs"}, {"path": "models/absent"}], + confirm)) + confirm.assert_called_once() + + def test_all_models_present_true_when_all_paths_hold_files(self): + target = self.checkout / "models" / "higgs" + target.mkdir(parents=True) + (target / "model.gguf").write_bytes(b"x") + self.assertTrue(make_server._all_models_present( + self.checkout, [{"path": "models/higgs"}])) + + def test_all_models_present_false_when_one_missing(self): + target = self.checkout / "models" / "higgs" + target.mkdir(parents=True) + (target / "model.gguf").write_bytes(b"x") + self.assertFalse(make_server._all_models_present( + self.checkout, + [{"path": "models/higgs"}, {"path": "models/absent"}])) + + def test_all_models_present_false_for_empty_selection(self): + self.assertFalse(make_server._all_models_present(self.checkout, [])) + + def test_all_models_present_honors_absolute_paths(self): + target = self.checkout / "models" / "higgs" + target.mkdir(parents=True) + (target / "model.gguf").write_bytes(b"x") + self.assertTrue(make_server._all_models_present( + self.checkout, [{"path": str(target)}])) + + def test_all_models_present_false_for_empty_dir(self): + (self.checkout / "models" / "higgs").mkdir(parents=True) + self.assertFalse(make_server._all_models_present( + self.checkout, [{"path": "models/higgs"}])) + class TranscribeWavDirTests(unittest.TestCase): def setUp(self): @@ -1621,7 +1673,7 @@ class DeleteModelFilesTests(unittest.TestCase): self.assertFalse(target.exists()) -class InstallModelsTests(unittest.TestCase): +class InstallModelsApiTests(unittest.TestCase): """install_models: runs the install helper with download=True.""" def test_downloads_delegating_to_install_models(self): |
