aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/converter/audio.py6
-rw-r--r--app/converter/converter.py34
-rw-r--r--app/converter/tts.py135
-rw-r--r--app/tests/test_tts.py58
4 files changed, 113 insertions, 120 deletions
diff --git a/app/converter/audio.py b/app/converter/audio.py
index 81431cb..eb970ff 100644
--- a/app/converter/audio.py
+++ b/app/converter/audio.py
@@ -15,6 +15,10 @@ logger = logging.getLogger(__name__)
CHUNKS_FOLDER = Path(__file__).resolve().parent.parent.parent / "app" / "chunks"
+# Tolerance for "is this speed 1.0?" comparisons (banner display, atempo
+# filter elision); shared by every speed check.
+SPEED_EPSILON = 1e-6
+
def atempo_filters(speed: float) -> str:
"""Return a comma-joined ffmpeg ``atempo`` filter chain for ``speed``.
@@ -24,7 +28,7 @@ def atempo_filters(speed: float) -> str:
"""
if speed <= 0:
raise ValueError(f"Speed must be a positive number, got {speed}")
- if abs(speed - 1.0) < 1e-6:
+ if abs(speed - 1.0) < SPEED_EPSILON:
return ""
remaining = float(speed)
chain = []
diff --git a/app/converter/converter.py b/app/converter/converter.py
index 455c1fb..1abc85c 100644
--- a/app/converter/converter.py
+++ b/app/converter/converter.py
@@ -64,7 +64,9 @@ def setup_logging(debug: bool = False, console: bool = True) -> None:
(DEBUG with --debug) so progress prints are never mirrored as
timestamped log lines; httpx/httpcore request logs stay file-only.
CONSOLE=False (the TUI run view owns the screen) keeps every record
- in the file only.
+ in the file only. Safe to call repeatedly in one process (the hub
+ calls it once per conversion run): force=True replaces the previous
+ handlers instead of silently keeping them.
"""
LOGS_FOLDER.mkdir(parents=True, exist_ok=True)
file_handler = logging.FileHandler(
@@ -82,9 +84,12 @@ def setup_logging(debug: bool = False, console: bool = True) -> None:
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
handlers=handlers,
+ force=True,
)
if debug:
logging.getLogger("converter").setLevel(logging.DEBUG)
+ else:
+ logging.getLogger("converter").setLevel(logging.INFO)
def setup_directories() -> None:
@@ -215,10 +220,16 @@ class AudiobookConverter:
self.instructions = instructions
self.request_options = dict(request_options or {})
self._validate_configuration()
+ # Interactive reporting (the TUI run view): PROGRESS receives an
+ # event dict per state change and turns the clients' console prints
+ # off (quiet is set at construction so connect-time lines respect it
+ # too); CANCEL (a threading.Event) stops the run between requests.
+ quiet = progress is not None
if backend == BACKEND_FASTER:
# The faster backend always voice-clones using a reference voice
# configured on the server, so no local reference audio is needed.
- self.tts = FasterTTSClient(voice=voice, api_url=api_url)
+ self.tts = FasterTTSClient(voice=voice, api_url=api_url,
+ quiet=quiet)
elif backend == BACKEND_AUDIOCPP:
# --voice picks the voice: a built-in speaker name on the
# CustomVoice entry, or a server-side preset (cloning)
@@ -230,7 +241,7 @@ class AudiobookConverter:
model_id=model_id,
instructions=instructions,
request_options=self.request_options,
- api_url=api_url)
+ api_url=api_url, quiet=quiet)
else:
self.tts = QwenTTSClient(
voice_mode=voice_mode,
@@ -239,14 +250,10 @@ class AudiobookConverter:
skip_transcription=skip_transcription,
language=self.language,
api_url=api_url,
+ quiet=quiet,
)
- # Interactive reporting/cancellation (the TUI run view): PROGRESS
- # receives an event dict per state change and turns the console
- # prints off (the view owns the screen); CANCEL (a
- # threading.Event) stops the run between requests.
self._progress = progress
self.tts.cancel = cancel
- self.tts.quiet = progress is not None
def _emit(self, event: dict) -> None:
"""Send one progress event (a no-op without a progress callback)."""
@@ -723,11 +730,11 @@ class AudiobookConverter:
self._say(f"Output format: {self.output_format}")
if self.single_file and self.output_format != "m4b":
self._say("Chapter mode: single file (--single-file)")
- if abs(self.speed - 1.0) >= 1e-6:
+ if abs(self.speed - 1.0) >= audio.SPEED_EPSILON:
self._say(f"Playback speed: {self.speed:g}x")
if self.debug:
self._say(f"Debug dumps (per-chunk text + raw audio): {DEBUG_FOLDER}")
- print("=" * 70)
+ self._say("=" * 70)
# ------------------------------------------------------------------
# Pre-flight: overwrite checks before connecting to a TTS server
@@ -857,11 +864,14 @@ class AudiobookConverter:
successful = sum(results.values())
total = len(results)
+ # A cancelled run is not a successful run on either path (the TUI
+ # event consumer and the console summary report it consistently).
+ ok = not cancelled and total > 0 and successful == total
self._emit({"kind": "done", "ok": successful,
"total": total or len(planned), "cancelled": cancelled})
if self._progress is not None:
- return not cancelled and total > 0 and successful == total
+ return ok
print("\n" + "=" * 70)
print("CONVERSION SUMMARY")
@@ -888,4 +898,4 @@ class AudiobookConverter:
print(f"\n[INFO] Generation completed in {duration}")
logger.info("Generation completed in %s", duration)
- return total > 0 and successful == total
+ return ok
diff --git a/app/converter/tts.py b/app/converter/tts.py
index 7204e0c..8130a44 100644
--- a/app/converter/tts.py
+++ b/app/converter/tts.py
@@ -370,6 +370,11 @@ class _BaseTTSClient:
cancel = None
quiet = False
+ def _report(self, message: str) -> None:
+ """Print a console line unless quiet (the run view owns the screen)."""
+ if not self.quiet:
+ print(message)
+
def generate_chunk(self, text: str, chunk_num: int) -> Optional[str]:
"""Generate one audio chunk; returns its path in the chunks folder."""
raise NotImplementedError
@@ -463,7 +468,11 @@ class QwenTTSClient(_BaseTTSClient):
def __init__(self, voice_mode: str = "custom_voice", voice_clone_ref_audio: Optional[str] = None,
voice_clone_ref_text: Optional[str] = None, skip_transcription: bool = False,
- language: Optional[str] = None, api_url: Optional[str] = None):
+ language: Optional[str] = None, api_url: Optional[str] = None,
+ quiet: bool = False):
+ # Quiet before connecting so connect-time status lines never reach
+ # a screen the TUI run view owns.
+ self.quiet = bool(quiet)
if voice_mode not in VOICE_MODES:
raise ValueError(
f"Unknown voice mode: {voice_mode!r} (expected one of {VOICE_MODES})"
@@ -505,11 +514,11 @@ class QwenTTSClient(_BaseTTSClient):
# Voice clone uses the Base-model demo, which is a separate server
# from the CustomVoice demo (that one only exposes /run_instruct).
self._init_client(api_url, clone=True)
- print(f"[OK] Connected to Voice Clone API at {api_url}")
+ self._report(f"[OK] Connected to Voice Clone API at {api_url}")
self._resolve_reference_text()
else:
self._init_client(api_url, clone=False)
- print("[OK] Connected to Qwen API")
+ self._report("[OK] Connected to Qwen API")
except Exception as exc:
raise RuntimeError(
f"Qwen API initialization failed at {api_url}: {exc}. "
@@ -523,15 +532,16 @@ class QwenTTSClient(_BaseTTSClient):
transcription, then x-vector-only mode."""
if not self.voice_clone_ref_text and self.voice_clone_ref_audio:
if self.skip_transcription:
- print("[INFO] Skipping reference audio transcription (--no-transcription).")
+ self._report("[INFO] Skipping reference audio transcription (--no-transcription).")
else:
- print("[INFO] Transcribing reference audio for voice cloning...")
+ self._report("[INFO] Transcribing reference audio for voice cloning...")
self.voice_clone_ref_text = self.transcribe_audio(self.voice_clone_ref_audio) or ""
if not self.voice_clone_ref_text:
- print("[WARNING] No reference text available; using x-vector-only clone mode (lower quality).")
- print(' Pass --transcription "..." for higher-quality in-context cloning.')
+ self._report("[WARNING] No reference text available; using "
+ "x-vector-only clone mode (lower quality).")
+ self._report(' Pass --transcription "..." for higher-quality in-context cloning.')
else:
- print(f"[OK] Reference text:\n{self.voice_clone_ref_text}")
+ self._report(f"[OK] Reference text:\n{self.voice_clone_ref_text}")
def _init_client(self, url: str, clone: bool = False) -> None:
"""Initialize a Gradio client and store its API metadata.
@@ -762,7 +772,11 @@ class FasterTTSClient(_BaseTTSClient):
request, so long chunks are sub-chunked client-side.
"""
- def __init__(self, voice: Optional[str] = None, api_url: Optional[str] = None):
+ def __init__(self, voice: Optional[str] = None, api_url: Optional[str] = None,
+ quiet: bool = False):
+ # Quiet before connecting so connect-time status lines never reach
+ # a screen the TUI run view owns.
+ self.quiet = bool(quiet)
self.voice = voice or config.FASTER_VOICE
self.api_url = (api_url or config.FASTER_API_URL).rstrip("/")
self._check_health()
@@ -784,9 +798,9 @@ class FasterTTSClient(_BaseTTSClient):
"The faster TTS server is running but its model is not loaded yet; "
"wait for model download and startup to finish, then retry."
)
- print(f"[OK] Connected to faster TTS API at {self.api_url} (voice '{self.voice}')")
- print(f"[INFO] The server silently falls back to its first configured voice if "
- f"'{self.voice}' is not defined in its voice config (see README).")
+ self._report(f"[OK] Connected to faster TTS API at {self.api_url} (voice '{self.voice}')")
+ self._report(f"[INFO] The server silently falls back to its first configured voice if "
+ f"'{self.voice}' is not defined in its voice config (see README).")
# ------------------------------------------------------------------
# HTTP requests
@@ -819,23 +833,6 @@ class FasterTTSClient(_BaseTTSClient):
raise RuntimeError("Faster TTS server returned empty audio")
return pcm
- def _request_pcm_with_retry(self, text: str, chunk_num: int, sub_num: int,
- sub_total: int) -> bytes:
- """Request one sub-chunk, retrying transient failures."""
- for attempt in range(config.MAX_RETRIES):
- self._check_cancelled()
- try:
- return self._request_pcm(text)
- except ConversionCancelled:
- raise
- except Exception as exc:
- logger.warning("Chunk %d sub-chunk %d/%d attempt %d failed: %s",
- chunk_num, sub_num, sub_total, attempt + 1, exc)
- if attempt < config.MAX_RETRIES - 1:
- self._sleep(2 + 2 * attempt)
- raise RuntimeError(f"Sub-chunk {sub_num}/{sub_total} failed after "
- f"{config.MAX_RETRIES} attempts")
-
# ------------------------------------------------------------------
# Chunk generation
# ------------------------------------------------------------------
@@ -850,8 +847,7 @@ class FasterTTSClient(_BaseTTSClient):
pcm_parts: List[bytes] = []
with self._chunk_heartbeat(chunk_num):
for sub_num, sub_text in enumerate(sub_chunks, 1):
- pcm = self._request_pcm_with_retry(
- sub_text, chunk_num, sub_num, len(sub_chunks))
+ pcm = self._request_pcm(sub_text)
pcm_parts.append(pcm)
output_path = self._chunk_path(chunk_num, ".wav")
@@ -936,7 +932,11 @@ class AudioCppTTSClient(_BaseTTSClient):
api_url: Optional[str] = None,
model_id: Optional[str] = None,
instructions: Optional[str] = None,
- request_options: Optional[Dict[str, str]] = None):
+ request_options: Optional[Dict[str, str]] = None,
+ quiet: bool = False):
+ # Quiet before connecting so connect-time status lines never reach
+ # a screen the TUI run view owns.
+ self.quiet = bool(quiet)
self.api_url = (api_url or config.AUDIOCPP_API_URL).rstrip("/")
# Per-run model selection: the --model CLI flag overrides config; an
# empty value is resolved at connect time when the server hosts exactly
@@ -989,6 +989,12 @@ class AudioCppTTSClient(_BaseTTSClient):
# Connection
# ------------------------------------------------------------------
+ def _connected(self, mode: str) -> None:
+ """Report the resolved connection (MODE: speaker/voice/... label)."""
+ self._report(f"[OK] Connected to audio.cpp server at {self.api_url} "
+ f"(model '{self.model_id}', family '{self.family}', "
+ f"{mode})")
+
def _connect(self) -> None:
"""Health-check the server and resolve the model, family, task, and voice.
@@ -1025,9 +1031,7 @@ class AudioCppTTSClient(_BaseTTSClient):
self._require_synthesis_task(models)
self.voice = speaker_display_name_for(self.voice)
self.speaker_mode = True
- print(f"[OK] Connected to audio.cpp server at {self.api_url} "
- f"(model '{self.model_id}', family '{self.family}', "
- f"speaker '{self.voice}')")
+ self._connected(f"speaker '{self.voice}'")
if not self.speaker_mode:
# Server-side preset (--voice): validate it and route to
# the clone model entry when AUDIOCPP_CLONE_MODEL_ID is set.
@@ -1043,9 +1047,7 @@ class AudioCppTTSClient(_BaseTTSClient):
f"'{self.model_id}': the voice is described by the "
"--instructions text instead (see README).")
self._check_voice()
- print(f"[OK] Connected to audio.cpp server at {self.api_url} "
- f"(model '{self.model_id}', family '{self.family}', "
- f"voice '{self.voice}')")
+ self._connected(f"voice '{self.voice}'")
else:
# No flag: the entry's capability picks the default mode.
self._require_model_id(models)
@@ -1062,25 +1064,19 @@ class AudioCppTTSClient(_BaseTTSClient):
"description of the voice to synthesize with, e.g. "
'--instructions "A warm adult female narrator with a '
'British accent" (see README).')
- print(f"[OK] Connected to audio.cpp server at {self.api_url} "
- f"(model '{self.model_id}', family '{self.family}', "
- "voice design)")
- print(f"[INFO] Designing the voice from: {self.instructions}")
+ self._connected("voice design")
+ self._report(f"[INFO] Designing the voice from: {self.instructions}")
elif capability == AUDIOCPP_VOICE_SPEAKER:
# No flag on a CustomVoice entry: the built-in config.SPEAKER.
self.voice = speaker_display_name()
self.speaker_mode = True
- print(f"[OK] Connected to audio.cpp server at {self.api_url} "
- f"(model '{self.model_id}', family '{self.family}', "
- f"speaker '{self.voice}')")
+ self._connected(f"speaker '{self.voice}'")
elif self.instructions:
# Families without built-in speakers can still get their voice
# from the instruction alone (e.g. OmniVoice voice design).
self.instruction_voice = True
- print(f"[OK] Connected to audio.cpp server at {self.api_url} "
- f"(model '{self.model_id}', family '{self.family}', "
- "instruction voice)")
- print(f"[INFO] Designing the voice from: {self.instructions}")
+ self._connected("instruction voice")
+ self._report(f"[INFO] Designing the voice from: {self.instructions}")
else:
raise RuntimeError(
f"The audio.cpp model '{self.model_id}' (family "
@@ -1091,9 +1087,9 @@ class AudioCppTTSClient(_BaseTTSClient):
"families that support it, or select the CustomVoice entry "
"for built-in speakers (see README).")
if self.instructions and not self.design_mode and not self.instruction_voice:
- print(f"[INFO] Sending instruction with every request: {self.instructions}")
- print("[INFO] Its effect (style, emotion, delivery) depends on the "
- "model family; models without instruction support ignore it.")
+ self._report(f"[INFO] Sending instruction with every request: {self.instructions}")
+ self._report("[INFO] Its effect (style, emotion, delivery) depends on the "
+ "model family; models without instruction support ignore it.")
if config.AUDIOCPP_UNLOAD_MODELS:
self._unload_server_models()
@@ -1128,14 +1124,14 @@ class AudioCppTTSClient(_BaseTTSClient):
with urllib.request.urlopen(request, timeout=10) as response:
payload = json.loads(response.read().decode("utf-8"))
except Exception as exc:
- print(f"[WARNING] Could not unload previously loaded models at "
- f"{self.api_url}: {exc}")
+ self._report(f"[WARNING] Could not unload previously loaded models at "
+ f"{self.api_url}: {exc}")
return
unloaded = [entry for entry in (payload.get("unloaded") or [])
if isinstance(entry, str)]
if unloaded:
- print(f"[OK] Unloaded {len(unloaded)} model(s) from server memory: "
- f"{', '.join(unloaded)}")
+ self._report(f"[OK] Unloaded {len(unloaded)} model(s) from server memory: "
+ f"{', '.join(unloaded)}")
else:
logger.debug("No loaded audio.cpp models to unload at %s", self.api_url)
@@ -1371,12 +1367,7 @@ class AudioCppTTSClient(_BaseTTSClient):
# ------------------------------------------------------------------
def _request_wav(self, text: str) -> bytes:
- """POST one sub-chunk and return the raw WAV bytes.
-
- The request timeout scales with the text length when a whole
- chapter is sent in one request (no client-side chunking), since a
- long chapter means many minutes of audio generated in one go.
- """
+ """POST one sub-chunk and return the raw WAV bytes."""
url = f"{self.api_url}/v1/audio/speech"
payload: Dict[str, Any] = {
"model": self.model_id,
@@ -1434,23 +1425,6 @@ class AudioCppTTSClient(_BaseTTSClient):
raise RuntimeError("audio.cpp server returned audio that is not a WAV file")
return wav
- def _request_wav_with_retry(self, text: str, chunk_num: int, sub_num: int,
- sub_total: int) -> bytes:
- """Request one sub-chunk, retrying transient failures."""
- for attempt in range(config.MAX_RETRIES):
- self._check_cancelled()
- try:
- return self._request_wav(text)
- except ConversionCancelled:
- raise
- except Exception as exc:
- logger.warning("Chunk %d sub-chunk %d/%d attempt %d failed: %s",
- chunk_num, sub_num, sub_total, attempt + 1, exc)
- if attempt < config.MAX_RETRIES - 1:
- self._sleep(2 + 2 * attempt)
- raise RuntimeError(f"Sub-chunk {sub_num}/{sub_total} failed after "
- f"{config.MAX_RETRIES} attempts")
-
# ------------------------------------------------------------------
# Chunk generation
# ------------------------------------------------------------------
@@ -1472,8 +1446,7 @@ class AudioCppTTSClient(_BaseTTSClient):
self._chunk_heartbeat(chunk_num):
part_paths = []
for sub_num, sub_text in enumerate(sub_texts, 1):
- wav = self._request_wav_with_retry(
- sub_text, chunk_num, sub_num, len(sub_texts))
+ wav = self._request_wav(sub_text)
destination = Path(parts_dir) / f"part_{sub_num:02d}.wav"
destination.write_bytes(wav)
part_paths.append(destination)
diff --git a/app/tests/test_tts.py b/app/tests/test_tts.py
index c17609b..02b7dc4 100644
--- a/app/tests/test_tts.py
+++ b/app/tests/test_tts.py
@@ -339,18 +339,20 @@ class FasterTTSClientGenerateTests(unittest.TestCase):
remaining = sorted(path.name for path in Path(self._tmp.name).glob("chunk_0001.*"))
self.assertEqual(remaining, ["chunk_0001.wav"])
- def test_transient_failure_is_retried(self):
+ def test_transient_failure_fails_the_chunk_attempt(self):
+ # Retrying is the chunk-level policy's job
+ # (process_chunk_with_retry); one generate_chunk call makes one
+ # request attempt per sub-chunk.
client = self._make_client()
pcm = b"\x01\x00" * 10
with patch.object(client, "_request_pcm",
side_effect=[RuntimeError("boom"), pcm]) as mock_pcm:
result = client.generate_chunk("Hello.", 1)
- self.assertIsNotNone(result)
- self.assertEqual(mock_pcm.call_count, 2)
+ self.assertIsNone(result)
+ self.assertEqual(mock_pcm.call_count, 1)
- def test_empty_pcm_response_is_treated_as_failure(self):
+ def test_empty_pcm_response_fails_the_chunk(self):
client = self._make_client()
- pcm = b"\x01\x00" * 10
def _response(body):
response = MagicMock()
@@ -359,20 +361,18 @@ class FasterTTSClientGenerateTests(unittest.TestCase):
return response
with patch("converter.tts.urllib.request.urlopen",
- side_effect=[_response(b""), _response(pcm)]) as mock_urlopen:
+ side_effect=[_response(b"")]) as mock_urlopen:
result = client.generate_chunk("Hello.", 1)
- self.assertIsNotNone(result)
- self.assertEqual(mock_urlopen.call_count, 2)
- _, _, _, frames = self._read_wav(Path(result))
- self.assertEqual(frames, pcm)
+ self.assertIsNone(result)
+ self.assertEqual(mock_urlopen.call_count, 1)
- def test_exhausted_subchunk_retries_fail_the_chunk(self):
+ def test_subchunk_request_failure_fails_the_chunk(self):
client = self._make_client()
with patch.object(client, "_request_pcm",
side_effect=RuntimeError("down")) as mock_pcm:
result = client.generate_chunk("Hello.", 1)
self.assertIsNone(result)
- self.assertEqual(mock_pcm.call_count, config.MAX_RETRIES)
+ self.assertEqual(mock_pcm.call_count, 1)
def test_empty_text_fails_the_chunk(self):
client = self._make_client()
@@ -1289,22 +1289,25 @@ class AudioCppTTSClientRequestTests(unittest.TestCase):
self.assertIn("500", str(ctx.exception))
self.assertIn("bad voice", str(ctx.exception))
- def test_transient_failure_is_retried(self):
+ def test_transient_failure_fails_the_chunk_attempt(self):
+ # Retrying is the chunk-level policy's job
+ # (process_chunk_with_retry); one generate_chunk call makes one
+ # request attempt per sub-chunk.
client = self._make_client()
wav = self._wav_bytes()
with patch.object(client, "_request_wav",
side_effect=[RuntimeError("boom"), wav]) as mock_request:
result = client.generate_chunk("Hello.", 1)
- self.assertIsNotNone(result)
- self.assertEqual(mock_request.call_count, 2)
+ self.assertIsNone(result)
+ self.assertEqual(mock_request.call_count, 1)
- def test_exhausted_retries_fail_the_chunk(self):
+ def test_request_failure_fails_the_chunk(self):
client = self._make_client()
with patch.object(client, "_request_wav",
side_effect=RuntimeError("down")) as mock_request:
result = client.generate_chunk("Hello.", 1)
self.assertIsNone(result)
- self.assertEqual(mock_request.call_count, config.MAX_RETRIES)
+ self.assertEqual(mock_request.call_count, 1)
def test_empty_text_fails_the_chunk(self):
client = self._make_client()
@@ -1396,7 +1399,7 @@ class AudioCppHeartbeatTests(unittest.TestCase):
buf = io.StringIO()
with patch.object(config, "HEARTBEAT_INTERVAL_SECONDS", 0.03), \
- patch.object(client, "_request_wav_with_retry",
+ patch.object(client, "_request_wav",
side_effect=slow_request), \
redirect_stdout(buf):
result = client.generate_chunk("Hello.", 1)
@@ -1591,7 +1594,8 @@ class BackendWiringTests(unittest.TestCase):
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)
+ mock_faster.assert_called_once_with(voice="narrator", api_url=None,
+ quiet=False)
mock_qwen.assert_not_called()
mock_audiocpp.assert_not_called()
@@ -1606,7 +1610,7 @@ class BackendWiringTests(unittest.TestCase):
model_id=None,
instructions=None,
request_options={},
- api_url=None)
+ api_url=None, quiet=False)
mock_faster.assert_not_called()
mock_qwen.assert_not_called()
@@ -1618,7 +1622,7 @@ class BackendWiringTests(unittest.TestCase):
model_id=None,
instructions=None,
request_options={},
- api_url=None)
+ api_url=None, quiet=False)
def test_audiocpp_backend_model_id_is_wired_through(self):
with patch("converter.converter.AudioCppTTSClient") as mock_audiocpp:
@@ -1628,7 +1632,7 @@ class BackendWiringTests(unittest.TestCase):
mock_audiocpp.assert_called_once_with(
voice="narrator", language=config.LANGUAGE,
model_id="higgs", instructions=None,
- request_options={}, api_url=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:
@@ -1642,7 +1646,7 @@ class BackendWiringTests(unittest.TestCase):
model_id=None,
instructions="A warm adult narrator",
request_options={"emotion": "neutral", "speed": "1.1"},
- api_url=None)
+ api_url=None, quiet=False)
def test_qwen_backend_uses_qwen_client(self):
with patch("converter.converter.FasterTTSClient") as mock_faster, \
@@ -1669,13 +1673,14 @@ class BackendWiringTests(unittest.TestCase):
mock_audiocpp.assert_called_once_with(
voice="narrator", language=config.LANGUAGE, model_id=None,
instructions=None, request_options={},
- api_url="http://10.0.0.5:8080")
+ 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",
api_url="http://10.0.0.5:8000")
mock_faster.assert_called_once_with(voice="narrator",
- api_url="http://10.0.0.5:8000")
+ 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,
@@ -1683,7 +1688,8 @@ class BackendWiringTests(unittest.TestCase):
mock_qwen.assert_called_once_with(
voice_mode=tts.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")
+ language=config.LANGUAGE, 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