diff options
Diffstat (limited to 'app/tests/test_tts.py')
| -rw-r--r-- | app/tests/test_tts.py | 102 |
1 files changed, 100 insertions, 2 deletions
diff --git a/app/tests/test_tts.py b/app/tests/test_tts.py index 2b2ac1c..49eb6f6 100644 --- a/app/tests/test_tts.py +++ b/app/tests/test_tts.py @@ -31,6 +31,8 @@ from converter.clients import ( TTS_LANGUAGES, VOICE_MODE_CLONE, VOICE_MODE_CUSTOM, + VOICE_MODE_DESIGN, + VOICE_MODES, AudioCppTTSClient, FasterTTSClient, QwenTTSClient, @@ -494,6 +496,80 @@ class QwenTTSClientGenerateTests(unittest.TestCase): mock_generate.assert_not_called() +class QwenTTSClientVoiceDesignTests(unittest.TestCase): + """Qwen VoiceDesign mode: instructions and the /run_voice_design call.""" + + def setUp(self): + self._tmp = tempfile.TemporaryDirectory() + + def tearDown(self): + self._tmp.cleanup() + + def _client(self, instructions=None): + client = QwenTTSClient.__new__(QwenTTSClient) + client.chunks_dir = Path(self._tmp.name) + client.voice_mode = VOICE_MODE_DESIGN + client.language = config.LANGUAGE + client.instructions = (instructions if instructions is not None + else config.INSTRUCT).strip() + client.api_info = {"named_endpoints": {"/run_voice_design": { + "parameters": [ + {"parameter_name": "text"}, + {"parameter_name": "lang_disp"}, + {"parameter_name": "design"}, + ]}}} + return client + + def _fake_output(self) -> str: + out = Path(self._tmp.name) / "server_out.wav" + out.write_bytes(b"\x01\x00") + return str(out) + + def test_voice_mode_design_is_valid(self): + self.assertIn(VOICE_MODE_DESIGN, VOICE_MODES) + + def test_generate_payload_and_return(self): + client = self._client(instructions="A warm narrator") + fake = MagicMock(return_value=(self._fake_output(),)) + with patch.object(client, "_generate_voice_design", fake): + result = client._generate_sub_request( + "Hello there.", self._tmp.name, 1, 1, 1) + fake.assert_called_once_with("Hello there.") + self.assertEqual(Path(result).name, "part_01.wav") + + def test_payload_uses_design_field_language_and_instruction(self): + client = self._client(instructions="A warm narrator") + captured = {} + + def fake_predict(**payload): + captured.update(payload) + return (self._fake_output(),) + + client.client = MagicMock() + client.client.predict.side_effect = fake_predict + result = client._generate_voice_design("Hi.") + self.assertEqual(captured["text"], "Hi.") + self.assertEqual(captured["lang_disp"], config.LANGUAGE) + self.assertEqual(captured["design"], "A warm narrator") + self.assertNotIn("seed", captured) # not accepted by this endpoint + self.assertEqual(result, (self._fake_output(),)) + + def test_payload_defaults_instructions_to_config(self): + client = self._client(instructions=None) + self.assertEqual(client.instructions, + (config.INSTRUCT or "").strip()) + + def test_unknown_api_falls_back_to_the_requested_name(self): + client = self._client() + client.api_info = {"named_endpoints": {}} + client.client = MagicMock() + client.client.predict.return_value = (self._fake_output(),) + client._generate_voice_design("Hi.") + _, kwargs = client.client.predict.call_args + self.assertEqual(kwargs["api_name"], "/run_voice_design") + + + class AudioCppTTSClientHealthTests(unittest.TestCase): """Connection behavior of the audio.cpp client.""" @@ -1682,6 +1758,28 @@ class BackendWiringTests(unittest.TestCase): AudiobookConverter(voice_mode=VOICE_MODE_CLONE, backend=BACKEND_QWEN) + def test_qwen_design_mode_without_instructions_rejected(self): + # A VoiceDesign run needs a description; an empty instructions + # value (not even the config default) is refused up front. + with patch("converter.converter.QwenTTSClient"): + with self.assertRaises(ValueError): + AudiobookConverter(voice_mode=VOICE_MODE_DESIGN, + backend=BACKEND_QWEN, instructions=" ") + + def test_qwen_design_mode_threads_instructions_to_the_client(self): + with patch("converter.converter.QwenTTSClient") as mock_qwen: + AudiobookConverter(voice_mode=VOICE_MODE_DESIGN, + backend=BACKEND_QWEN, + instructions="A warm adult female narrator") + _, kwargs = mock_qwen.call_args + self.assertEqual(kwargs["instructions"], + "A warm adult female narrator") + + def test_qwen_design_narrator_tag_uses_designed(self): + self.assertEqual(AudiobookConverter.compute_narrator_tag( + BACKEND_QWEN, None, VOICE_MODE_DESIGN, None, + "A warm adult female narrator"), "designed") + def test_api_url_override_reaches_each_client(self): # A remote conversion threads api_url through to the selected client. with patch("converter.converter.AudioCppTTSClient") as mock_audiocpp: @@ -1709,8 +1807,8 @@ class BackendWiringTests(unittest.TestCase): chunks_dir=converter_mod.CHUNKS_FOLDER, voice_mode=VOICE_MODE_CUSTOM, voice_clone_ref_audio=None, voice_clone_ref_text=None, skip_transcription=False, - language=config.LANGUAGE, api_url="http://10.0.0.5:7860", - quiet=False) + language=config.LANGUAGE, instructions=None, + api_url="http://10.0.0.5:7860", quiet=False) def test_audiocpp_clone_mode_does_not_require_reference(self): # Cloning is server-side for the audiocpp backend, so the |
