aboutsummaryrefslogtreecommitdiff
path: root/app/converter/converter.py
diff options
context:
space:
mode:
authorhistoria <historiavg@proton.me>2026-08-25 16:40:30 -0400
committerhistoria <historiavg@proton.me>2026-08-25 16:40:30 -0400
commit867866f131b0b6c76c54272791e7f7dea01db990 (patch)
treeb57ecdd66eeaf7ad73742d2f2bbe15d3a5498fa3 /app/converter/converter.py
parentfca3431721a55277f139efc83df2438207917448 (diff)
downloadtts-audiobook-generator-867866f131b0b6c76c54272791e7f7dea01db990.tar.gz
feat: better tui menu option gating for models that support custom voices (qwen) and models that do not support instructions
Diffstat (limited to 'app/converter/converter.py')
-rw-r--r--app/converter/converter.py43
1 files changed, 28 insertions, 15 deletions
diff --git a/app/converter/converter.py b/app/converter/converter.py
index b2c2923..6e677c8 100644
--- a/app/converter/converter.py
+++ b/app/converter/converter.py
@@ -185,6 +185,7 @@ class AudiobookConverter:
instructions: Optional[str] = None,
request_options: Optional[Dict[str, str]] = None,
api_url: Optional[str] = None,
+ speaker: Optional[str] = None,
progress: Optional[Callable[[dict], None]] = None,
cancel=None):
if speed <= 0:
@@ -205,6 +206,7 @@ class AudiobookConverter:
self.output_format = output_format
self.backend = backend
self.voice = voice
+ self.speaker = speaker
self.debug = bool(debug)
# Voice design / style instruction and free-form request options
# (audio.cpp only): forwarded to AudioCppTTSClient, which validates
@@ -217,16 +219,18 @@ class AudiobookConverter:
# configured on the server, so no local reference audio is needed.
self.tts = FasterTTSClient(voice=voice, api_url=api_url)
elif backend == BACKEND_AUDIOCPP:
- # Speaker mode (no voice) uses a built-in CustomVoice speaker;
- # an explicit voice selects a server-side preset (cloning).
- # 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.
+ # Speaker mode (--speaker or no flag on a CustomVoice entry) uses
+ # a built-in speaker name; an explicit --voice selects a
+ # server-side preset (cloning). 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.
self.tts = AudioCppTTSClient(voice=voice, language=self.language,
model_id=model_id,
instructions=instructions,
request_options=self.request_options,
- api_url=api_url)
+ api_url=api_url,
+ speaker=speaker)
else:
self.tts = QwenTTSClient(
voice_mode=voice_mode,
@@ -290,22 +294,24 @@ class AudiobookConverter:
"""Narrator name used in output file names (see compute_narrator_tag)."""
return self.compute_narrator_tag(
self.backend, self.voice, self.voice_mode,
- self.voice_clone_ref_audio, self.instructions)
+ self.voice_clone_ref_audio, self.instructions, self.speaker)
@staticmethod
def compute_narrator_tag(backend: str, voice: Optional[str],
voice_mode: str,
voice_clone_ref_audio: Optional[str],
- instructions: Optional[str] = None) -> str:
+ instructions: Optional[str] = None,
+ speaker: Optional[str] = None) -> str:
"""Narrator name used in output file names, without a server connection.
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 (falling back to
- the built-in speaker for the audiocpp backend's speaker mode). 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 (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").
Pure (no I/O, no server) so the pre-flight overwrite check can
compute the exact output names a run would produce before spending
@@ -316,6 +322,8 @@ class AudiobookConverter:
elif backend == BACKEND_AUDIOCPP:
if voice:
narrator = voice
+ elif speaker:
+ narrator = speaker
elif instructions:
# The voice comes from the instruction, not a speaker name.
narrator = "designed"
@@ -672,6 +680,9 @@ class AudiobookConverter:
if self.voice:
self._say("Backend: audio.cpp (voice cloning, reference configured on server)")
self._say(f"Voice: {self.voice}")
+ elif self.speaker:
+ self._say("Backend: audio.cpp (custom voice, built-in speaker)")
+ self._say(f"Speaker: {self.speaker}")
elif self.instructions:
self._say("Backend: audio.cpp (voice from --instructions description)")
self._say(f"Instruction: {self.instructions}")
@@ -715,6 +726,7 @@ class AudiobookConverter:
voice_clone_ref_audio: Optional[str],
output_format: str,
instructions: Optional[str] = None,
+ speaker: Optional[str] = None,
confirm: Optional[Callable[[str, bool], bool]] = None,
) -> Tuple[List[Path], List[Tuple[Path, str]]]:
"""Discover books and ask every overwrite question up front.
@@ -746,7 +758,8 @@ class AudiobookConverter:
# starts, so the rest of the run is unattended.
planned: List[Tuple[Path, str]] = []
narrator_tag = AudiobookConverter.compute_narrator_tag(
- backend, voice, voice_mode, voice_clone_ref_audio, instructions)
+ backend, voice, voice_mode, voice_clone_ref_audio, instructions,
+ speaker)
for book_file in book_files:
output_name = book_file.stem
if stem_counts[book_file.stem] > 1:
@@ -782,7 +795,7 @@ class AudiobookConverter:
book_files, planned = AudiobookConverter.preflight_overwrites(
self.backend, self.voice, self.voice_mode,
self.voice_clone_ref_audio, self.output_format,
- self.instructions)
+ self.instructions, self.speaker)
if not book_files:
self._say(f"[INFO] No supported files found in {BOOKS_FOLDER}")