diff --git a/src/calibre/gui2/tts2/manager.py b/src/calibre/gui2/tts2/manager.py index 260dca7183..b7448bdb70 100644 --- a/src/calibre/gui2/tts2/manager.py +++ b/src/calibre/gui2/tts2/manager.py @@ -112,6 +112,7 @@ class ResumeData: class TTSManager(QObject): state_changed = pyqtSignal(QTextToSpeech.State) + state_event = pyqtSignal(str) saying = pyqtSignal(int, int) def __init__(self, parent=None): @@ -201,10 +202,22 @@ class TTSManager(QObject): self.tts.reload_after_configure() def _state_changed(self, state: QTextToSpeech.State) -> None: - self.state = state + prev_state, self.state = self.state, state if state is QTextToSpeech.State.Error: error_dialog(self, _('Read aloud failed'), self.tts.error_message(), show=True) self.state_changed.emit(state) + if state is QTextToSpeech.State.Paused: + self.state_event.emit('pause') + elif state is QTextToSpeech.State.Speaking: + if prev_state is QTextToSpeech.State.Paused: + self.state_event.emit('resume') + elif prev_state is QTextToSpeech.State.Ready: + self.state_event.emit('begin') + elif state is QTextToSpeech.State.Ready: + if prev_state in (QTextToSpeech.State.Paused, QTextToSpeech.State.Speaking): + self.state_event.emit('end') + elif state is QTextToSpeech.State.Error: + self.state_event.emit('cancel') def _saying(self, offset: int, length: int) -> None: self.tracker.boundary_reached(offset) diff --git a/src/calibre/gui2/viewer/tts.py b/src/calibre/gui2/viewer/tts.py index bba47ce8ac..6034abe9df 100644 --- a/src/calibre/gui2/viewer/tts.py +++ b/src/calibre/gui2/viewer/tts.py @@ -1,12 +1,7 @@ #!/usr/bin/env python # License: GPL v3 Copyright: 2020, Kovid Goyal -from qt.core import QDialog, QDialogButtonBox, QObject, QVBoxLayout, pyqtSignal - -from calibre.gui2 import error_dialog -from calibre.gui2.viewer.config import get_pref_group, vprefs -from calibre.gui2.widgets2 import Dialog -from calibre.utils.localization import _ +from qt.core import QObject, pyqtSignal def set_sync_override(allowed): @@ -14,142 +9,62 @@ def set_sync_override(allowed): set_sync_override(allowed) -class Config(Dialog): - - def __init__(self, tts_client, ui_settings, backend_settings, parent): - self.tts_client = tts_client - self.ui_settings = ui_settings - self.backend_settings = backend_settings - Dialog.__init__(self, _('Configure Read aloud'), 'read-aloud-config', parent, prefs=vprefs) - - def setup_ui(self): - self.l = l = QVBoxLayout(self) - self.config_widget = self.tts_client.config_widget(self.backend_settings, self) - l.addWidget(self.config_widget) - l.addWidget(self.bb) - self.config_widget.restore_to_defaults - b = self.bb.addButton(QDialogButtonBox.StandardButton.RestoreDefaults) - b.clicked.connect(self.restore_to_defaults) - self.config_widget.restore_state(vprefs) - - def save_state(self): - self.config_widget.save_state(vprefs) - - def restore_to_defaults(self): - self.config_widget.restore_to_defaults() - - def accept(self): - self.backend_settings = self.config_widget.backend_settings - return super().accept() - - class TTS(QObject): - dispatch_on_main_thread_signal = pyqtSignal(object) event_received = pyqtSignal(object, object) settings_changed = pyqtSignal(object) def __init__(self, parent=None): - QObject.__init__(self, parent) - self._tts_client = None - self.dispatch_on_main_thread_signal.connect(self.dispatch_on_main_thread) - - def dispatch_on_main_thread(self, func): - try: - func() - except Exception as e: - import traceback - traceback.print_exc() - if getattr(e, 'display_to_user', False): - error_dialog(self.parent(), _('Error in speech subsystem'), str(e), det_msg=traceback.format_exc(), show=True) + super().__init__(parent) + self._manager = None @property - def tts_client_class(self): - from calibre.gui2.tts.implementation import Client - return Client - - @property - def tts_client(self): - if self._tts_client is None: - settings = self.backend_settings - self._tts_client = self.tts_client_class(settings, self.dispatch_on_main_thread_signal.emit) - if self._tts_client.settings != settings: - self.backend_settings = self._tts_client.settings - return self._tts_client + def manager(self): + if self._manager is None: + from calibre.gui2.tts2.manager import TTSManager + self._manager = TTSManager(self) + self._manager.saying.connect(self.saying) + self._manager.state_event.connect(self.state_event) + return self._manager def shutdown(self): - if self._tts_client is not None: - self._tts_client.shutdown() - self._tts_client = None + if self._manager is not None: + self._manager.stop() + self._manager = None def speak_simple_text(self, text): - from calibre.gui2.tts.errors import TTSSystemUnavailable - try: - self.tts_client.speak_simple_text(text) - except TTSSystemUnavailable as err: - return error_dialog(self.parent(), _('Text-to-Speech unavailable'), str(err), show=True) + self.manager.speak_simple_text(text) def action(self, action, data): - from calibre.gui2.tts.errors import TTSSystemUnavailable - try: - getattr(self, action)(data) - except TTSSystemUnavailable as err: - return error_dialog(self.parent(), _('Text-to-Speech unavailable'), str(err), show=True) + getattr(self, action)(data) def play(self, data): set_sync_override(False) - self.tts_client.speak_marked_text(data['marked_text'], self.callback) + self.manager.speak_marked_text(data['marked_text']) def pause(self, data): set_sync_override(True) - self.tts_client.pause() + self.manager.pause() def resume(self, data): set_sync_override(False) - self.tts_client.resume() + self.manager.resume() - def resume_after_configure(self, data): - set_sync_override(False) - self.tts_client.resume_after_configure() + def saying(self, first: int, last: int) -> None: + self.event_received.emit('mark', {'first': first, 'last': last}) - def callback(self, event): - data = event.data - if event.type is event.type.mark: - data = {'first': int(data), 'last': int(data)} - self.event_received.emit(event.type.name, data) + def state_event(self, name: str): + self.event_received.emit(name, None) def stop(self, data): set_sync_override(True) - self.tts_client.stop() - - @property - def backend_settings(self): - key = 'tts_' + self.tts_client_class.name - return vprefs.get(key) or {} - - @backend_settings.setter - def backend_settings(self, val): - key = 'tts_' + self.tts_client_class.name - vprefs.set(key, val or {}) + self.manager.stop() def configure(self, data): - ui_settings = get_pref_group('tts').copy() - d = Config(self.tts_client, ui_settings, self.backend_settings, parent=self.parent()) - if d.exec() == QDialog.DialogCode.Accepted: - s = d.backend_settings - self.backend_settings = s - self.tts_client.apply_settings(s) - self.settings_changed.emit(d.ui_settings) - else: - self.settings_changed.emit(None) - d.save_state() + self.manager.configure() def slower(self, data): - settings = self.tts_client.change_rate(steps=-1) - if settings is not None: - self.backend_settings = settings + self.manager.change_rate(steps=-1) def faster(self, data): - settings = self.tts_client.change_rate(steps=1) - if settings is not None: - self.backend_settings = settings + self.manager.change_rate(steps=1)