From 4d3530f63730b47870d25629802c0c41f0c9ffae Mon Sep 17 00:00:00 2001 From: historia Date: Thu, 20 Aug 2026 16:17:57 -0400 Subject: feat: audio.cpp backend support --- converter/converter.py | 60 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 45 insertions(+), 15 deletions(-) (limited to 'converter/converter.py') diff --git a/converter/converter.py b/converter/converter.py index 4422316..eb3e80e 100644 --- a/converter/converter.py +++ b/converter/converter.py @@ -15,10 +15,15 @@ from typing import Dict, List, Optional, Tuple from . import audio, chunking, config, cover, extractors from .audio import TrackMeta from .tts import ( + BACKENDS, + BACKEND_AUDIOCPP, + BACKEND_FASTER, + BACKEND_GRADIO, MODEL_SIZE, VOICE_MODE_CLONE, VOICE_MODE_CUSTOM, VOICE_MODES, + AudioCppTTSClient, FasterTTSClient, QwenTTSClient, normalize_language, @@ -128,12 +133,16 @@ class AudiobookConverter: def __init__(self, voice_mode: str = VOICE_MODE_CUSTOM, voice_clone_ref_audio: Optional[str] = None, voice_clone_ref_text: Optional[str] = None, skip_transcription: bool = False, speed: float = 1.0, single_file: bool = False, output_format: str = config.AUDIO_FORMAT, - language: Optional[str] = None, faster: bool = False, - faster_voice: Optional[str] = None, debug: bool = False): + language: Optional[str] = None, backend: str = BACKEND_GRADIO, + voice: Optional[str] = None, debug: bool = False): if speed <= 0: raise ValueError(f"Speed must be a positive number, got {speed}") if output_format not in AUDIO_FORMATS: raise ValueError(f"Unsupported output format: {output_format}") + if backend not in BACKENDS: + raise ValueError( + f"Unknown backend: {backend!r} (expected one of {BACKENDS})" + ) if language is None: language = config.LANGUAGE self.language = normalize_language(language) @@ -142,14 +151,18 @@ class AudiobookConverter: self.speed = speed self.single_file = single_file self.output_format = output_format - self.faster = faster - self.faster_voice = faster_voice + self.backend = backend + self.voice = voice self.debug = bool(debug) self._validate_configuration() - if faster: + 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=faster_voice) + self.tts = FasterTTSClient(voice=voice) + elif backend == BACKEND_AUDIOCPP: + # Speaker mode (no voice) uses a built-in CustomVoice speaker; + # an explicit voice selects a server-side preset (cloning). + self.tts = AudioCppTTSClient(voice=voice, language=self.language) else: self.tts = QwenTTSClient( voice_mode=voice_mode, @@ -166,7 +179,7 @@ class AudiobookConverter: f"Unknown voice mode: {self.voice_mode!r} " f"(expected one of {VOICE_MODES})" ) - if self.voice_mode == VOICE_MODE_CLONE and not self.faster: + if self.voice_mode == VOICE_MODE_CLONE and self.backend == BACKEND_GRADIO: if not self.voice_clone_ref_audio: raise ValueError( "Voice Clone mode requires a reference audio file. " @@ -189,12 +202,15 @@ class AudiobookConverter: """Narrator name used in output file names. Custom voice mode uses the built-in speaker's display name; voice - clone mode uses the reference audio file's stem; the faster backend - uses the server-side voice name. Spaces become underscores - (e.g. "Uncle Fu" -> "Uncle_Fu"). + clone mode uses the reference audio file's stem; the faster and + audiocpp backends use the server-side voice name (falling back to + the built-in speaker for the audiocpp backend's speaker mode). + Spaces become underscores (e.g. "Uncle Fu" -> "Uncle_Fu"). """ - if self.faster: - narrator = self.faster_voice or config.FASTER_VOICE + if self.backend == BACKEND_FASTER: + narrator = self.voice or config.FASTER_VOICE + elif self.backend == BACKEND_AUDIOCPP: + narrator = self.voice or speaker_display_name() elif self.voice_mode == VOICE_MODE_CLONE: narrator = Path(self.voice_clone_ref_audio).stem else: @@ -445,7 +461,11 @@ class AudiobookConverter: chunk_sizes = [len(chunk.split()) for chunk in chunks] avg_chunk_size = sum(chunk_sizes) / len(chunk_sizes) logger.info("Split into %d chunks (avg %.0f words per chunk)", total_chunks, avg_chunk_size) - backend = "faster TTS API" if self.faster else "Qwen API" + backend_labels = { + BACKEND_FASTER: "faster TTS API", + BACKEND_AUDIOCPP: "audio.cpp server", + } + backend = backend_labels.get(self.backend, "Qwen API") print(f"[INFO] Processing {total_chunks} chunks via {backend}...") results = self._synthesize_chunks(chunks, debug_dir=debug_dir) @@ -493,10 +513,20 @@ class AudiobookConverter: print("=" * 70) print(f"Books folder: {BOOKS_FOLDER}") print(f"Output folder: {AUDIOBOOKS_FOLDER}") - if self.faster: + if self.backend == BACKEND_FASTER: print(f"Faster TTS endpoint: {config.FASTER_API_URL}") print("Backend: faster (voice cloning, reference configured on server)") - print(f"Voice: {self.faster_voice or config.FASTER_VOICE}") + print(f"Voice: {self.voice or config.FASTER_VOICE}") + elif self.backend == BACKEND_AUDIOCPP: + print(f"audio.cpp endpoint: {config.AUDIOCPP_API_URL}") + print(f"Model id: {config.AUDIOCPP_MODEL_ID}") + if self.voice: + print("Backend: audio.cpp (voice cloning, reference configured on server)") + print(f"Voice: {self.voice}") + else: + print("Backend: audio.cpp (custom voice, built-in speaker)") + print(f"Speaker: {config.SPEAKER}") + print(f"Language: {self.language}") else: api_url = (config.CLONE_API_URL if self.voice_mode == VOICE_MODE_CLONE else config.QWEN_API_URL) -- cgit v1.2.3