aboutsummaryrefslogtreecommitdiff
path: root/app/converter/converter.py
diff options
context:
space:
mode:
authorhistoria <historiavg@proton.me>2026-08-28 14:57:48 -0400
committerhistoria <historiavg@proton.me>2026-08-28 14:57:48 -0400
commitdb38085d07ce75f8961eecdc1919e98748254c53 (patch)
treebeaea2ba84de05bfc2fa90e9b92a24568d3b4cb9 /app/converter/converter.py
parentafb2c2d5b297c5aa28bcced0e3f90e207d799c2a (diff)
downloadtts-audiobook-generator-db38085d07ce75f8961eecdc1919e98748254c53.tar.gz
refactor: overhaul config.py, remove cli default options
Diffstat (limited to 'app/converter/converter.py')
-rw-r--r--app/converter/converter.py50
1 files changed, 30 insertions, 20 deletions
diff --git a/app/converter/converter.py b/app/converter/converter.py
index 5235991..a9ea7eb 100644
--- a/app/converter/converter.py
+++ b/app/converter/converter.py
@@ -31,7 +31,7 @@ from .clients import (
FasterTTSClient,
QwenTTSClient,
normalize_language,
- speaker_display_name,
+ speaker_display_name_for,
)
logger = logging.getLogger(__name__)
@@ -207,7 +207,7 @@ 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, backend: str = config.BACKEND,
+ language: Optional[str] = None, backend: str = None,
voice: Optional[str] = None, debug: bool = False,
model_id: Optional[str] = None,
instructions: Optional[str] = None,
@@ -219,6 +219,8 @@ class AudiobookConverter:
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 is None:
+ raise ValueError("backend is required (pass --backend)")
if backend not in BACKENDS:
raise ValueError(
f"Unknown backend: {backend!r} (expected one of {BACKENDS})"
@@ -257,10 +259,10 @@ class AudiobookConverter:
elif backend == BACKEND_AUDIOCPP:
# --voice picks the voice: a built-in speaker name on the
# CustomVoice entry, or a server-side preset (cloning)
- # elsewhere. model_id overrides AUDIOCPP_MODEL_ID for
- # multi-model servers; instructions describe or style the
- # voice, request_options pass per-model controls through to
- # the server.
+ # elsewhere. model_id picks the server entry per run
+ # (auto-selected on single-entry servers); instructions
+ # describe or style the voice, request_options pass
+ # per-model controls through to the server.
self.tts = AudioCppTTSClient(chunks_dir=CHUNKS_FOLDER,
voice=voice, language=self.language,
model_id=model_id,
@@ -270,6 +272,7 @@ class AudiobookConverter:
else:
# Qwen: the voice mode picks the request shape (built-in
# speaker, clone from a reference .wav, or a designed voice);
+ # --voice names the built-in speaker in speaker mode and
# instructions describe the voice in design mode.
self.tts = QwenTTSClient(
chunks_dir=CHUNKS_FOLDER,
@@ -281,6 +284,7 @@ class AudiobookConverter:
instructions=self.instructions,
api_url=api_url,
quiet=quiet,
+ voice=voice,
)
self._progress = progress
self.tts.cancel = cancel
@@ -314,6 +318,13 @@ class AudiobookConverter:
"Voice Design mode requires a voice description. "
"Use --instructions \"...\" to describe the voice to synthesize with."
)
+ if self.backend == BACKEND_QWEN and self.voice_mode == VOICE_MODE_CUSTOM \
+ and not (self.voice or "").strip():
+ raise ValueError(
+ "CustomVoice mode requires a speaker. Use --voice SPEAKER "
+ "(e.g. Vivian) to pick one, or --clone / --instructions "
+ "for the other voice modes."
+ )
if self.voice_mode == VOICE_MODE_CLONE and self.backend == BACKEND_QWEN:
if not self.voice_clone_ref_audio:
raise ValueError(
@@ -348,19 +359,18 @@ class AudiobookConverter:
Custom voice mode uses the built-in speaker's display name; voice
clone mode uses the reference audio file's stem; the faster and
- audiocpp backends use the server-side voice name (or, for audiocpp's
- speaker mode, the selected built-in CustomVoice speaker, falling back
- to the configured one). An instruction without a voice (voice design,
- or instruction-defined voices on families without built-in speakers)
- uses "designed". Spaces become underscores (e.g. "Uncle Fu" ->
- "Uncle_Fu").
+ audiocpp backends use the server-side voice name (for audiocpp's
+ speaker mode, the selected built-in CustomVoice speaker). An
+ instruction without a voice (voice design, or instruction-defined
+ voices on families without built-in speakers) uses "designed".
+ Spaces become underscores (e.g. "Uncle Fu" -> "Uncle_Fu").
Pure (no I/O, no server) so the pre-flight overwrite check can
compute the exact output names a run would produce before spending
time connecting to a TTS server.
"""
if backend == BACKEND_FASTER:
- narrator = voice or config.FASTER_VOICE
+ narrator = voice or "default"
elif backend == BACKEND_AUDIOCPP:
if voice:
narrator = voice
@@ -368,7 +378,10 @@ class AudiobookConverter:
# The voice comes from the instruction, not a speaker name.
narrator = "designed"
else:
- narrator = speaker_display_name()
+ # Unreachable in a valid run (the audiocpp client refuses a
+ # speaker-capable entry without --voice); keep a stable tag
+ # for the pre-flight of runs that will fail at connect time.
+ narrator = "narrator"
elif voice_mode == VOICE_MODE_DESIGN:
# Qwen's VoiceDesign model: the voice is described by an
# instruction and has no speaker name.
@@ -376,7 +389,7 @@ class AudiobookConverter:
elif voice_mode == VOICE_MODE_CLONE:
narrator = Path(voice_clone_ref_audio).stem
else:
- narrator = speaker_display_name()
+ narrator = speaker_display_name_for(voice or "")
return AudiobookConverter._sanitize_filename(
narrator, fallback="narrator").replace(" ", "_")
@@ -736,7 +749,7 @@ class AudiobookConverter:
if self.backend == BACKEND_FASTER:
self._say(f"Faster TTS endpoint: {config.FASTER_API_URL}")
self._say("Backend: faster (voice cloning, reference configured on server)")
- self._say(f"Voice: {self.voice or config.FASTER_VOICE}")
+ self._say(f"Voice: {self.voice}")
elif self.backend == BACKEND_AUDIOCPP:
self._say(f"audio.cpp endpoint: {config.AUDIOCPP_API_URL}")
self._say(f"Model id: {self.tts.model_id}")
@@ -750,9 +763,6 @@ class AudiobookConverter:
elif self.instructions:
self._say("Backend: audio.cpp (voice from --instructions description)")
self._say(f"Instruction: {self.instructions}")
- else:
- self._say("Backend: audio.cpp (custom voice, built-in speaker)")
- self._say(f"Speaker: {config.SPEAKER}")
if self.request_options:
self._say(f"Request options: {self.request_options}")
self._say(f"Language: {self.language}")
@@ -764,7 +774,7 @@ class AudiobookConverter:
self._say(f"Voice mode: {self.voice_mode}")
self._say(f"Model size: {MODEL_SIZE} (always)")
if self.voice_mode == VOICE_MODE_CUSTOM:
- self._say(f"Speaker: {config.SPEAKER}")
+ self._say(f"Speaker: {self.voice}")
self._say(f"Language: {self.language}")
elif self.voice_mode == VOICE_MODE_CLONE:
self._say(f"Reference audio: {Path(self.voice_clone_ref_audio).name}")