diff options
| author | historia <historiavg@proton.me> | 2026-08-25 20:48:51 -0400 |
|---|---|---|
| committer | historia <historiavg@proton.me> | 2026-08-25 20:48:51 -0400 |
| commit | d453d88d1d40c5e54af07ceb89c8c18d09cfdb0e (patch) | |
| tree | aaf99573d0380c57174ca1ec5437e09e26fece37 | |
| parent | f3f7bb98e4bc969a17fba71294177819fbbbd070 (diff) | |
| download | tts-audiobook-generator-d453d88d1d40c5e54af07ceb89c8c18d09cfdb0e.tar.gz | |
feat: default save and exit server setting
| -rw-r--r-- | app/converter/config.py | 4 | ||||
| -rw-r--r-- | app/tests/test_hub.py | 33 | ||||
| -rw-r--r-- | app/ui/hub.py | 9 | ||||
| -rw-r--r-- | app/ui/runview.py | 6 |
4 files changed, 42 insertions, 10 deletions
diff --git a/app/converter/config.py b/app/converter/config.py index 3caa1ab..3727885 100644 --- a/app/converter/config.py +++ b/app/converter/config.py @@ -10,6 +10,10 @@ HEARTBEAT_INTERVAL_SECONDS = 30 # Print "still working" in console logs every N # Words per TTS generation request (client-side chunking). CHUNK_SIZE = 250 +# Default for "Stop server and exit" on the Generate audiobooks form +# (TUI Settings menu: "Default stop server and exit"). +STOP_SERVER_AND_EXIT = False + # Default TTS backend. # audiocpp: audiocpp_server # qwen: qwen-tts-demo diff --git a/app/tests/test_hub.py b/app/tests/test_hub.py index 794faf2..982df1d 100644 --- a/app/tests/test_hub.py +++ b/app/tests/test_hub.py @@ -1136,6 +1136,19 @@ class ConvertFlowTests(unittest.TestCase): self.assertIsNotNone(cmd) self.assertIs(self.nav, hub.tui.Wizard.BACK) + def test_settings_default_feeds_the_stop_and_exit_toggle(self): + # The Settings menu's "Default stop server and exit" value seeds the + # generate-form toggle (and so the run's stop-and-exit behavior). + st = self._remote("faster", "faster-qwen3-tts", + url="http://10.0.0.5:8000") + with patch.object(hub.config, "STOP_SERVER_AND_EXIT", False): + self._answer_form(backend="faster-remote", + faster_voice="obama", stop_and_exit=False) + cmd = self._convert(None, [st]) + self.assertIsNotNone(cmd) + self.assertFalse(self._field("stop_and_exit")["value"]) + self.assertFalse(cmd[2]["stop_and_exit"]) + def test_qwen_remote_limited_modes_and_api_url(self): # A remote qwen with only the Base (clone) demo answering: the form # offers only clone mode and targets the clone remote URL. @@ -1540,7 +1553,8 @@ class SettingsTests(unittest.TestCase): original = {name: getattr(hub.config, name) for name in ("AUDIO_FORMAT", "AUDIO_BITRATE", "LANGUAGE", - "CHUNK_SIZE", "AUDIOCPP_UNLOAD_MODELS", + "CHUNK_SIZE", "STOP_SERVER_AND_EXIT", + "AUDIOCPP_UNLOAD_MODELS", "QWEN_API_URL", "CLONE_API_URL", "FASTER_API_URL", "AUDIOCPP_API_URL", "QWEN_REMOTE_URL", "CLONE_REMOTE_URL", @@ -1549,6 +1563,7 @@ class SettingsTests(unittest.TestCase): for name, value in original.items()]) values = {"audio_format": "ogg", "audio_bitrate": " 192k ", "language": "en", "chunk_size": "300", + "stop_and_exit": False, "unload_models": True, "qwen_custom_port": "7862", "qwen_clone_port": "7863", "faster_port": "8001", "audiocpp_port": "8081", @@ -1565,6 +1580,7 @@ class SettingsTests(unittest.TestCase): "AUDIO_BITRATE": "192k", "LANGUAGE": "English", "CHUNK_SIZE": 300, + "STOP_SERVER_AND_EXIT": False, "AUDIOCPP_UNLOAD_MODELS": True, "QWEN_API_URL": "http://127.0.0.1:7862", "CLONE_API_URL": "http://127.0.0.1:7863", @@ -1582,6 +1598,7 @@ class SettingsTests(unittest.TestCase): self.assertEqual(hub.config.AUDIO_BITRATE, "192k") self.assertEqual(hub.config.LANGUAGE, "English") self.assertEqual(hub.config.CHUNK_SIZE, 300) + self.assertEqual(hub.config.STOP_SERVER_AND_EXIT, False) self.assertEqual(hub.config.AUDIOCPP_UNLOAD_MODELS, True) self.assertEqual(hub.config.QWEN_API_URL, "http://127.0.0.1:7862") self.assertEqual(hub.config.FASTER_API_URL, "http://127.0.0.1:8001") @@ -1591,7 +1608,8 @@ class SettingsTests(unittest.TestCase): def test_apply_settings_rejects_bad_values(self): original = {name: getattr(hub.config, name) for name in ("AUDIO_FORMAT", "AUDIO_BITRATE", "LANGUAGE", - "CHUNK_SIZE", "AUDIOCPP_UNLOAD_MODELS", + "CHUNK_SIZE", "STOP_SERVER_AND_EXIT", + "AUDIOCPP_UNLOAD_MODELS", "QWEN_API_URL", "CLONE_API_URL", "FASTER_API_URL", "AUDIOCPP_API_URL", "QWEN_REMOTE_URL", "CLONE_REMOTE_URL", @@ -1600,6 +1618,7 @@ class SettingsTests(unittest.TestCase): for name, value in original.items()]) base = {"audio_format": "m4b", "audio_bitrate": "128k", "language": "English", "chunk_size": "250", + "stop_and_exit": True, "unload_models": True, "qwen_custom_port": "7860", "qwen_clone_port": "7861", "faster_port": "8000", "audiocpp_port": "8080"} @@ -1638,6 +1657,7 @@ class SettingsTests(unittest.TestCase): captured["fields"] = fields return {"audio_format": "ogg", "audio_bitrate": "192k", "language": "English", "chunk_size": "300", + "stop_and_exit": True, "unload_models": True, "qwen_custom_port": "7860", "qwen_clone_port": "7861", "faster_port": "8000", "audiocpp_port": "8080"} @@ -1656,7 +1676,8 @@ class SettingsTests(unittest.TestCase): hub._Hub(None).screen_settings() self.assertEqual([f["key"] for f in captured["fields"]], ["audio_format", "audio_bitrate", "language", - "chunk_size", "unload_models", "audiocpp_port", + "chunk_size", "stop_and_exit", "unload_models", + "audiocpp_port", "faster_port", "qwen_custom_port", "qwen_clone_port", "audiocpp_remote_url", "faster_remote_url", "qwen_custom_remote_url", @@ -1665,6 +1686,7 @@ class SettingsTests(unittest.TestCase): self.assertEqual(kinds["audio_format"], "choice") self.assertEqual(kinds["audio_bitrate"], "text") self.assertEqual(kinds["audiocpp_port"], "text") + self.assertEqual(kinds["stop_and_exit"], "bool") self.assertEqual(kinds["unload_models"], "bool") self.assertEqual(kinds["audiocpp_remote_url"], "text") labels = {f["key"]: f["label"] for f in captured["fields"]} @@ -1683,6 +1705,7 @@ class SettingsTests(unittest.TestCase): "audio_bitrate": "192k", "language": "English", "chunk_size": "300", + "stop_and_exit": True, "unload_models": True, "qwen_custom_port": "7860", "qwen_clone_port": "7861", @@ -1715,7 +1738,8 @@ class SettingsTests(unittest.TestCase): original = {name: getattr(hub.config, name) for name in ("AUDIO_FORMAT", "AUDIO_BITRATE", "LANGUAGE", - "CHUNK_SIZE", "AUDIOCPP_UNLOAD_MODELS", + "CHUNK_SIZE", "STOP_SERVER_AND_EXIT", + "AUDIOCPP_UNLOAD_MODELS", "QWEN_API_URL", "CLONE_API_URL", "FASTER_API_URL", "AUDIOCPP_API_URL", "QWEN_REMOTE_URL", "CLONE_REMOTE_URL", @@ -1732,6 +1756,7 @@ class SettingsTests(unittest.TestCase): 'LANGUAGE = "English"\n' "\n" "CHUNK_SIZE = 250\n" + "STOP_SERVER_AND_EXIT = True\n" "AUDIOCPP_UNLOAD_MODELS = True\n" 'QWEN_API_URL = "http://127.0.0.1:7860"\n' 'CLONE_API_URL = "http://127.0.0.1:7861"\n' diff --git a/app/ui/hub.py b/app/ui/hub.py index 1eef63b..cad9a9d 100644 --- a/app/ui/hub.py +++ b/app/ui/hub.py @@ -366,7 +366,7 @@ class _Hub: if not ok: return tui.Wizard.BACK if self._run_conversion(cmd[1], cmd[2]): - # "Stop server and exit after generating" was on: returning + # "Stop server and exit" was on: returning # None ends the wizard stack (the whole TUI). return None return tui.Wizard.BACK @@ -850,8 +850,8 @@ def _common_fields() -> list: "kind": "bool", "value": False, "visible": lambda fs: _field_value(fs, "output_format") != "m4b"}, {"key": "debug", "label": "Debug", "kind": "bool", "value": False}, - {"key": "stop_and_exit", "label": "Stop server and exit after " - "generating", "kind": "bool", "value": True}, + {"key": "stop_and_exit", "label": "Stop server and exit", + "kind": "bool", "value": config.STOP_SERVER_AND_EXIT}, ] @@ -1203,6 +1203,8 @@ def _settings_fields() -> list: "value": config.LANGUAGE, "validate": _validate_language}, {"key": "chunk_size", "label": "Chunk size (words)", "kind": "text", "value": str(config.CHUNK_SIZE), "validate": _validate_chunk_size}, + {"key": "stop_and_exit", "label": "Default stop server and exit", + "kind": "bool", "value": config.STOP_SERVER_AND_EXIT}, {"key": "unload_models", "label": "Unload models", "kind": "bool", "value": config.AUDIOCPP_UNLOAD_MODELS, "note": "audio.cpp: Unload previously-loaded models before converting to prevent VRAM exhaustion."}, @@ -1330,6 +1332,7 @@ def _apply_settings(values: dict) -> None: "AUDIO_BITRATE": bitrate, "LANGUAGE": normalize_language(values["language"]), "CHUNK_SIZE": chunk_size, + "STOP_SERVER_AND_EXIT": bool(values["stop_and_exit"]), "AUDIOCPP_UNLOAD_MODELS": bool(values["unload_models"]), "QWEN_API_URL": common.url_with_port( config.QWEN_API_URL, ports["qwen_custom_port"]), diff --git a/app/ui/runview.py b/app/ui/runview.py index eb385f0..60c43b0 100644 --- a/app/ui/runview.py +++ b/app/ui/runview.py @@ -19,7 +19,7 @@ The screen is fed by two threads the widget spawns: Esc and 'q' do the same thing everywhere: a confirmation to cancel processing, then (when this run started the server) a confirmation to shut it down. On the finished screen the behavior follows the convert form's -"Stop server and exit after generating" toggle: ON stops the server +"Stop server and exit" toggle: ON stops the server automatically, quits the whole TUI, and prints the results summary to the real terminal after curses closes; OFF waits for a key press and returns to the hub menu with the server still running. Errors (the server exits @@ -308,7 +308,7 @@ class RunView: """Run the view until the user leaves the terminal screen. Returns True only when the run should end with the whole TUI - quitting — the "Stop server and exit after generating" path, which + quitting — the "Stop server and exit" path, which stops the server automatically and records the results as a post-TUI notice. Every other exit (a key press on the summary screen, the Esc cancel flow) lands back on the hub menu. @@ -449,7 +449,7 @@ class RunView: self.render() def _auto_stop_and_exit(self) -> bool: - """The "Stop server and exit after generating" path. + """The "Stop server and exit" path. No prompts and no key waits: stop the managed server this run started (if any), record the results summary as a post-TUI notice |
