aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhistoria <historiavg@proton.me>2026-08-25 20:48:51 -0400
committerhistoria <historiavg@proton.me>2026-08-25 20:48:51 -0400
commitd453d88d1d40c5e54af07ceb89c8c18d09cfdb0e (patch)
treeaaf99573d0380c57174ca1ec5437e09e26fece37
parentf3f7bb98e4bc969a17fba71294177819fbbbd070 (diff)
downloadtts-audiobook-generator-d453d88d1d40c5e54af07ceb89c8c18d09cfdb0e.tar.gz
feat: default save and exit server setting
-rw-r--r--app/converter/config.py4
-rw-r--r--app/tests/test_hub.py33
-rw-r--r--app/ui/hub.py9
-rw-r--r--app/ui/runview.py6
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