From 58277b0a480ad156798ed5eabb8761eac8579d95 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 2 Sep 2024 21:29:56 +0530 Subject: [PATCH] Download piper voice when configuring if needed --- src/calibre/gui2/tts2/config.py | 6 +++++- src/calibre/gui2/tts2/piper.py | 35 +++++++++++++++++++++++++++++++-- src/calibre/gui2/tts2/types.py | 3 +++ 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/calibre/gui2/tts2/config.py b/src/calibre/gui2/tts2/config.py index 8995dc6852..9f1a70e8b6 100644 --- a/src/calibre/gui2/tts2/config.py +++ b/src/calibre/gui2/tts2/config.py @@ -296,10 +296,14 @@ class ConfigDialog(Dialog): return self.engine_choice.value != self.initial_engine_choice def accept(self): + engine_name = self.engine_choice.value + tts = create_tts_backend(engine_name) s = self.engine_specific_config.as_settings() + if not tts.validate_settings(s, self): + return prefs = load_config() with prefs: - if engine_name := self.engine_choice.value: + if engine_name: prefs['engine'] = engine_name else: prefs.pop('engine', None) diff --git a/src/calibre/gui2/tts2/piper.py b/src/calibre/gui2/tts2/piper.py index a545e13c7c..0ebc27d008 100644 --- a/src/calibre/gui2/tts2/piper.py +++ b/src/calibre/gui2/tts2/piper.py @@ -11,9 +11,25 @@ from dataclasses import dataclass from itertools import count from time import monotonic -from qt.core import QAudio, QAudioFormat, QAudioSink, QByteArray, QDialog, QIODevice, QIODeviceBase, QObject, QProcess, Qt, QTextToSpeech, pyqtSignal, sip +from qt.core import ( + QAudio, + QAudioFormat, + QAudioSink, + QByteArray, + QDialog, + QIODevice, + QIODeviceBase, + QObject, + QProcess, + Qt, + QTextToSpeech, + QWidget, + pyqtSignal, + sip, +) from calibre.constants import cache_dir, is_debugging +from calibre.gui2 import error_dialog from calibre.gui2.tts2.types import EngineSpecificSettings, Quality, TTSBackend, Voice, piper_cmdline, widget_parent from calibre.spell.break_iterator import sentence_positions, split_into_words_and_positions from calibre.utils.localization import canonicalize_lang, get_lang @@ -429,7 +445,7 @@ class Piper(TTSBackend): return model_path, config_path os.makedirs(os.path.dirname(model_path), exist_ok=True) from calibre.gui2.tts2.download import DownloadResources - d = DownloadResources(_('Downloading voice data'), _('Downloading neural network for the {} voice').format(voice.human_name), { + d = DownloadResources(_('Downloading voice for Read aloud'), _('Downloading neural network for the {} voice').format(voice.human_name), { voice.engine_data['model_url']: (model_path, _('Neural network data')), voice.engine_data['config_url']: (config_path, _('Neural network metadata')), }, parent=widget_parent(self)) @@ -437,6 +453,21 @@ class Piper(TTSBackend): return model_path, config_path return '', '' + def validate_settings(self, s: EngineSpecificSettings, parent: QWidget | None) -> bool: + self._load_voice_metadata() + voice = self._voice_name_map.get(s.voice_name) or self._default_voice + try: + m, c = self._ensure_voice_is_downloaded(voice) + if not m: + error_dialog(parent, _('Failed to download voice'), _('Failed to download the voice: {}').format(voice.human_name), show=True) + return False + except Exception: + import traceback + error_dialog(parent, _('Failed to download voice'), _('Failed to download the voice: {}').format(voice.human_name), + det_msg=traceback.format_exc(), show=True) + return False + return True + def develop(): # {{{ import tty diff --git a/src/calibre/gui2/tts2/types.py b/src/calibre/gui2/tts2/types.py index f12fa4da78..6743bcd934 100644 --- a/src/calibre/gui2/tts2/types.py +++ b/src/calibre/gui2/tts2/types.py @@ -241,6 +241,9 @@ class TTSBackend(QObject): def reload_after_configure(self) -> None: raise NotImplementedError() + def validate_settings(self, s: EngineSpecificSettings, parent: QWidget | None) -> bool: + return True + engine_instances: dict[str, TTSBackend] = {}