diff options
| author | historia <historiavg@proton.me> | 2026-09-02 19:30:29 -0400 |
|---|---|---|
| committer | historia <historiavg@proton.me> | 2026-09-02 19:30:29 -0400 |
| commit | b5bb90e15a6e17fc9b5061792f6b158199fa91bb (patch) | |
| tree | adf6a616ae93d0b61faffc31ce77305325fb4984 /app/tests | |
| parent | 6804c785c6b506c47b45264398728d0a609310be (diff) | |
| download | tts-audiobook-generator-b5bb90e15a6e17fc9b5061792f6b158199fa91bb.tar.gz | |
feat: voice menu combined for tts/clone paths in tui
Diffstat (limited to 'app/tests')
| -rw-r--r-- | app/tests/test_hub.py | 222 |
1 files changed, 141 insertions, 81 deletions
diff --git a/app/tests/test_hub.py b/app/tests/test_hub.py index 8e71104..94ea286 100644 --- a/app/tests/test_hub.py +++ b/app/tests/test_hub.py @@ -2654,8 +2654,8 @@ class ConvertFlowTests(unittest.TestCase): patch.object(hub.config, "STOP_SERVER_AND_EXIT", True): self._mock_preflight() self._answer_form(backend="sglomni", model_id=entry.key, - voice="Vivian", named_voice="", clone="", - clone_dir="/tmp", instructions="") + voice="Vivian", clone_dir="/tmp", + instructions="") cmd = self._convert(None, [self._ready("sglomni", "SGLang-Omni")]) self.assertEqual(cmd[1], hub.BACKEND_SGLOMNI) @@ -2668,6 +2668,39 @@ class ConvertFlowTests(unittest.TestCase): def test_sglomni_managed_clone_model_routes_the_reference(self): entry = hub.sglomni_backend.entry_by_key("higgs_audio_v3_tts") self._patch_sglomni_installed([entry]) + with tempfile.TemporaryDirectory() as td: + ref = Path(td) / "ref.wav" + ref.write_bytes(b"RIFF") + with patch.object(hub.config, "AUDIO_FORMAT", "m4b"), \ + patch.object(hub.config, "LANGUAGE", "English"), \ + patch.object(hub.config, "SPEED", 1.0), \ + patch.object(hub.config, "DEBUG", False), \ + patch.object(hub.config, "STOP_SERVER_AND_EXIT", True): + self._mock_preflight() + self._answer_form(backend="sglomni", model_id=entry.key, + voice=str(ref), clone_dir=td, + instructions="") + cmd = self._convert(None, [self._ready("sglomni", + "SGLang-Omni")]) + fields = self.tui.forms_seen[-1][1] + voice_field = self._field("voice") + next(f for f in fields + if f["key"] == "sglomni.clone_dir")["value"] = td + wav_labels = [label for label, _ in + voice_field["choices"](fields)] + kwargs = cmd[2] + self.assertEqual(kwargs["clone"], str(ref)) + # The .wav pick rides the clone kwarg; no named voice is sent. + self.assertNotIn("voice", kwargs) + # The combined Voice menu opens on the built-in default voice, + # with the built-in entry above the clone directory's .wavs. + self.assertEqual(voice_field["value"], "") + self.assertEqual(wav_labels, ["<built-in voice>", "ref.wav"]) + + def test_sglomni_managed_clone_model_blank_pick_uses_builtin_voice(self): + """A blank Voice pick on Higgs narrates with the model's own voice.""" + entry = hub.sglomni_backend.entry_by_key("higgs_audio_v3_tts") + self._patch_sglomni_installed([entry]) with patch.object(hub.config, "AUDIO_FORMAT", "m4b"), \ patch.object(hub.config, "LANGUAGE", "English"), \ patch.object(hub.config, "SPEED", 1.0), \ @@ -2675,15 +2708,12 @@ class ConvertFlowTests(unittest.TestCase): patch.object(hub.config, "STOP_SERVER_AND_EXIT", True): self._mock_preflight() self._answer_form(backend="sglomni", model_id=entry.key, - voice="", named_voice="", - clone="/tmp/ref.wav", clone_dir="/tmp", - instructions="") + voice="", clone_dir="/tmp", instructions="") cmd = self._convert(None, [self._ready("sglomni", "SGLang-Omni")]) kwargs = cmd[2] - self.assertEqual(kwargs["clone"], "/tmp/ref.wav") - # A reference wins over any named voice: ref_audio drives the clone. - self.assertIsNone(kwargs["voice"]) + self.assertNotIn("clone", kwargs) + self.assertNotIn("voice", kwargs) def test_sglomni_managed_design_model_sends_instructions(self): entry = hub.sglomni_backend.entry_by_key("qwen3_tts_1_7b_voicedesign") @@ -2695,8 +2725,7 @@ class ConvertFlowTests(unittest.TestCase): patch.object(hub.config, "STOP_SERVER_AND_EXIT", True): self._mock_preflight() self._answer_form(backend="sglomni", model_id=entry.key, - voice="", named_voice="", clone="", - clone_dir="/tmp", + voice="", clone_dir="/tmp", instructions="A warm narrator.") cmd = self._convert(None, [self._ready("sglomni", "SGLang-Omni")]) @@ -2721,15 +2750,22 @@ class ConvertFlowTests(unittest.TestCase): self._mock_preflight() self._answer_form(backend="sglomni-remote", model_id="higgs_audio_v3_tts", - voice="", named_voice="narrator", - clone="", clone_dir="/tmp", instructions="") + voice="narrator", clone_dir="/tmp", + instructions="") cmd = self._convert( None, [self._remote("sglomni", "SGLang-Omni", url="http://sgl.local:8100")]) + fields = self.tui.forms_seen[-1][1] + voice_field = self._field("voice") kwargs = cmd[2] self.assertEqual(kwargs["model_id"], "higgs_audio_v3_tts") - # An uploaded (named) voice rides the request's voice field. + # The uploaded (named) voice joins the combined Voice menu below + # the built-in entry, and rides the request's voice field. + self.assertEqual( + [label for label, _ in voice_field["choices"](fields)], + ["<built-in voice>", "narrator"]) self.assertEqual(kwargs["voice"], "narrator") + self.assertNotIn("clone", kwargs) self.assertEqual(kwargs["api_url"], "http://sgl.local:8100") def test_sglomni_fields_do_not_shadow_audiocpp_fields(self): @@ -2767,10 +2803,11 @@ class ConvertFlowTests(unittest.TestCase): patch.object(hub.config, "STOP_SERVER_AND_EXIT", True): self._mock_preflight() # Voxtral picked: its run carries the preset voice, and in - # the form its Voice menu shows while the clone picker hides. + # the form its Voice menu shows the preset only (no + # built-in entry, no clone .wavs — those belong to the + # clone-capable models' combined menu). self._answer_form(backend="sglomni", model_id="voxtral_tts", - voice="casual_male", named_voice="", - clone="", clone_dir="/tmp", + voice="casual_male", clone_dir="/tmp", instructions="") cmd = self._convert(None, statuses) fields = self.tui.forms_seen[-1][1] @@ -2782,12 +2819,12 @@ class ConvertFlowTests(unittest.TestCase): next(f for f in fields if f["key"] == "sglomni.model_id")["value"] = \ "voxtral_tts" - self.assertTrue(self._field("voice")["visible"](fields)) - self.assertIn( - "casual_male", - [label for label, _ in - self._field("voice")["choices"](fields)]) - self.assertFalse(self._field("clone")["visible"](fields)) + voice_field = self._field("voice") + self.assertEqual(voice_field["key"], "sglomni.voice") + self.assertTrue(voice_field["visible"](fields)) + self.assertEqual( + [label for label, _ in voice_field["choices"](fields)], + ["casual_male"]) # An audio.cpp submission keeps its own picks: SGLang's # same-named fields must not leak into its run. with patch.object(hub.audiocpp_backend, "find_local_checkout", @@ -2799,9 +2836,7 @@ class ConvertFlowTests(unittest.TestCase): patch.object(hub.config, "STOP_SERVER_AND_EXIT", True): self._mock_preflight() self._answer_form(backend="audiocpp", model_id="qwen", - audiocpp_voice="", instructions="Style.", - voice="", named_voice="", clone="", - clone_dir="/tmp") + audiocpp_voice="", instructions="Style.") cmd = self._convert(None, statuses) self.assertEqual(cmd[2]["model_id"], "qwen") self.assertEqual(cmd[2]["instructions"], "Style.") @@ -2809,9 +2844,10 @@ class ConvertFlowTests(unittest.TestCase): def test_sglomni_every_catalog_model_drives_the_right_form(self): """Every catalog entry shows the capability-matched voice fields. - speaker -> the preset Voice menu; clone with a required reference - -> the clone picker only; clone that narrates without one -> the - default-voice pick alongside the clone picker; design -> the + speaker -> the preset Voice menu; clone -> the combined Voice + menu (the built-in default voice on top when the model can + narrate without a reference, then the clone directory's .wavs) + beside the Clone .wav directory browser; design -> the Instructions box. """ @@ -2825,59 +2861,83 @@ class ConvertFlowTests(unittest.TestCase): presets.start() self.addCleanup(presets.stop) self._patch_sglomni_installed(list(hub.sglomni_backend.ENTRIES)) - with patch.object(hub.config, "AUDIO_FORMAT", "m4b"), \ - patch.object(hub.config, "LANGUAGE", "English"), \ - patch.object(hub.config, "SPEED", 1.0), \ - patch.object(hub.config, "DEBUG", False), \ - patch.object(hub.config, "STOP_SERVER_AND_EXIT", True): - self._mock_preflight() - for entry in hub.sglomni_backend.ENTRIES: - with self.subTest(entry=entry.key): - overrides = {"backend": "sglomni", - "model_id": entry.key, - "voice": "", "named_voice": "", - "clone": "", "clone_dir": "/tmp", - "instructions": ""} - if entry.capability == "speaker": - overrides["voice"] = \ - (entry.speakers or ("casual_male",))[0] - elif entry.capability == "clone": - overrides["clone"] = "/tmp/ref.wav" - else: # design - overrides["instructions"] = "A warm narrator." - self._answer_form(**overrides) - cmd = self._convert(None, [ - self._ready("sglomni", "SGLang-Omni")]) - self.assertIsNotNone(cmd) - kwargs = cmd[2] - self.assertEqual(kwargs["model_id"], entry.key) - fields = self.tui.forms_seen[-1][1] - next(f for f in fields - if f["key"] == "sglomni.model_id")["value"] = \ - entry.key - shown = {f["key"] for f in fields - if f["key"].startswith("sglomni.") - and f["visible"](fields)} - expected = {"sglomni.model_id"} - if entry.capability == "speaker": - expected.add("sglomni.voice") - self.assertEqual( - kwargs.get("voice"), - (entry.speakers or ("casual_male",))[0]) - self.assertNotIn("clone", kwargs) - elif entry.capability == "clone": - expected |= {"sglomni.clone_dir", "sglomni.clone"} - if not entry.requires_reference: - expected.add("sglomni.named_voice") - self.assertEqual(kwargs.get("clone"), "/tmp/ref.wav") - self.assertIsNone(kwargs.get("voice")) - else: # design - expected.add("sglomni.instructions") - self.assertEqual(kwargs.get("instructions"), - "A warm narrator.") - self.assertNotIn("voice", kwargs) - self.assertNotIn("clone", kwargs) - self.assertEqual(shown, expected) + with tempfile.TemporaryDirectory() as td: + ref = Path(td) / "ref.wav" + ref.write_bytes(b"RIFF") + with patch.object(hub.config, "AUDIO_FORMAT", "m4b"), \ + patch.object(hub.config, "LANGUAGE", "English"), \ + patch.object(hub.config, "SPEED", 1.0), \ + patch.object(hub.config, "DEBUG", False), \ + patch.object(hub.config, "STOP_SERVER_AND_EXIT", True): + self._mock_preflight() + for entry in hub.sglomni_backend.ENTRIES: + with self.subTest(entry=entry.key): + overrides = {"backend": "sglomni", + "model_id": entry.key, + "voice": "", "clone_dir": td, + "instructions": ""} + if entry.capability == "speaker": + overrides["voice"] = \ + (entry.speakers or ("casual_male",))[0] + elif entry.capability == "clone": + if entry.requires_reference: + overrides["voice"] = str(ref) + else: # design + overrides["instructions"] = "A warm narrator." + self._answer_form(**overrides) + cmd = self._convert(None, [ + self._ready("sglomni", "SGLang-Omni")]) + self.assertIsNotNone(cmd) + kwargs = cmd[2] + self.assertEqual(kwargs["model_id"], entry.key) + fields = self.tui.forms_seen[-1][1] + next(f for f in fields + if f["key"] == "sglomni.model_id")["value"] = \ + entry.key + next(f for f in fields + if f["key"] == "sglomni.clone_dir")["value"] \ + = td + shown = {f["key"] for f in fields + if f["key"].startswith("sglomni.") + and f["visible"](fields)} + voice_labels = [ + label for label, _ in + self._field("voice")["choices"](fields)] + if entry.capability == "speaker": + expected = {"sglomni.model_id", "sglomni.voice"} + preset = (entry.speakers + or ("casual_male",))[0] + self.assertEqual( + voice_labels, + list(entry.speakers or ("casual_male",))) + self.assertEqual(kwargs.get("voice"), preset) + self.assertNotIn("clone", kwargs) + elif entry.capability == "clone": + expected = {"sglomni.model_id", "sglomni.voice", + "sglomni.clone_dir"} + if entry.requires_reference: + # Cannot narrate without a reference: the + # menu is the clone directory's .wavs only. + self.assertEqual(voice_labels, ["ref.wav"]) + self.assertEqual(kwargs.get("clone"), + str(ref)) + self.assertNotIn("voice", kwargs) + else: + # Built-in default voice above the .wavs; + # the blank pick narrates without a clone. + self.assertEqual(voice_labels, + ["<built-in voice>", + "ref.wav"]) + self.assertNotIn("clone", kwargs) + self.assertNotIn("voice", kwargs) + else: # design + expected = {"sglomni.model_id", + "sglomni.instructions"} + self.assertEqual(kwargs.get("instructions"), + "A warm narrator.") + self.assertNotIn("voice", kwargs) + self.assertNotIn("clone", kwargs) + self.assertEqual(shown, expected) class SelectSpecTests(unittest.TestCase): |
