diff options
| author | historia <historiavg@proton.me> | 2026-08-19 05:13:36 -0400 |
|---|---|---|
| committer | historia <historiavg@proton.me> | 2026-08-19 05:13:36 -0400 |
| commit | 94ddbb0634022a6e5209b5221c057228ec3d1418 (patch) | |
| tree | d7892e758cad10336816695166c511f7a9689704 /tests/test_tts.py | |
| parent | 9d4d7ef806c17387af9778725cd65a5e7ed10e39 (diff) | |
| download | tts-audiobook-generator-94ddbb0634022a6e5209b5221c057228ec3d1418.tar.gz | |
feat: reuse one seed per run for consistent voice across chunks
Diffstat (limited to 'tests/test_tts.py')
| -rw-r--r-- | tests/test_tts.py | 64 |
1 files changed, 57 insertions, 7 deletions
diff --git a/tests/test_tts.py b/tests/test_tts.py index 26f663e..afaa2e3 100644 --- a/tests/test_tts.py +++ b/tests/test_tts.py @@ -77,6 +77,54 @@ class QwenTTSClientLanguageTests(unittest.TestCase): mock_connect.assert_not_called() +class SeedResolutionTests(unittest.TestCase): + """CONSTANT_SEED: one seed per run, reused for every request, so the + voice stays consistent across chunk boundaries (the servers + re-sample the voice when the seed changes between generations).""" + + def _make_client(self, **kwargs): + with patch.object(QwenTTSClient, "_connect"): + return QwenTTSClient(**kwargs) + + def test_constant_seed_draws_one_nonnegative_seed(self): + with patch.object(config, "CONSTANT_SEED", True), \ + patch.object(config, "SEED", -1): + client = self._make_client(voice_mode=tts.VOICE_MODE_CUSTOM) + self.assertGreaterEqual(client._seed, 0) + + def test_explicit_seed_wins_over_constant_seed(self): + with patch.object(config, "CONSTANT_SEED", True), \ + patch.object(config, "SEED", 42): + client = self._make_client(voice_mode=tts.VOICE_MODE_CUSTOM) + self.assertEqual(client._seed, 42) + + def test_without_constant_seed_minus_one_is_forwarded(self): + with patch.object(config, "CONSTANT_SEED", False), \ + patch.object(config, "SEED", -1): + client = self._make_client(voice_mode=tts.VOICE_MODE_CUSTOM) + self.assertEqual(client._seed, -1) + + def test_resolved_seed_is_reused_across_requests(self): + api_info = { + "named_endpoints": { + "/run_custom_voice": { + "parameters": [{"parameter_name": "seed"}] + } + } + } + client = QwenTTSClient.__new__(QwenTTSClient) + client.voice_mode = tts.VOICE_MODE_CUSTOM + client.language = "English" + client._seed = 1234 + client.api_info = api_info + client.client = MagicMock() + client._generate_custom_voice("first text") + client._generate_custom_voice("second text") + seeds = [call.kwargs["seed"] + for call in client.client.predict.call_args_list] + self.assertEqual(seeds, [1234, 1234]) + + class PayloadLanguageTests(unittest.TestCase): """The language must reach the API payload in every endpoint variant.""" @@ -92,6 +140,7 @@ class PayloadLanguageTests(unittest.TestCase): client = QwenTTSClient.__new__(QwenTTSClient) client.voice_mode = tts.VOICE_MODE_CUSTOM client.language = language + client._seed = config.SEED client.api_info = api_info if api_info is not None else { "named_endpoints": {endpoint: {}} } @@ -102,6 +151,7 @@ class PayloadLanguageTests(unittest.TestCase): client = QwenTTSClient.__new__(QwenTTSClient) client.voice_mode = tts.VOICE_MODE_CLONE client.language = language + client._seed = config.SEED client.voice_clone_ref_audio = str(self.ref_audio) client.voice_clone_ref_text = ref_text client.clone_api_info = api_info if api_info is not None else { @@ -185,8 +235,8 @@ class FasterTTSClientHealthTests(unittest.TestCase): with patch("converter.tts.urllib.request.urlopen", return_value=self._health_response()): client = FasterTTSClient() - self.assertEqual(client.voice, config.FASTER_TTS_VOICE) - self.assertEqual(client.api_url, config.FASTER_TTS_API_URL.rstrip("/")) + self.assertEqual(client.voice, config.FASTER_VOICE) + self.assertEqual(client.api_url, config.FASTER_API_URL.rstrip("/")) def test_explicit_voice_and_url_override_config(self): with patch("converter.tts.urllib.request.urlopen", @@ -241,7 +291,7 @@ class FasterTTSClientGenerateTests(unittest.TestCase): sentences = [" ".join(f"word{i}" for i in range(6)) + "." for _ in range(3)] text = " ".join(sentences) pcm_parts = [b"\x01\x00" * 10, b"\x02\x00" * 20, b"\x03\x00" * 30] - with patch.object(config, "MAX_REQUEST_WORDS", 10), \ + with patch.object(tts, "MAX_REQUEST_WORDS", 10), \ patch.object(client, "_request_pcm", side_effect=pcm_parts) as mock_pcm: result = client.generate_chunk(text, 1) self.assertEqual(mock_pcm.call_count, 3) @@ -252,10 +302,10 @@ class FasterTTSClientGenerateTests(unittest.TestCase): client = self._make_client() text = " ".join(f"word{i}" for i in range(8)) pcm = b"\x01\x00" * 10 - with patch.object(config, "CHUNK_SIZE_WORDS", 4), \ + with patch.object(config, "CHUNK_SIZE", 4), \ patch.object(client, "_request_pcm", return_value=pcm) as mock_pcm: result = client.generate_chunk(text, 1) - # CHUNK_SIZE_WORDS no longer drives request size: the hard ceiling + # CHUNK_SIZE no longer drives request size: the hard ceiling # does, so the whole (8-word) text is one request here. self.assertEqual(mock_pcm.call_count, 1) self.assertIsNotNone(result) @@ -434,7 +484,7 @@ class QwenTTSClientGenerateTests(unittest.TestCase): first = self._write_wav(Path(self._tmp.name) / "one.wav", b"\x01\x00" * 10) second = self._write_wav(Path(self._tmp.name) / "two.wav", b"\x02\x00" * 20) text = " ".join(f"word{i}" for i in range(12)) - with patch.object(config, "MAX_REQUEST_WORDS", 5), \ + with patch.object(tts, "MAX_REQUEST_WORDS", 5), \ patch.object(client, "_generate_custom_voice", side_effect=[(str(first),), (str(second),), (str(first),)]) as mock_generate: @@ -503,7 +553,7 @@ class FasterModeWiringTests(unittest.TestCase): def test_narrator_tag_falls_back_to_config_voice(self): converter = self._faster_converter() - self.assertEqual(converter._narrator_tag(), config.FASTER_TTS_VOICE) + self.assertEqual(converter._narrator_tag(), config.FASTER_VOICE) def test_banner_and_narrator_work_without_reference_audio(self): converter = self._faster_converter(faster_voice="male_richard_poe") |
