From ed4a397de1093b82f55315618d4b82ae0d287f6e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 7 Dec 2020 15:50:46 +0530 Subject: [PATCH] macOS: Workaround for Apple being unable to resume speech after changing the voice --- src/calibre/gui2/tts/linux.py | 1 + src/calibre/gui2/tts/macos.py | 18 ++++++++++++++++++ src/calibre/gui2/viewer/tts.py | 3 +++ src/pyj/read_book/read_aloud.pyj | 4 ++-- 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/tts/linux.py b/src/calibre/gui2/tts/linux.py index 2103515ed1..7cd0d38685 100644 --- a/src/calibre/gui2/tts/linux.py +++ b/src/calibre/gui2/tts/linux.py @@ -166,6 +166,7 @@ class Client: text = self.current_marked_text[idx:] self.ensure_state(use_ssml=True) self.ssip_client.speak(wrap_in_ssml(text), callback=self.current_callback) + resume_after_configure = resume def stop(self): self.current_callback = self.current_marked_text = self.last_mark = None diff --git a/src/calibre/gui2/tts/macos.py b/src/calibre/gui2/tts/macos.py index df7ba620e5..67b19d4df8 100644 --- a/src/calibre/gui2/tts/macos.py +++ b/src/calibre/gui2/tts/macos.py @@ -21,6 +21,7 @@ class Client: self.default_system_rate = self.nsss.get_current_rate() self.default_system_voice = self.nsss.get_current_voice() self.current_callback = None + self.current_marked_text = self.last_mark = None self.dispatch_on_main_thread = dispatch_on_main_thread self.status = {'synthesizing': False, 'paused': False} self.apply_settings(settings) @@ -38,6 +39,7 @@ class Client: from calibre_extensions.cocoa import MARK, END event = None if message_type == MARK: + self.last_mark = data if data == self.END_MARK: event = Event(EventType.end) self.status = {'synthesizing': False, 'paused': False} @@ -56,6 +58,7 @@ class Client: def speak_simple_text(self, text): self.current_callback = None + self.current_marked_text = self.last_mark = None self.nsss.speak(self.escape_marked_text(text)) self.status = {'synthesizing': True, 'paused': False} @@ -64,6 +67,8 @@ class Client: # on macOS didFinishSpeaking is never called for some reason, so work # around it by adding an extra, special mark at the end text += self.mark_template.format(self.END_MARK) + self.current_marked_text = text + self.last_mark = None self.nsss.speak(text) self.status = {'synthesizing': True, 'paused': False} self.current_callback(Event(EventType.begin)) @@ -82,6 +87,19 @@ class Client: if self.current_callback is not None: self.current_callback(Event(EventType.resume)) + def resume_after_configure(self): + if self.status['paused'] and self.last_mark is not None and self.current_marked_text: + mark = self.mark_template.format(self.last_mark) + idx = self.current_marked_text.find(mark) + if idx == -1: + text = self.current_marked_text + else: + text = self.current_marked_text[idx:] + self.nsss.speak(text) + self.status = {'synthesizing': True, 'paused': False} + if self.current_callback is not None: + self.current_callback(Event(EventType.resume)) + def stop(self): self.nsss.stop() diff --git a/src/calibre/gui2/viewer/tts.py b/src/calibre/gui2/viewer/tts.py index 1a1f346379..c5d078ee7c 100644 --- a/src/calibre/gui2/viewer/tts.py +++ b/src/calibre/gui2/viewer/tts.py @@ -100,6 +100,9 @@ class TTS(QObject): def resume(self, data): self.tts_client.resume() + def resume_after_configure(self, data): + self.tts_client.resume_after_configure() + def callback(self, event): data = event.data if event.type is event.type.mark: diff --git a/src/pyj/read_book/read_aloud.pyj b/src/pyj/read_book/read_aloud.pyj index 3be553c883..5e5b5dac23 100644 --- a/src/pyj/read_book/read_aloud.pyj +++ b/src/pyj/read_book/read_aloud.pyj @@ -137,7 +137,8 @@ class ReadAloud: def play(self): if self.state is PAUSED: - ui_operations.tts('resume') + ui_operations.tts('resume_after_configure' if self.waiting_for_configure else 'resume') + self.waiting_for_configure = False self.state = PLAYING elif self.state is STOPPED: self.send_message('play') @@ -203,7 +204,6 @@ class ReadAloud: self.view.show_next_spine_item() elif which is 'configured': if self.waiting_for_configure: - self.waiting_for_configure = False self.play() if data is not None: pass