aboutsummaryrefslogtreecommitdiff
path: root/converter/converter.py
diff options
context:
space:
mode:
authorhistoria <historiavg@proton.me>2026-08-18 23:27:42 -0400
committerhistoria <historiavg@proton.me>2026-08-18 23:27:42 -0400
commitf3e21980320c1708ff17cc6f699a9aa4758accdf (patch)
tree596ff71ba68600bb24a5a9d88424951c77e02808 /converter/converter.py
parenta97c0506f8b20cdc5ed8a11892ef10a9fc1938ef (diff)
downloadtts-audiobook-generator-f3e21980320c1708ff17cc6f699a9aa4758accdf.tar.gz
feat: support for faster-qwen3-tts backend server
Diffstat (limited to 'converter/converter.py')
-rw-r--r--converter/converter.py72
1 files changed, 46 insertions, 26 deletions
diff --git a/converter/converter.py b/converter/converter.py
index 4d20083..ec06bbb 100644
--- a/converter/converter.py
+++ b/converter/converter.py
@@ -13,7 +13,7 @@ from typing import Dict, List, Optional, Tuple
from . import audio, chunking, config, cover, extractors
from .audio import TrackMeta
-from .tts import QwenTTSClient, normalize_language, speaker_display_name
+from .tts import FasterTTSClient, QwenTTSClient, normalize_language, speaker_display_name
logger = logging.getLogger(__name__)
@@ -89,7 +89,8 @@ class AudiobookConverter:
def __init__(self, voice_mode: str = config.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):
+ language: Optional[str] = None, faster: bool = False,
+ faster_voice: Optional[str] = None):
if speed <= 0:
raise ValueError(f"Speed must be a positive number, got {speed}")
if output_format not in config.AUDIO_FORMATS:
@@ -103,14 +104,21 @@ class AudiobookConverter:
self.speed = speed
self.single_file = single_file
self.output_format = output_format
+ self.faster = faster
+ self.faster_voice = faster_voice
self._validate_configuration()
- self.tts = QwenTTSClient(
- voice_mode=voice_mode,
- voice_clone_ref_audio=voice_clone_ref_audio,
- voice_clone_ref_text=voice_clone_ref_text,
- skip_transcription=skip_transcription,
- language=self.language,
- )
+ if 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)
+ else:
+ self.tts = QwenTTSClient(
+ voice_mode=voice_mode,
+ voice_clone_ref_audio=voice_clone_ref_audio,
+ voice_clone_ref_text=voice_clone_ref_text,
+ skip_transcription=skip_transcription,
+ language=self.language,
+ )
def _validate_configuration(self) -> None:
"""Validate configuration settings."""
@@ -119,7 +127,7 @@ class AudiobookConverter:
f"Unknown voice mode: {self.voice_mode!r} "
f"(expected one of {config.VOICE_MODES})"
)
- if self.voice_mode == config.VOICE_MODE_CLONE:
+ if self.voice_mode == config.VOICE_MODE_CLONE and not self.faster:
if not self.voice_clone_ref_audio:
raise ValueError(
"Voice Clone mode requires a reference audio file. "
@@ -142,10 +150,13 @@ 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. Spaces become
- underscores (e.g. "Uncle Fu" -> "Uncle_Fu").
+ 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").
"""
- if self.voice_mode == config.VOICE_MODE_CLONE:
+ if self.faster:
+ narrator = self.faster_voice or config.FASTER_TTS_VOICE
+ elif self.voice_mode == config.VOICE_MODE_CLONE:
narrator = Path(self.voice_clone_ref_audio).stem
else:
narrator = speaker_display_name()
@@ -371,24 +382,29 @@ class AudiobookConverter:
logger.error(traceback.format_exc())
return False
- def run(self) -> bool:
- """Main conversion process. Returns True if all books converted."""
- api_url = config.VOICE_CLONE_API_URL if self.voice_mode == config.VOICE_MODE_CLONE else config.QWEN_API_URL
-
+ def _print_banner(self) -> None:
+ """Print the startup summary for the selected backend."""
print("=" * 70)
print("QWEN-BASED AUDIOBOOK CONVERTER")
print("=" * 70)
print(f"Books folder: {config.BOOKS_FOLDER}")
print(f"Output folder: {config.AUDIOBOOKS_FOLDER}")
- print(f"Qwen API endpoint: {api_url}")
- print(f"Voice mode: {self.voice_mode}")
- print("Model size: 1.7B (always)")
- if self.voice_mode == config.VOICE_MODE_CUSTOM:
- print(f"Speaker: {config.CUSTOM_VOICE_SPEAKER}")
- print(f"Language: {self.language}")
- elif self.voice_mode == config.VOICE_MODE_CLONE:
- print(f"Reference audio: {Path(self.voice_clone_ref_audio).name}")
- print(f"Language: {self.language}")
+ if self.faster:
+ print(f"Faster TTS endpoint: {config.FASTER_TTS_API_URL}")
+ print("Backend: faster (voice cloning, reference configured on server)")
+ print(f"Voice: {self.faster_voice or config.FASTER_TTS_VOICE}")
+ else:
+ api_url = (config.VOICE_CLONE_API_URL if self.voice_mode == config.VOICE_MODE_CLONE
+ else config.QWEN_API_URL)
+ print(f"Qwen API endpoint: {api_url}")
+ print(f"Voice mode: {self.voice_mode}")
+ print("Model size: 1.7B (always)")
+ if self.voice_mode == config.VOICE_MODE_CUSTOM:
+ print(f"Speaker: {config.CUSTOM_VOICE_SPEAKER}")
+ print(f"Language: {self.language}")
+ elif self.voice_mode == config.VOICE_MODE_CLONE:
+ print(f"Reference audio: {Path(self.voice_clone_ref_audio).name}")
+ print(f"Language: {self.language}")
print(f"Output format: {self.output_format}")
if self.single_file and self.output_format != "m4b":
print("Chapter mode: single file (--single-file)")
@@ -396,6 +412,10 @@ class AudiobookConverter:
print(f"Playback speed: {self.speed:g}x")
print("=" * 70)
+ def run(self) -> bool:
+ """Main conversion process. Returns True if all books converted."""
+ self._print_banner()
+
# Check for books
book_files = sorted(
f for f in config.BOOKS_FOLDER.iterdir()