aboutsummaryrefslogtreecommitdiff
path: root/app/tests/test_tts.py
diff options
context:
space:
mode:
authorhistoria <historiavg@proton.me>2026-08-26 01:43:41 -0400
committerhistoria <historiavg@proton.me>2026-08-26 01:43:41 -0400
commitacbd9ff2c91182d96c57ffb57bee6e9b3fcbcbd4 (patch)
treee336c11f2a57cff5566e249aa5d5477a3dc63c55 /app/tests/test_tts.py
parent104a0d65c1ba37847c15b64212b7fec8ba371ccb (diff)
downloadtts-audiobook-generator-acbd9ff2c91182d96c57ffb57bee6e9b3fcbcbd4.tar.gz
refactor: split tts.py into per-backend packages
Diffstat (limited to 'app/tests/test_tts.py')
-rw-r--r--app/tests/test_tts.py355
1 files changed, 188 insertions, 167 deletions
diff --git a/app/tests/test_tts.py b/app/tests/test_tts.py
index 02b7dc4..2b2ac1c 100644
--- a/app/tests/test_tts.py
+++ b/app/tests/test_tts.py
@@ -4,20 +4,43 @@ import io
import json
import tempfile
import time
+import urllib.error
import unittest
import wave
from contextlib import redirect_stdout
from pathlib import Path
from unittest.mock import MagicMock, patch
-from converter import config, tts
-from converter.converter import AudiobookConverter
-from converter.tts import (
+from converter import config
+from converter import converter as converter_mod
+from converter.clients import (
+ AUDIOCPP_DEFAULT_FAMILY_PROFILE,
+ AUDIOCPP_FAMILY_PROFILES,
+ AUDIOCPP_LANG_OMIT,
+ AUDIOCPP_TASK_TTS,
+ AUDIOCPP_TASK_VDES,
+ AUDIOCPP_VOICE_CLONE,
+ AUDIOCPP_VOICE_DESIGN,
+ AUDIOCPP_VOICE_SPEAKER,
+ BACKEND_AUDIOCPP,
+ BACKEND_FASTER,
+ BACKEND_QWEN,
+ LANGUAGE_ISO_CODES,
+ MODEL_SIZE,
+ SAMPLE_RATE,
+ TTS_LANGUAGES,
+ VOICE_MODE_CLONE,
+ VOICE_MODE_CUSTOM,
AudioCppTTSClient,
FasterTTSClient,
QwenTTSClient,
+ audiocpp_entry_voice_capability,
normalize_language,
)
+from converter.converter import AudiobookConverter
+
+# Chunks folder handed to clients whose tests never write chunk files.
+_DUMMY_CHUNKS = Path(tempfile.gettempdir()) / "audiobook_tts_test_chunks"
class NormalizeLanguageTests(unittest.TestCase):
@@ -43,7 +66,7 @@ class NormalizeLanguageTests(unittest.TestCase):
self.assertEqual(normalize_language("it"), "Italian")
def test_all_supported_languages_round_trip(self):
- for name in tts.TTS_LANGUAGES:
+ for name in TTS_LANGUAGES:
self.assertEqual(normalize_language(name.lower()), name)
def test_unknown_language_rejected_with_guidance(self):
@@ -65,34 +88,34 @@ class QwenTTSClientLanguageTests(unittest.TestCase):
def _make_client(self, **kwargs):
with patch.object(QwenTTSClient, "_connect"):
- return QwenTTSClient(**kwargs)
+ return QwenTTSClient(_DUMMY_CHUNKS, **kwargs)
def test_default_follows_config_for_each_mode(self):
- custom = self._make_client(voice_mode=tts.VOICE_MODE_CUSTOM)
+ custom = self._make_client(voice_mode=VOICE_MODE_CUSTOM)
self.assertEqual(custom.language, config.LANGUAGE)
- clone = self._make_client(voice_mode=tts.VOICE_MODE_CLONE,
+ clone = self._make_client(voice_mode=VOICE_MODE_CLONE,
voice_clone_ref_audio="ref.wav")
self.assertEqual(clone.language, config.LANGUAGE)
def test_explicit_language_normalized(self):
- client = self._make_client(voice_mode=tts.VOICE_MODE_CUSTOM, language="ja")
+ client = self._make_client(voice_mode=VOICE_MODE_CUSTOM, language="ja")
self.assertEqual(client.language, "Japanese")
def test_invalid_language_fails_before_connect(self):
with patch.object(QwenTTSClient, "_connect") as mock_connect:
with self.assertRaises(ValueError):
- QwenTTSClient(language="klingon")
+ QwenTTSClient(_DUMMY_CHUNKS, language="klingon")
mock_connect.assert_not_called()
def test_api_url_override_stored(self):
- client = self._make_client(voice_mode=tts.VOICE_MODE_CUSTOM,
+ client = self._make_client(voice_mode=VOICE_MODE_CUSTOM,
api_url="http://10.0.0.5:7860")
self.assertEqual(client.api_url, "http://10.0.0.5:7860")
def test_api_url_override_used_by_connect(self):
with patch.object(QwenTTSClient, "_init_client") as mk_init:
client = QwenTTSClient.__new__(QwenTTSClient)
- client.voice_mode = tts.VOICE_MODE_CUSTOM
+ client.voice_mode = VOICE_MODE_CUSTOM
client.api_url = "http://10.0.0.5:7860"
client._connect()
mk_init.assert_called_once_with("http://10.0.0.5:7860", clone=False)
@@ -105,24 +128,24 @@ class SeedResolutionTests(unittest.TestCase):
def _make_client(self, **kwargs):
with patch.object(QwenTTSClient, "_connect"):
- return QwenTTSClient(**kwargs)
+ return QwenTTSClient(_DUMMY_CHUNKS, **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)
+ client = self._make_client(voice_mode=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)
+ client = self._make_client(voice_mode=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)
+ client = self._make_client(voice_mode=VOICE_MODE_CUSTOM)
self.assertEqual(client._seed, -1)
def test_resolved_seed_is_reused_across_requests(self):
@@ -134,7 +157,7 @@ class SeedResolutionTests(unittest.TestCase):
}
}
client = QwenTTSClient.__new__(QwenTTSClient)
- client.voice_mode = tts.VOICE_MODE_CUSTOM
+ client.voice_mode = VOICE_MODE_CUSTOM
client.language = "English"
client._seed = 1234
client.api_info = api_info
@@ -159,7 +182,7 @@ class PayloadLanguageTests(unittest.TestCase):
def _custom_client(self, language, endpoint, api_info=None):
client = QwenTTSClient.__new__(QwenTTSClient)
- client.voice_mode = tts.VOICE_MODE_CUSTOM
+ client.voice_mode = VOICE_MODE_CUSTOM
client.language = language
client._seed = config.SEED
client.api_info = api_info if api_info is not None else {
@@ -170,7 +193,7 @@ class PayloadLanguageTests(unittest.TestCase):
def _clone_client(self, language, endpoint, api_info=None, ref_text="hello"):
client = QwenTTSClient.__new__(QwenTTSClient)
- client.voice_mode = tts.VOICE_MODE_CLONE
+ client.voice_mode = VOICE_MODE_CLONE
client.language = language
client._seed = config.SEED
client.voice_clone_ref_audio = str(self.ref_audio)
@@ -220,7 +243,7 @@ class PayloadLanguageTests(unittest.TestCase):
client = self._clone_client("English", "/generate_voice_clone", api_info=api_info)
client._generate_voice_clone("text")
kwargs = client.clone_client.predict.call_args.kwargs
- self.assertEqual(kwargs["model_size"], tts.MODEL_SIZE)
+ self.assertEqual(kwargs["model_size"], MODEL_SIZE)
self.assertEqual(kwargs["seed"], config.SEED)
@@ -236,32 +259,33 @@ class FasterTTSClientHealthTests(unittest.TestCase):
def test_unreachable_server_raises_with_readme_pointer(self):
import urllib.error
- with patch("converter.tts.urllib.request.urlopen",
+ with patch("converter.clients.faster.urllib.request.urlopen",
side_effect=urllib.error.URLError("Connection refused")):
with self.assertRaises(RuntimeError) as ctx:
- FasterTTSClient()
+ FasterTTSClient(_DUMMY_CHUNKS)
message = str(ctx.exception)
self.assertIn("not reachable", message)
self.assertIn("README", message)
def test_model_not_loaded_raises(self):
- with patch("converter.tts.urllib.request.urlopen",
+ with patch("converter.clients.faster.urllib.request.urlopen",
return_value=self._health_response(model_loaded=False)):
with self.assertRaises(RuntimeError) as ctx:
- FasterTTSClient()
+ FasterTTSClient(_DUMMY_CHUNKS)
self.assertIn("not loaded", str(ctx.exception))
def test_healthy_server_defaults_from_config(self):
- with patch("converter.tts.urllib.request.urlopen",
+ with patch("converter.clients.faster.urllib.request.urlopen",
return_value=self._health_response()):
- client = FasterTTSClient()
+ client = FasterTTSClient(_DUMMY_CHUNKS)
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",
+ with patch("converter.clients.faster.urllib.request.urlopen",
return_value=self._health_response()):
- client = FasterTTSClient(voice="narrator", api_url="http://10.0.0.5:9000/")
+ client = FasterTTSClient(_DUMMY_CHUNKS,
+ voice="narrator", api_url="http://10.0.0.5:9000/")
self.assertEqual(client.voice, "narrator")
self.assertEqual(client.api_url, "http://10.0.0.5:9000")
@@ -271,18 +295,16 @@ class FasterTTSClientGenerateTests(unittest.TestCase):
def setUp(self):
self._tmp = tempfile.TemporaryDirectory()
- self._chunks = patch.object(tts, "CHUNKS_FOLDER", Path(self._tmp.name))
- self._chunks.start()
- self._sleep = patch("converter.tts.time.sleep")
+ self._sleep = patch("converter.clients.base.time.sleep")
self._sleep.start()
def tearDown(self):
self._sleep.stop()
- self._chunks.stop()
self._tmp.cleanup()
def _make_client(self):
client = FasterTTSClient.__new__(FasterTTSClient)
+ client.chunks_dir = Path(self._tmp.name)
client.voice = "default"
client.api_url = "http://127.0.0.1:8000"
return client
@@ -303,7 +325,7 @@ class FasterTTSClientGenerateTests(unittest.TestCase):
channels, sampwidth, framerate, frames = self._read_wav(path)
self.assertEqual(channels, 1)
self.assertEqual(sampwidth, 2)
- self.assertEqual(framerate, tts.SAMPLE_RATE)
+ self.assertEqual(framerate, SAMPLE_RATE)
self.assertEqual(frames, pcm)
def test_long_text_is_subchunked_and_concatenated_in_order(self):
@@ -360,7 +382,7 @@ class FasterTTSClientGenerateTests(unittest.TestCase):
response.read.return_value = body
return response
- with patch("converter.tts.urllib.request.urlopen",
+ with patch("converter.clients.faster.urllib.request.urlopen",
side_effect=[_response(b"")]) as mock_urlopen:
result = client.generate_chunk("Hello.", 1)
self.assertIsNone(result)
@@ -386,7 +408,7 @@ class FasterTTSClientGenerateTests(unittest.TestCase):
response = MagicMock()
response.__enter__.return_value = response
response.read.return_value = b"\x01\x00" * 10
- with patch("converter.tts.urllib.request.urlopen",
+ with patch("converter.clients.faster.urllib.request.urlopen",
return_value=response) as mock_urlopen:
pcm = client._request_pcm("Hello world.")
self.assertEqual(pcm, b"\x01\x00" * 10)
@@ -401,7 +423,7 @@ class FasterTTSClientGenerateTests(unittest.TestCase):
client = self._make_client()
text = " ".join(f"word{i}" for i in range(12))
# 12 words -> expected 4.8s, half is 2.4s -> 2.5s of audio passes.
- pcm = b"\x01\x00" * int(2.5 * tts.SAMPLE_RATE)
+ pcm = b"\x01\x00" * int(2.5 * SAMPLE_RATE)
with patch.object(client, "_request_pcm", return_value=pcm):
result = client.generate_chunk(text, 1)
self.assertIsNotNone(result)
@@ -412,16 +434,14 @@ class QwenTTSClientGenerateTests(unittest.TestCase):
def setUp(self):
self._tmp = tempfile.TemporaryDirectory()
- self._chunks = patch.object(tts, "CHUNKS_FOLDER", Path(self._tmp.name))
- self._chunks.start()
def tearDown(self):
- self._chunks.stop()
self._tmp.cleanup()
def _make_client(self):
client = QwenTTSClient.__new__(QwenTTSClient)
- client.voice_mode = tts.VOICE_MODE_CUSTOM
+ client.chunks_dir = Path(self._tmp.name)
+ client.voice_mode = VOICE_MODE_CUSTOM
return client
@staticmethod
@@ -429,7 +449,7 @@ class QwenTTSClientGenerateTests(unittest.TestCase):
with wave.open(str(path), "wb") as wav_file:
wav_file.setnchannels(1)
wav_file.setsampwidth(2)
- wav_file.setframerate(tts.SAMPLE_RATE)
+ wav_file.setframerate(SAMPLE_RATE)
wav_file.writeframes(frames)
return path
@@ -513,17 +533,17 @@ class AudioCppTTSClientHealthTests(unittest.TestCase):
return _dispatch
def _client(self, voice=None, language=None, model_id=None, **kwargs):
- with patch("converter.tts.urllib.request.urlopen",
+ with patch("converter.clients.faster.urllib.request.urlopen",
side_effect=self._get_responses(**kwargs)):
- return AudioCppTTSClient(voice=voice, language=language,
- model_id=model_id)
+ return AudioCppTTSClient(_DUMMY_CHUNKS, voice=voice,
+ language=language, model_id=model_id)
def test_unreachable_server_raises_with_readme_pointer(self):
import urllib.error
- with patch("converter.tts.urllib.request.urlopen",
+ with patch("converter.clients.faster.urllib.request.urlopen",
side_effect=urllib.error.URLError("Connection refused")):
with self.assertRaises(RuntimeError) as ctx:
- AudioCppTTSClient()
+ AudioCppTTSClient(_DUMMY_CHUNKS)
message = str(ctx.exception)
self.assertIn("not reachable", message)
self.assertIn("README", message)
@@ -625,9 +645,9 @@ class AudioCppTTSClientHealthTests(unittest.TestCase):
self.assertEqual(client.voice, "narrator")
def test_invalid_language_fails_before_connect(self):
- with patch("converter.tts.urllib.request.urlopen") as mock_urlopen:
+ with patch("converter.clients.faster.urllib.request.urlopen") as mock_urlopen:
with self.assertRaises(ValueError):
- AudioCppTTSClient(language="klingon")
+ AudioCppTTSClient(_DUMMY_CHUNKS, language="klingon")
mock_urlopen.assert_not_called()
def test_explicit_language_normalized(self):
@@ -651,7 +671,7 @@ class AudioCppTTSClientHealthTests(unittest.TestCase):
def test_preset_mode_falls_back_when_clone_model_not_on_server(self):
with patch.object(config, "AUDIOCPP_MODEL_ID", "qwen3-tts"), \
patch.object(config, "AUDIOCPP_CLONE_MODEL_ID", "qwen3-tts-clone"), \
- self.assertLogs("converter.tts", level="WARNING") as logs:
+ self.assertLogs("converter.clients.audiocpp", level="WARNING") as logs:
client = self._client(
voice="narrator",
models={"data": [{"id": "qwen3-tts", "family": "qwen3_tts"},
@@ -730,7 +750,7 @@ class AudioCppTTSClientHealthTests(unittest.TestCase):
# requirement error lists both configured ids instead.
with patch.object(config, "AUDIOCPP_MODEL_ID", "qwen3-tts"), \
patch.object(config, "AUDIOCPP_CLONE_MODEL_ID", "qwen3-tts-clone"), \
- self.assertNoLogs("converter.tts", level="WARNING"):
+ self.assertNoLogs("converter.clients.audiocpp", level="WARNING"):
with self.assertRaises(RuntimeError) as ctx:
self._client(voice="narrator",
models={"data": [{"id": "pocket-tts"}]})
@@ -774,16 +794,17 @@ class AudioCppTaskDetectionTests(unittest.TestCase):
return self._json_response({"voices": ["narrator"]})
raise AssertionError(f"unexpected URL: {url}")
- with patch("converter.tts.urllib.request.urlopen",
+ with patch("converter.clients.faster.urllib.request.urlopen",
side_effect=_dispatch):
- return AudioCppTTSClient(voice=voice, instructions=instructions,
+ return AudioCppTTSClient(_DUMMY_CHUNKS, voice=voice,
+ instructions=instructions,
request_options=request_options)
def test_missing_task_falls_back_to_tts(self):
# Servers that predate the task field hosted plain TTS models.
client = self._client(models={"data": [
{"id": config.AUDIOCPP_MODEL_ID, "family": "qwen3_tts"}]})
- self.assertEqual(client.task, tts.AUDIOCPP_TASK_TTS)
+ self.assertEqual(client.task, AUDIOCPP_TASK_TTS)
self.assertFalse(client.design_mode)
def test_task_detected_from_models_endpoint(self):
@@ -791,7 +812,7 @@ class AudioCppTaskDetectionTests(unittest.TestCase):
{"id": config.AUDIOCPP_MODEL_ID, "family": "qwen3_tts",
"task": "vdes"}]},
instructions="A warm adult narrator")
- self.assertEqual(client.task, tts.AUDIOCPP_TASK_VDES)
+ self.assertEqual(client.task, AUDIOCPP_TASK_VDES)
self.assertTrue(client.design_mode)
def test_clon_task_entry_connects_in_preset_mode(self):
@@ -912,15 +933,15 @@ class AudioCppFamilyDetectionTests(unittest.TestCase):
return self._json_response({"voices": [voice] if voice else []})
raise AssertionError(f"unexpected URL: {url}")
- with patch("converter.tts.urllib.request.urlopen",
+ with patch("converter.clients.faster.urllib.request.urlopen",
side_effect=_dispatch):
- return AudioCppTTSClient(voice=voice)
+ return AudioCppTTSClient(_DUMMY_CHUNKS, voice=voice)
def test_family_detected_from_models_endpoint(self):
client = self._client(models={"data": [
{"id": config.AUDIOCPP_MODEL_ID, "family": "higgs_audio_tts"}]})
self.assertEqual(client.family, "higgs_audio_tts")
- self.assertIs(client.profile, tts.AUDIOCPP_DEFAULT_FAMILY_PROFILE)
+ self.assertIs(client.profile, AUDIOCPP_DEFAULT_FAMILY_PROFILE)
def test_missing_family_uses_generic_profile(self):
# A missing family is unknown (not guessed as qwen3_tts): it falls
@@ -928,14 +949,14 @@ class AudioCppFamilyDetectionTests(unittest.TestCase):
client = self._client(models={"data": [
{"id": config.AUDIOCPP_MODEL_ID}]})
self.assertEqual(client.family, "")
- self.assertIs(client.profile, tts.AUDIOCPP_DEFAULT_FAMILY_PROFILE)
+ self.assertIs(client.profile, AUDIOCPP_DEFAULT_FAMILY_PROFILE)
def test_unknown_family_uses_generic_profile(self):
client = self._client(models={"data": [
{"id": config.AUDIOCPP_MODEL_ID, "family": "future_tts"}]})
self.assertEqual(client.family, "future_tts")
- self.assertIs(client.profile, tts.AUDIOCPP_DEFAULT_FAMILY_PROFILE)
- self.assertEqual(client.profile.language_style, tts.AUDIOCPP_LANG_OMIT)
+ self.assertIs(client.profile, AUDIOCPP_DEFAULT_FAMILY_PROFILE)
+ self.assertEqual(client.profile.language_style, AUDIOCPP_LANG_OMIT)
def test_speaker_mode_rejected_for_clone_only_family(self):
client = None
@@ -977,7 +998,7 @@ class AudioCppFamilyDetectionTests(unittest.TestCase):
def test_clone_model_id_of_different_family_is_ignored(self):
with patch.object(config, "AUDIOCPP_MODEL_ID", "higgs"), \
patch.object(config, "AUDIOCPP_CLONE_MODEL_ID", "qwen-clone"), \
- self.assertLogs("converter.tts", level="WARNING") as logs:
+ self.assertLogs("converter.clients.audiocpp", level="WARNING") as logs:
client = self._client(models={"data": [
{"id": "higgs", "family": "higgs_audio_tts"},
{"id": "qwen-clone", "family": "qwen3_tts"}]})
@@ -989,7 +1010,7 @@ class AudioCppFamilyDetectionTests(unittest.TestCase):
def test_clone_model_id_missing_on_non_qwen_server_is_debug_only(self):
with patch.object(config, "AUDIOCPP_MODEL_ID", "higgs"), \
patch.object(config, "AUDIOCPP_CLONE_MODEL_ID", "qwen-clone"), \
- self.assertNoLogs("converter.tts", level="WARNING"):
+ self.assertNoLogs("converter.clients.audiocpp", level="WARNING"):
client = self._client(models={"data": [
{"id": "higgs", "family": "higgs_audio_tts"}]})
self.assertEqual(client.model_id, "higgs")
@@ -997,7 +1018,7 @@ class AudioCppFamilyDetectionTests(unittest.TestCase):
def test_clone_model_id_missing_on_qwen_server_still_warns(self):
with patch.object(config, "AUDIOCPP_MODEL_ID", "qwen3-tts"), \
patch.object(config, "AUDIOCPP_CLONE_MODEL_ID", "qwen3-tts-clone"), \
- self.assertLogs("converter.tts", level="WARNING") as logs:
+ self.assertLogs("converter.clients.audiocpp", level="WARNING") as logs:
client = self._client(models={"data": [
{"id": "qwen3-tts", "family": "qwen3_tts"},
{"id": "pocket-tts", "family": "pocket_tts"}]})
@@ -1005,54 +1026,54 @@ class AudioCppFamilyDetectionTests(unittest.TestCase):
self.assertTrue(any("qwen3-tts-clone" in line for line in logs.output))
def test_iso_language_code_helper(self):
- self.assertEqual(tts.LANGUAGE_ISO_CODES["English"], "en")
- self.assertIsNone(tts.LANGUAGE_ISO_CODES.get("Auto"))
+ self.assertEqual(LANGUAGE_ISO_CODES["English"], "en")
+ self.assertIsNone(LANGUAGE_ISO_CODES.get("Auto"))
class AudiocppEntryVoiceCapabilityTests(unittest.TestCase):
"""The per-entry voice capability resolver (speaker/clone/design)."""
def _cap(self, family="", task="tts", model_id=""):
- return tts.audiocpp_entry_voice_capability(family, task, model_id)
+ return audiocpp_entry_voice_capability(family, task, model_id)
def test_vdes_task_is_design(self):
self.assertEqual(self._cap("qwen3_tts", "vdes",
"Qwen3-TTS-VoiceDesign-GGUF"),
- tts.AUDIOCPP_VOICE_DESIGN)
+ AUDIOCPP_VOICE_DESIGN)
def test_qwen_customvoice_entry_is_speaker(self):
self.assertEqual(self._cap("qwen3_tts", "tts",
"Qwen3-TTS-12Hz-1.7B-CustomVoice-GGUF"),
- tts.AUDIOCPP_VOICE_SPEAKER)
+ AUDIOCPP_VOICE_SPEAKER)
def test_qwen_base_entry_is_clone(self):
self.assertEqual(self._cap("qwen3_tts", "tts",
"Qwen3-TTS-12Hz-1.7B-Base-GGUF"),
- tts.AUDIOCPP_VOICE_CLONE)
+ AUDIOCPP_VOICE_CLONE)
def test_qwen_unidentified_entry_is_clone(self):
self.assertEqual(self._cap("qwen3_tts", "tts", "qwen"),
- tts.AUDIOCPP_VOICE_CLONE)
+ AUDIOCPP_VOICE_CLONE)
def test_other_families_are_clone(self):
self.assertEqual(self._cap("higgs_audio_tts", "tts", "higgs"),
- tts.AUDIOCPP_VOICE_CLONE)
+ AUDIOCPP_VOICE_CLONE)
def test_missing_family_is_clone(self):
self.assertEqual(self._cap("", "tts", "legacy"),
- tts.AUDIOCPP_VOICE_CLONE)
+ AUDIOCPP_VOICE_CLONE)
def test_customvoice_match_is_case_insensitive(self):
self.assertEqual(self._cap("qwen3_tts", "tts",
"Qwen3-TTS-12Hz-1.7B-CUSTOMVOICE-GGUF"),
- tts.AUDIOCPP_VOICE_SPEAKER)
+ AUDIOCPP_VOICE_SPEAKER)
def test_customvoice_id_in_other_family_is_not_speaker(self):
# The "customvoice" substring only marks a speaker for the qwen3_tts
# family; another family with a lookalike id stays clone-only.
self.assertEqual(self._cap("future_tts", "tts",
"Qwen3-TTS-CustomVoice"),
- tts.AUDIOCPP_VOICE_CLONE)
+ AUDIOCPP_VOICE_CLONE)
class AudioCppTTSClientRequestTests(unittest.TestCase):
@@ -1060,21 +1081,18 @@ class AudioCppTTSClientRequestTests(unittest.TestCase):
def setUp(self):
self._tmp = tempfile.TemporaryDirectory()
- self._chunks = patch.object(tts, "CHUNKS_FOLDER", Path(self._tmp.name))
- self._chunks.start()
- self._sleep = patch("converter.tts.time.sleep")
+ self._sleep = patch("converter.clients.base.time.sleep")
self._sleep.start()
def tearDown(self):
self._sleep.stop()
- self._chunks.stop()
self._tmp.cleanup()
- @staticmethod
- def _make_client(preset_mode=False, voice="Vivian", language="English", seed=-1,
+ def _make_client(self, preset_mode=False, voice="Vivian", language="English", seed=-1,
family="qwen3_tts", task="tts",
instructions=None, request_options=None):
client = AudioCppTTSClient.__new__(AudioCppTTSClient)
+ client.chunks_dir = Path(self._tmp.name)
client.api_url = "http://127.0.0.1:8080"
client.model_id = config.AUDIOCPP_MODEL_ID
client.preset_mode = preset_mode
@@ -1083,23 +1101,23 @@ class AudioCppTTSClientRequestTests(unittest.TestCase):
client._seed = seed
client.family = family
client.task = task
- client.profile = tts.AUDIOCPP_FAMILY_PROFILES.get(
- family, tts.AUDIOCPP_DEFAULT_FAMILY_PROFILE)
+ client.profile = AUDIOCPP_FAMILY_PROFILES.get(
+ family, AUDIOCPP_DEFAULT_FAMILY_PROFILE)
client.instructions = instructions or ""
client.request_options = dict(request_options or {})
- client.design_mode = task == tts.AUDIOCPP_TASK_VDES
+ client.design_mode = task == AUDIOCPP_TASK_VDES
# Mirrors the connect-time rule: an instruction-defined voice on a
# clone-capable entry with no --voice (design mode takes precedence).
- capability = tts.audiocpp_entry_voice_capability(
+ capability = audiocpp_entry_voice_capability(
family, task, client.model_id)
client.instruction_voice = (
not preset_mode and not client.design_mode
- and capability == tts.AUDIOCPP_VOICE_CLONE
+ and capability == AUDIOCPP_VOICE_CLONE
and bool(client.instructions))
return client
@staticmethod
- def _wav_bytes(frames=b"\x01\x00" * 10, rate=tts.SAMPLE_RATE):
+ def _wav_bytes(frames=b"\x01\x00" * 10, rate=SAMPLE_RATE):
buffer = io.BytesIO()
with wave.open(buffer, "wb") as wav_file:
wav_file.setnchannels(1)
@@ -1117,7 +1135,7 @@ class AudioCppTTSClientRequestTests(unittest.TestCase):
def test_payload_includes_model_input_voice_language_and_seed(self):
client = self._make_client(preset_mode=True, voice="narrator",
language="Japanese", seed=1234)
- with patch("converter.tts.urllib.request.urlopen",
+ with patch("converter.clients.faster.urllib.request.urlopen",
return_value=self._post_response(self._wav_bytes())) as mock_urlopen:
client._request_wav("Hello world.")
request = mock_urlopen.call_args[0][0]
@@ -1133,7 +1151,7 @@ class AudioCppTTSClientRequestTests(unittest.TestCase):
def test_negative_seed_omitted_from_payload(self):
client = self._make_client(preset_mode=True, voice="narrator", seed=-1)
- with patch("converter.tts.urllib.request.urlopen",
+ with patch("converter.clients.faster.urllib.request.urlopen",
return_value=self._post_response(self._wav_bytes())) as mock_urlopen:
client._request_wav("Hello world.")
payload = json.loads(mock_urlopen.call_args[0][0].data.decode("utf-8"))
@@ -1142,7 +1160,7 @@ class AudioCppTTSClientRequestTests(unittest.TestCase):
def test_request_timeout_is_the_configured_api_timeout(self):
client = self._make_client()
long_text = " ".join(f"word{i}" for i in range(1500))
- with patch("converter.tts.urllib.request.urlopen",
+ with patch("converter.clients.faster.urllib.request.urlopen",
return_value=self._post_response(self._wav_bytes())) as mock_urlopen:
client._request_wav(long_text)
timeout = mock_urlopen.call_args[1]["timeout"]
@@ -1150,7 +1168,7 @@ class AudioCppTTSClientRequestTests(unittest.TestCase):
def test_speaker_mode_sends_instruct(self):
client = self._make_client(preset_mode=False)
- with patch("converter.tts.urllib.request.urlopen",
+ with patch("converter.clients.faster.urllib.request.urlopen",
return_value=self._post_response(self._wav_bytes())) as mock_urlopen:
client._request_wav("Hello.")
payload = json.loads(mock_urlopen.call_args[0][0].data.decode("utf-8"))
@@ -1160,7 +1178,7 @@ class AudioCppTTSClientRequestTests(unittest.TestCase):
# --instructions overrides the INSTRUCT default in speaker mode.
client = self._make_client(preset_mode=False,
instructions="Read whisper quiet.")
- with patch("converter.tts.urllib.request.urlopen",
+ with patch("converter.clients.faster.urllib.request.urlopen",
return_value=self._post_response(self._wav_bytes())) as mock_urlopen:
client._request_wav("Hello.")
payload = json.loads(mock_urlopen.call_args[0][0].data.decode("utf-8"))
@@ -1171,7 +1189,7 @@ class AudioCppTTSClientRequestTests(unittest.TestCase):
# instruction reach the model.
client = self._make_client(preset_mode=True, voice="narrator",
instructions="Calm and steady.")
- with patch("converter.tts.urllib.request.urlopen",
+ with patch("converter.clients.faster.urllib.request.urlopen",
return_value=self._post_response(self._wav_bytes())) as mock_urlopen:
client._request_wav("Hello.")
payload = json.loads(mock_urlopen.call_args[0][0].data.decode("utf-8"))
@@ -1181,7 +1199,7 @@ class AudioCppTTSClientRequestTests(unittest.TestCase):
def test_design_mode_payload_omits_voice_and_sends_instructions(self):
client = self._make_client(task="vdes",
instructions="A warm adult narrator")
- with patch("converter.tts.urllib.request.urlopen",
+ with patch("converter.clients.faster.urllib.request.urlopen",
return_value=self._post_response(self._wav_bytes())) as mock_urlopen:
client._request_wav("Hello.")
payload = json.loads(mock_urlopen.call_args[0][0].data.decode("utf-8"))
@@ -1193,7 +1211,7 @@ class AudioCppTTSClientRequestTests(unittest.TestCase):
# takes Qwen display names like the other variants.
client = self._make_client(task="vdes", language="Japanese",
instructions="A warm adult narrator")
- with patch("converter.tts.urllib.request.urlopen",
+ with patch("converter.clients.faster.urllib.request.urlopen",
return_value=self._post_response(self._wav_bytes())) as mock_urlopen:
client._request_wav("Hello.")
payload = json.loads(mock_urlopen.call_args[0][0].data.decode("utf-8"))
@@ -1204,7 +1222,7 @@ class AudioCppTTSClientRequestTests(unittest.TestCase):
# no speaker name is invented, the instruction carries the voice.
client = self._make_client(family="omnivoice",
instructions="female, young adult")
- with patch("converter.tts.urllib.request.urlopen",
+ with patch("converter.clients.faster.urllib.request.urlopen",
return_value=self._post_response(self._wav_bytes())) as mock_urlopen:
client._request_wav("Hello.")
payload = json.loads(mock_urlopen.call_args[0][0].data.decode("utf-8"))
@@ -1216,7 +1234,7 @@ class AudioCppTTSClientRequestTests(unittest.TestCase):
client = self._make_client(preset_mode=True, voice="narrator",
request_options={"emotion": "neutral",
"speed": "1.1"})
- with patch("converter.tts.urllib.request.urlopen",
+ with patch("converter.clients.faster.urllib.request.urlopen",
return_value=self._post_response(self._wav_bytes())) as mock_urlopen:
client._request_wav("Hello.")
payload = json.loads(mock_urlopen.call_args[0][0].data.decode("utf-8"))
@@ -1225,7 +1243,7 @@ class AudioCppTTSClientRequestTests(unittest.TestCase):
def test_empty_request_options_omit_options_field(self):
client = self._make_client(preset_mode=True, voice="narrator")
- with patch("converter.tts.urllib.request.urlopen",
+ with patch("converter.clients.faster.urllib.request.urlopen",
return_value=self._post_response(self._wav_bytes())) as mock_urlopen:
client._request_wav("Hello.")
payload = json.loads(mock_urlopen.call_args[0][0].data.decode("utf-8"))
@@ -1235,7 +1253,7 @@ class AudioCppTTSClientRequestTests(unittest.TestCase):
# Clone-only families (higgs_audio_tts, voxcpm2, ...) detect the
# language themselves and take no style instruction.
client = self._make_client(preset_mode=False, family="higgs_audio_tts")
- with patch("converter.tts.urllib.request.urlopen",
+ with patch("converter.clients.faster.urllib.request.urlopen",
return_value=self._post_response(self._wav_bytes())) as mock_urlopen:
client._request_wav("Hello.")
payload = json.loads(mock_urlopen.call_args[0][0].data.decode("utf-8"))
@@ -1245,7 +1263,7 @@ class AudioCppTTSClientRequestTests(unittest.TestCase):
def test_iso_family_sends_language_code(self):
client = self._make_client(preset_mode=True, voice="narrator",
language="Japanese", family="index_tts2")
- with patch("converter.tts.urllib.request.urlopen",
+ with patch("converter.clients.faster.urllib.request.urlopen",
return_value=self._post_response(self._wav_bytes())) as mock_urlopen:
client._request_wav("Hello.")
payload = json.loads(mock_urlopen.call_args[0][0].data.decode("utf-8"))
@@ -1254,7 +1272,7 @@ class AudioCppTTSClientRequestTests(unittest.TestCase):
def test_iso_family_auto_omits_language(self):
client = self._make_client(preset_mode=True, voice="narrator",
language="Auto", family="index_tts2")
- with patch("converter.tts.urllib.request.urlopen",
+ with patch("converter.clients.faster.urllib.request.urlopen",
return_value=self._post_response(self._wav_bytes())) as mock_urlopen:
client._request_wav("Hello.")
payload = json.loads(mock_urlopen.call_args[0][0].data.decode("utf-8"))
@@ -1263,7 +1281,7 @@ class AudioCppTTSClientRequestTests(unittest.TestCase):
def test_qwen_language_display_name_still_sent(self):
client = self._make_client(preset_mode=True, voice="narrator",
language="Japanese", family="qwen3_tts")
- with patch("converter.tts.urllib.request.urlopen",
+ with patch("converter.clients.faster.urllib.request.urlopen",
return_value=self._post_response(self._wav_bytes())) as mock_urlopen:
client._request_wav("Hello.")
payload = json.loads(mock_urlopen.call_args[0][0].data.decode("utf-8"))
@@ -1272,7 +1290,7 @@ class AudioCppTTSClientRequestTests(unittest.TestCase):
def test_non_wav_response_rejected(self):
client = self._make_client()
for body in (b"", b"RIFFxxxx", b"MP3DATA-MP3DATA", b"RIFF\x00\x00\x00\x00mpeg"):
- with patch("converter.tts.urllib.request.urlopen",
+ with patch("converter.clients.faster.urllib.request.urlopen",
return_value=self._post_response(body)):
with self.assertRaises(RuntimeError):
client._request_wav("Hello.")
@@ -1283,7 +1301,7 @@ class AudioCppTTSClientRequestTests(unittest.TestCase):
error = urllib.error.HTTPError(
"http://127.0.0.1:8080/v1/audio/speech", 500,
"Server Error", {}, io.BytesIO(b'{"error":"bad voice"}'))
- with patch("converter.tts.urllib.request.urlopen", side_effect=error):
+ with patch("converter.clients.faster.urllib.request.urlopen", side_effect=error):
with self.assertRaises(RuntimeError) as ctx:
client._request_wav("Hello.")
self.assertIn("500", str(ctx.exception))
@@ -1327,7 +1345,7 @@ class AudioCppTTSClientRequestTests(unittest.TestCase):
with wave.open(str(path), "rb") as wav_file:
self.assertEqual(wav_file.getnchannels(), 1)
self.assertEqual(wav_file.getsampwidth(), 2)
- self.assertEqual(wav_file.getframerate(), tts.SAMPLE_RATE)
+ self.assertEqual(wav_file.getframerate(), SAMPLE_RATE)
self.assertEqual(wav_file.readframes(wav_file.getnframes()), frames)
def test_long_text_is_subchunked_and_concatenated_in_order(self):
@@ -1360,16 +1378,13 @@ class AudioCppHeartbeatTests(unittest.TestCase):
def setUp(self):
self._tmp = tempfile.TemporaryDirectory()
- self._chunks = patch.object(tts, "CHUNKS_FOLDER", Path(self._tmp.name))
- self._chunks.start()
def tearDown(self):
- self._chunks.stop()
self._tmp.cleanup()
- @staticmethod
- def _client():
+ def _client(self):
client = AudioCppTTSClient.__new__(AudioCppTTSClient)
+ client.chunks_dir = Path(self._tmp.name)
client.api_url = "http://127.0.0.1:8080"
client.model_id = config.AUDIOCPP_MODEL_ID
client.preset_mode = False
@@ -1377,7 +1392,7 @@ class AudioCppHeartbeatTests(unittest.TestCase):
client.language = "English"
client._seed = -1
client.family = "qwen3_tts"
- client.profile = tts.AUDIOCPP_DEFAULT_FAMILY_PROFILE
+ client.profile = AUDIOCPP_DEFAULT_FAMILY_PROFILE
return client
@staticmethod
@@ -1386,7 +1401,7 @@ class AudioCppHeartbeatTests(unittest.TestCase):
with wave.open(buffer, "wb") as wav_file:
wav_file.setnchannels(1)
wav_file.setsampwidth(2)
- wav_file.setframerate(tts.SAMPLE_RATE)
+ wav_file.setframerate(SAMPLE_RATE)
wav_file.writeframes(b"\x01\x00" * 10)
return buffer.getvalue()
@@ -1416,15 +1431,13 @@ class AudioCppTTSClientTruncationTests(unittest.TestCase):
def setUp(self):
self._tmp = tempfile.TemporaryDirectory()
- self._chunks = patch.object(tts, "CHUNKS_FOLDER", Path(self._tmp.name))
- self._chunks.start()
def tearDown(self):
- self._chunks.stop()
self._tmp.cleanup()
def _make_client(self):
client = AudioCppTTSClient.__new__(AudioCppTTSClient)
+ client.chunks_dir = Path(self._tmp.name)
client.api_url = "http://127.0.0.1:8080"
client.model_id = config.AUDIOCPP_MODEL_ID
client.preset_mode = True
@@ -1432,7 +1445,7 @@ class AudioCppTTSClientTruncationTests(unittest.TestCase):
client.language = "English"
client._seed = -1
client.family = "qwen3_tts"
- client.profile = tts.AUDIOCPP_FAMILY_PROFILES["qwen3_tts"]
+ client.profile = AUDIOCPP_FAMILY_PROFILES["qwen3_tts"]
return client
@staticmethod
@@ -1441,7 +1454,7 @@ class AudioCppTTSClientTruncationTests(unittest.TestCase):
with wave.open(buffer, "wb") as wav_file:
wav_file.setnchannels(1)
wav_file.setsampwidth(2)
- wav_file.setframerate(tts.SAMPLE_RATE)
+ wav_file.setframerate(SAMPLE_RATE)
wav_file.writeframes(frames)
return buffer.getvalue()
@@ -1449,7 +1462,7 @@ class AudioCppTTSClientTruncationTests(unittest.TestCase):
client = self._make_client()
text = " ".join(f"word{i}" for i in range(12))
# 12 words -> expected 4.8s, half is 2.4s -> 2.5s of audio passes.
- wav = self._wav_bytes(b"\x01\x00" * int(2.5 * tts.SAMPLE_RATE))
+ wav = self._wav_bytes(b"\x01\x00" * int(2.5 * SAMPLE_RATE))
with patch.object(client, "_request_wav", return_value=wav):
result = client.generate_chunk(text, 1)
self.assertIsNotNone(result)
@@ -1473,7 +1486,7 @@ class AudioCppUnloadModelsTests(unittest.TestCase):
def test_posts_to_unload_all_models(self):
client = self._client()
- with patch("converter.tts.urllib.request.urlopen",
+ with patch("converter.clients.audiocpp.urllib.request.urlopen",
return_value=self._response(b'{"unloaded": ["qwen"]}')) as mock_urlopen:
client._unload_server_models()
request = mock_urlopen.call_args[0][0]
@@ -1485,7 +1498,7 @@ class AudioCppUnloadModelsTests(unittest.TestCase):
def test_reports_unloaded_ids(self):
client = self._client()
buf = io.StringIO()
- with patch("converter.tts.urllib.request.urlopen",
+ with patch("converter.clients.audiocpp.urllib.request.urlopen",
return_value=self._response(b'{"unloaded": ["a", "b"]}')), \
redirect_stdout(buf):
client._unload_server_models()
@@ -1495,7 +1508,7 @@ class AudioCppUnloadModelsTests(unittest.TestCase):
def test_no_loaded_models_is_silent(self):
client = self._client()
buf = io.StringIO()
- with patch("converter.tts.urllib.request.urlopen",
+ with patch("converter.clients.audiocpp.urllib.request.urlopen",
return_value=self._response(b'{"unloaded": []}')), \
redirect_stdout(buf):
client._unload_server_models()
@@ -1504,8 +1517,8 @@ class AudioCppUnloadModelsTests(unittest.TestCase):
def test_http_error_warns_and_continues(self):
client = self._client()
buf = io.StringIO()
- with patch("converter.tts.urllib.request.urlopen",
- side_effect=tts.urllib.error.HTTPError(
+ with patch("converter.clients.audiocpp.urllib.request.urlopen",
+ side_effect=urllib.error.HTTPError(
"http://127.0.0.1:8080/v1/tasks/unload_all_models",
404, "Not Found", None, io.BytesIO())), \
redirect_stdout(buf):
@@ -1517,8 +1530,8 @@ class AudioCppUnloadModelsTests(unittest.TestCase):
def test_connection_error_warns_and_continues(self):
client = self._client()
buf = io.StringIO()
- with patch("converter.tts.urllib.request.urlopen",
- side_effect=tts.urllib.error.URLError("refused")), \
+ with patch("converter.clients.audiocpp.urllib.request.urlopen",
+ side_effect=urllib.error.URLError("refused")), \
redirect_stdout(buf):
client._unload_server_models()
self.assertIn("[WARNING]", buf.getvalue())
@@ -1532,8 +1545,8 @@ class AudioCppUnloadModelsTests(unittest.TestCase):
client.language = "English"
client._seed = -1
client.family = "qwen3_tts"
- client.task = tts.AUDIOCPP_TASK_TTS
- client.profile = tts.AUDIOCPP_FAMILY_PROFILES["qwen3_tts"]
+ client.task = AUDIOCPP_TASK_TTS
+ client.profile = AUDIOCPP_FAMILY_PROFILES["qwen3_tts"]
client.design_mode = False
client.instruction_voice = False
client.speaker_mode = False
@@ -1562,8 +1575,8 @@ class AudioCppUnloadModelsTests(unittest.TestCase):
client.language = "English"
client._seed = -1
client.family = "qwen3_tts"
- client.task = tts.AUDIOCPP_TASK_TTS
- client.profile = tts.AUDIOCPP_FAMILY_PROFILES["qwen3_tts"]
+ client.task = AUDIOCPP_TASK_TTS
+ client.profile = AUDIOCPP_FAMILY_PROFILES["qwen3_tts"]
client.design_mode = False
client.instruction_voice = False
client.speaker_mode = False
@@ -1579,7 +1592,7 @@ class AudioCppUnloadModelsTests(unittest.TestCase):
patch.object(client, "_resolve_family"), \
patch.object(client, "_resolve_task"), \
patch.object(client, "_check_voice"), \
- patch.object(tts.config, "AUDIOCPP_UNLOAD_MODELS", False), \
+ patch.object(config, "AUDIOCPP_UNLOAD_MODELS", False), \
patch.object(client, "_unload_server_models") as mock_unload:
client._connect()
mock_unload.assert_not_called()
@@ -1592,9 +1605,10 @@ class BackendWiringTests(unittest.TestCase):
with patch("converter.converter.FasterTTSClient") as mock_faster, \
patch("converter.converter.QwenTTSClient") as mock_qwen, \
patch("converter.converter.AudioCppTTSClient") as mock_audiocpp:
- AudiobookConverter(voice_mode=tts.VOICE_MODE_CLONE,
- backend=tts.BACKEND_FASTER, voice="narrator")
- mock_faster.assert_called_once_with(voice="narrator", api_url=None,
+ AudiobookConverter(voice_mode=VOICE_MODE_CLONE,
+ backend=BACKEND_FASTER, voice="narrator")
+ mock_faster.assert_called_once_with(chunks_dir=converter_mod.CHUNKS_FOLDER,
+ voice="narrator", api_url=None,
quiet=False)
mock_qwen.assert_not_called()
mock_audiocpp.assert_not_called()
@@ -1603,10 +1617,11 @@ class BackendWiringTests(unittest.TestCase):
with patch("converter.converter.FasterTTSClient") as mock_faster, \
patch("converter.converter.QwenTTSClient") as mock_qwen, \
patch("converter.converter.AudioCppTTSClient") as mock_audiocpp:
- AudiobookConverter(voice_mode=tts.VOICE_MODE_CLONE,
- backend=tts.BACKEND_AUDIOCPP, voice="narrator",
+ AudiobookConverter(voice_mode=VOICE_MODE_CLONE,
+ backend=BACKEND_AUDIOCPP, voice="narrator",
language="ja")
- mock_audiocpp.assert_called_once_with(voice="narrator", language="Japanese",
+ mock_audiocpp.assert_called_once_with(chunks_dir=converter_mod.CHUNKS_FOLDER,
+ voice="narrator", language="Japanese",
model_id=None,
instructions=None,
request_options={},
@@ -1616,9 +1631,10 @@ class BackendWiringTests(unittest.TestCase):
def test_audiocpp_backend_without_voice_uses_audiocpp_client(self):
with patch("converter.converter.AudioCppTTSClient") as mock_audiocpp:
- AudiobookConverter(voice_mode=tts.VOICE_MODE_CUSTOM,
- backend=tts.BACKEND_AUDIOCPP)
- mock_audiocpp.assert_called_once_with(voice=None, language=config.LANGUAGE,
+ AudiobookConverter(voice_mode=VOICE_MODE_CUSTOM,
+ backend=BACKEND_AUDIOCPP)
+ mock_audiocpp.assert_called_once_with(chunks_dir=converter_mod.CHUNKS_FOLDER,
+ voice=None, language=config.LANGUAGE,
model_id=None,
instructions=None,
request_options={},
@@ -1626,22 +1642,24 @@ class BackendWiringTests(unittest.TestCase):
def test_audiocpp_backend_model_id_is_wired_through(self):
with patch("converter.converter.AudioCppTTSClient") as mock_audiocpp:
- AudiobookConverter(voice_mode=tts.VOICE_MODE_CLONE,
- backend=tts.BACKEND_AUDIOCPP, voice="narrator",
+ AudiobookConverter(voice_mode=VOICE_MODE_CLONE,
+ backend=BACKEND_AUDIOCPP, voice="narrator",
model_id="higgs")
mock_audiocpp.assert_called_once_with(
+ chunks_dir=converter_mod.CHUNKS_FOLDER,
voice="narrator", language=config.LANGUAGE,
model_id="higgs", instructions=None,
request_options={}, api_url=None, quiet=False)
def test_audiocpp_backend_instructions_and_options_are_wired_through(self):
with patch("converter.converter.AudioCppTTSClient") as mock_audiocpp:
- AudiobookConverter(voice_mode=tts.VOICE_MODE_CUSTOM,
- backend=tts.BACKEND_AUDIOCPP,
+ AudiobookConverter(voice_mode=VOICE_MODE_CUSTOM,
+ backend=BACKEND_AUDIOCPP,
instructions="A warm adult narrator",
request_options={"emotion": "neutral",
"speed": "1.1"})
mock_audiocpp.assert_called_once_with(
+ chunks_dir=converter_mod.CHUNKS_FOLDER,
voice=None, language=config.LANGUAGE,
model_id=None,
instructions="A warm adult narrator",
@@ -1652,8 +1670,8 @@ class BackendWiringTests(unittest.TestCase):
with patch("converter.converter.FasterTTSClient") as mock_faster, \
patch("converter.converter.QwenTTSClient") as mock_qwen, \
patch("converter.converter.AudioCppTTSClient") as mock_audiocpp:
- AudiobookConverter(voice_mode=tts.VOICE_MODE_CUSTOM,
- backend=tts.BACKEND_QWEN)
+ AudiobookConverter(voice_mode=VOICE_MODE_CUSTOM,
+ backend=BACKEND_QWEN)
mock_qwen.assert_called_once()
mock_faster.assert_not_called()
mock_audiocpp.assert_not_called()
@@ -1661,32 +1679,35 @@ class BackendWiringTests(unittest.TestCase):
def test_qwen_clone_mode_still_requires_reference(self):
with patch("converter.converter.QwenTTSClient"):
with self.assertRaises(ValueError):
- AudiobookConverter(voice_mode=tts.VOICE_MODE_CLONE,
- backend=tts.BACKEND_QWEN)
+ AudiobookConverter(voice_mode=VOICE_MODE_CLONE,
+ backend=BACKEND_QWEN)
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:
- AudiobookConverter(voice_mode=tts.VOICE_MODE_CLONE,
- backend=tts.BACKEND_AUDIOCPP, voice="narrator",
+ AudiobookConverter(voice_mode=VOICE_MODE_CLONE,
+ backend=BACKEND_AUDIOCPP, voice="narrator",
api_url="http://10.0.0.5:8080")
mock_audiocpp.assert_called_once_with(
+ chunks_dir=converter_mod.CHUNKS_FOLDER,
voice="narrator", language=config.LANGUAGE, model_id=None,
instructions=None, request_options={},
api_url="http://10.0.0.5:8080", quiet=False)
with patch("converter.converter.FasterTTSClient") as mock_faster:
- AudiobookConverter(voice_mode=tts.VOICE_MODE_CLONE,
- backend=tts.BACKEND_FASTER, voice="narrator",
+ AudiobookConverter(voice_mode=VOICE_MODE_CLONE,
+ backend=BACKEND_FASTER, voice="narrator",
api_url="http://10.0.0.5:8000")
- mock_faster.assert_called_once_with(voice="narrator",
+ mock_faster.assert_called_once_with(chunks_dir=converter_mod.CHUNKS_FOLDER,
+ voice="narrator",
api_url="http://10.0.0.5:8000",
quiet=False)
with patch("converter.converter.QwenTTSClient") as mock_qwen:
- AudiobookConverter(voice_mode=tts.VOICE_MODE_CUSTOM,
- backend=tts.BACKEND_QWEN,
+ AudiobookConverter(voice_mode=VOICE_MODE_CUSTOM,
+ backend=BACKEND_QWEN,
api_url="http://10.0.0.5:7860")
mock_qwen.assert_called_once_with(
- voice_mode=tts.VOICE_MODE_CUSTOM, voice_clone_ref_audio=None,
+ 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)
@@ -1695,8 +1716,8 @@ class BackendWiringTests(unittest.TestCase):
# Cloning is server-side for the audiocpp backend, so the
# clone-mode voice can be selected without local reference audio.
with patch("converter.converter.AudioCppTTSClient"):
- converter = AudiobookConverter(voice_mode=tts.VOICE_MODE_CLONE,
- backend=tts.BACKEND_AUDIOCPP,
+ converter = AudiobookConverter(voice_mode=VOICE_MODE_CLONE,
+ backend=BACKEND_AUDIOCPP,
voice="narrator")
self.assertIsNone(converter.voice_clone_ref_audio)
@@ -1710,8 +1731,8 @@ class BackendWiringTests(unittest.TestCase):
def test_chapter_chunks_qwen_always_splits(self):
with patch("converter.converter.QwenTTSClient"):
- converter = AudiobookConverter(voice_mode=tts.VOICE_MODE_CUSTOM,
- backend=tts.BACKEND_QWEN)
+ converter = AudiobookConverter(voice_mode=VOICE_MODE_CUSTOM,
+ backend=BACKEND_QWEN)
text = " ".join(f"word{i}" for i in range(50))
with patch.object(config, "CHUNK_SIZE", 10):
chunks = converter._chapter_chunks(text)
@@ -1720,27 +1741,27 @@ class BackendWiringTests(unittest.TestCase):
def test_faster_backend_still_validates_other_settings(self):
with patch("converter.converter.FasterTTSClient"):
with self.assertRaises(ValueError):
- AudiobookConverter(backend=tts.BACKEND_FASTER, speed=0)
+ AudiobookConverter(backend=BACKEND_FASTER, speed=0)
with self.assertRaises(ValueError):
- AudiobookConverter(backend=tts.BACKEND_FASTER, language="klingon")
+ AudiobookConverter(backend=BACKEND_FASTER, language="klingon")
def test_audiocpp_backend_still_validates_other_settings(self):
with patch("converter.converter.AudioCppTTSClient"):
with self.assertRaises(ValueError):
- AudiobookConverter(backend=tts.BACKEND_AUDIOCPP, speed=0)
+ AudiobookConverter(backend=BACKEND_AUDIOCPP, speed=0)
with self.assertRaises(ValueError):
- AudiobookConverter(backend=tts.BACKEND_AUDIOCPP, language="klingon")
+ AudiobookConverter(backend=BACKEND_AUDIOCPP, language="klingon")
def _faster_converter(self, voice=None):
with patch("converter.converter.FasterTTSClient"):
- return AudiobookConverter(voice_mode=tts.VOICE_MODE_CLONE,
- backend=tts.BACKEND_FASTER, voice=voice)
+ return AudiobookConverter(voice_mode=VOICE_MODE_CLONE,
+ backend=BACKEND_FASTER, voice=voice)
def _audiocpp_converter(self, voice=None):
with patch("converter.converter.AudioCppTTSClient"):
return AudiobookConverter(
- voice_mode=tts.VOICE_MODE_CLONE if voice else tts.VOICE_MODE_CUSTOM,
- backend=tts.BACKEND_AUDIOCPP, voice=voice)
+ voice_mode=VOICE_MODE_CLONE if voice else VOICE_MODE_CUSTOM,
+ backend=BACKEND_AUDIOCPP, voice=voice)
def test_narrator_tag_uses_faster_voice_name(self):
converter = self._faster_converter(voice="male_richard_poe")
@@ -1783,9 +1804,9 @@ class BackendWiringTests(unittest.TestCase):
ref = Path(tmp) / "ref.wav"
ref.write_bytes(b"x")
with patch("converter.converter.QwenTTSClient"):
- converter = AudiobookConverter(voice_mode=tts.VOICE_MODE_CLONE,
+ converter = AudiobookConverter(voice_mode=VOICE_MODE_CLONE,
voice_clone_ref_audio=str(ref),
- backend=tts.BACKEND_QWEN)
+ backend=BACKEND_QWEN)
self.assertEqual(converter._narrator_tag(), "ref")