|
7 | 7 | from typing import Optional |
8 | 8 | from enum import IntEnum |
9 | 9 | import locale |
10 | | -from collections import OrderedDict |
| 10 | +from collections import OrderedDict, deque |
11 | 11 | import comtypes.client |
12 | 12 | from comtypes import COMError |
13 | 13 | import winreg |
@@ -78,12 +78,23 @@ def Bookmark(self, streamNum, pos, bookmark, bookmarkId): |
78 | 78 | log.debugWarning("Called Bookmark method on SapiSink while driver is dead") |
79 | 79 | return |
80 | 80 | synthIndexReached.notify(synth=synth, index=bookmarkId) |
| 81 | + # remove already triggered bookmarks |
| 82 | + if streamNum in synth._streamBookmarks: |
| 83 | + bookmarks = synth._streamBookmarks[streamNum] |
| 84 | + while bookmarks: |
| 85 | + if bookmarks.popleft() == bookmarkId: |
| 86 | + break |
81 | 87 |
|
82 | 88 | def EndStream(self, streamNum, pos): |
83 | 89 | synth = self.synthRef() |
84 | 90 | if synth is None: |
85 | 91 | log.debugWarning("Called Bookmark method on EndStream while driver is dead") |
86 | 92 | return |
| 93 | + # trigger all untriggered bookmarks |
| 94 | + if streamNum in synth._streamBookmarks: |
| 95 | + for bookmark in synth._streamBookmarks[streamNum]: |
| 96 | + synthIndexReached.notify(synth=synth, index=bookmark) |
| 97 | + del synth._streamBookmarks[streamNum] |
87 | 98 | synthDoneSpeaking.notify(synth=synth) |
88 | 99 | if synth._audioDucker: |
89 | 100 | if audioDucking._isDebug(): |
@@ -138,6 +149,7 @@ def __init__(self, _defaultVoiceToken=None): |
138 | 149 | self._audioDucker = audioDucking.AudioDucker() |
139 | 150 | self._pitch = 50 |
140 | 151 | self._initTts(_defaultVoiceToken) |
| 152 | + self._streamBookmarks = dict() # key = stream num, value = deque of bookmarks |
141 | 153 |
|
142 | 154 | def terminate(self): |
143 | 155 | self._eventsConnection = None |
@@ -263,6 +275,7 @@ def _convertPhoneme(self, ipa): |
263 | 275 |
|
264 | 276 | def speak(self, speechSequence): |
265 | 277 | textList = [] |
| 278 | + bookmarks = deque() |
266 | 279 |
|
267 | 280 | # NVDA SpeechCommands are linear, but XML is hierarchical. |
268 | 281 | # Therefore, we track values for non-empty tags. |
@@ -298,6 +311,7 @@ def outputTags(): |
298 | 311 | textList.append(item.replace("<", "<")) |
299 | 312 | elif isinstance(item, IndexCommand): |
300 | 313 | textList.append('<Bookmark Mark="%d" />' % item.index) |
| 314 | + bookmarks.append(item.index) |
301 | 315 | elif isinstance(item, CharacterModeCommand): |
302 | 316 | if item.state: |
303 | 317 | tags["spell"] = {} |
@@ -397,7 +411,8 @@ def outputTags(): |
397 | 411 | log.debug("Enabling audio ducking due to speak call") |
398 | 412 | tempAudioDucker.enable() |
399 | 413 | try: |
400 | | - self.tts.Speak(text, flags) |
| 414 | + streamNum = self.tts.Speak(text, flags) |
| 415 | + self._streamBookmarks[streamNum] = bookmarks |
401 | 416 | finally: |
402 | 417 | if tempAudioDucker: |
403 | 418 | if audioDucking._isDebug(): |
|
0 commit comments