diff options
Diffstat (limited to 'app/converter/converter.py')
| -rw-r--r-- | app/converter/converter.py | 43 |
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}") |
