Read Aloud: Fix clicking in text to change what is being read causing tracking of currently read word to fail when using the legacy Windows TTS engine (winsapi)

See #2080705 (Download read aloud api failed)
This commit is contained in:
Kovid Goyal 2024-09-26 11:42:35 +05:30
parent 152af7ae51
commit c07c662a5a
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C

View File

@ -13,8 +13,9 @@ class QtTTSBackend(TTSBackend):
super().__init__(parent) super().__init__(parent)
self.speaking_text = '' self.speaking_text = ''
self.last_word_offset = 0 self.last_word_offset = 0
self._qt_reload_after_configure(engine_name)
self.last_spoken_word = None self.last_spoken_word = None
self.ignore_tracking_until_state_changes_to_speaking = False
self._qt_reload_after_configure(engine_name)
@property @property
def available_voices(self) -> dict[str, tuple[Voice, ...]]: def available_voices(self) -> dict[str, tuple[Voice, ...]]:
@ -43,6 +44,8 @@ class QtTTSBackend(TTSBackend):
self.last_word_offset = 0 self.last_word_offset = 0
self.last_spoken_word = None self.last_spoken_word = None
self.speaking_text = text self.speaking_text = text
if self.tts.engine() == 'sapi':
self.ignore_tracking_until_state_changes_to_speaking = True
self.tts.say(text) self.tts.say(text)
def error_message(self) -> str: def error_message(self) -> str:
@ -96,18 +99,23 @@ class QtTTSBackend(TTSBackend):
self._current_settings = settings self._current_settings = settings
def _saying_word(self, word: str, utterance_id: int, start: int, length: int) -> None: def _saying_word(self, word: str, utterance_id: int, start: int, length: int) -> None:
# print(f'{repr(word)=} {start=} {length=}, {repr(self.speaking_text[start:start+length])=} {self.ignore_tracking_until_state_changes_to_speaking=}')
if self.ignore_tracking_until_state_changes_to_speaking:
return
# Qt's word tracking is broken with non-BMP unicode chars, the # Qt's word tracking is broken with non-BMP unicode chars, the
# start and length values are totally wrong, so track manually # start and length values are totally wrong, so track manually
# print(f'{repr(word)=} {idx=} {start=} {length=}, {repr(self.speaking_text[start:start+length])=}')
key = word, start, length key = word, start, length
# Qt sometimes repeats words with windows modern engine at least, for a test case see https://bugs.launchpad.net/calibre/+bug/2080708 # Qt sometimes repeats words with windows modern engine at least, for a test case see https://bugs.launchpad.net/calibre/+bug/2080708
if self.last_spoken_word == key: if self.last_spoken_word == key:
return return
self.last_spoken_word = key self.last_spoken_word = key
idx = self.speaking_text.find(word, self.last_word_offset) idx = self.speaking_text.find(word, self.last_word_offset)
# print(f'{self.last_word_offset=} {idx=}')
if idx > -1: if idx > -1:
self.saying.emit(idx, len(word)) self.saying.emit(idx, len(word))
self.last_word_offset = idx + len(word) self.last_word_offset = idx + len(word)
def _state_changed(self, state: QTextToSpeech.State) -> None: def _state_changed(self, state: QTextToSpeech.State) -> None:
if self.ignore_tracking_until_state_changes_to_speaking and state is QTextToSpeech.State.Speaking:
self.ignore_tracking_until_state_changes_to_speaking = False
self.state_changed.emit(state) self.state_changed.emit(state)