def nextLine(self): if not self.reader: # We were stopped. return if not self.reader.obj: # The object died, so we should too. self.finish() return bookmark = self.reader.bookmark # Expand to the current line. # We use move end rather than expand # because the user might start in the middle of a line # and we don't want to read from the start of the line in that case. # For lines after the first, it's also more efficient because # we're already at the start of the line, so there's no need to search backwards. delta = self.reader.move(textInfos.UNIT_READINGCHUNK, 1, endPoint="end") if delta <= 0: # No more text. if isinstance(self.reader.obj, textInfos.DocumentWithPageTurns): # Once the last line finishes reading, try turning the page. cb = speech.CallbackCommand(self.turnPage) speech.speakWithoutPauses([cb, speech.EndUtteranceCommand()]) else: self.finish() return # Call lineReached when we start speaking this line. # lineReached will move the cursor and trigger reading of the next line. cb = speech.CallbackCommand( lambda obj=self.reader.obj, state=self.speakTextInfoState.copy( ): self.lineReached(obj, bookmark, state)) spoke = speech.speakTextInfo(self.reader, unit=textInfos.UNIT_READINGCHUNK, reason=controlTypes.REASON_SAYALL, _prefixSpeechCommand=cb, useCache=self.speakTextInfoState) # Collapse to the end of this line, ready to read the next. try: self.reader.collapse(end=True) except RuntimeError: # This occurs in Microsoft Word when the range covers the end of the document. # without this exception to indicate that further collapsing is not possible, say all could enter an infinite loop. self.finish() return if not spoke: # This line didn't include a natural pause, so nothing was spoken. self.numBufferedLines += 1 if self.numBufferedLines < self.MAX_BUFFERED_LINES: # Move on to the next line. # We queue this to allow the user a chance to stop say all. queueHandler.queueFunction(queueHandler.eventQueue, self.nextLine) else: # We don't want to buffer too much. # Force speech. lineReached will resume things when speech catches up. speech.speakWithoutPauses(None) # The first buffered line has now started speaking. self.numBufferedLines -= 1
def finish(self): # There is no more text. # Call stop to clean up, but only after speech completely finishes. # Otherwise, if a different synth is being used for say all, # we might switch synths too early and truncate the final speech. # We do this by putting a CallbackCommand at the start of a new utterance. cb = speech.CallbackCommand(self.stop, name="say-all:stop") speech.speakWithoutPauses( [speech.EndUtteranceCommand(), cb, speech.EndUtteranceCommand()])
def nextLine(self): if not self.reader: log.debug("no self.reader") # We were stopped. return if not self.reader.obj: log.debug("no self.reader.obj") # The object died, so we should too. self.finish() return bookmark = self.reader.bookmark # Expand to the current line. # We use move end rather than expand # because the user might start in the middle of a line # and we don't want to read from the start of the line in that case. # For lines after the first, it's also more efficient because # we're already at the start of the line, so there's no need to search backwards. delta = self.reader.move(textInfos.UNIT_READINGCHUNK, 1, endPoint="end") if delta <= 0: # No more text. if isinstance(self.reader.obj, textInfos.DocumentWithPageTurns): # Once the last line finishes reading, try turning the page. cb = speech.CallbackCommand(self.turnPage, name="say-all:turnPage") speech.speakWithoutPauses([cb, speech.EndUtteranceCommand()]) else: self.finish() return # Copy the speakTextInfoState so that speak callbackCommand # and its associated callback are using a copy isolated to this specific line. state = self.speakTextInfoState.copy() # Call lineReached when we start speaking this line. # lineReached will move the cursor and trigger reading of the next line. def _onLineReached(obj=self.reader.obj, state=state): self.lineReached(obj, bookmark, state) cb = speech.CallbackCommand(_onLineReached, name="say-all:lineReached") # Generate the speech sequence for the reader textInfo # and insert the lineReached callback at the very beginning of the sequence. # _linePrefix on speakTextInfo cannot be used here # As it would be inserted in the sequence after all initial control starts which is too late. speechGen = speech.getTextInfoSpeech(self.reader, unit=textInfos.UNIT_READINGCHUNK, reason=controlTypes.REASON_SAYALL, useCache=state) seq = list(speech._flattenNestedSequences(speechGen)) seq.insert(0, cb) # Speak the speech sequence. spoke = speech.speakWithoutPauses(seq) # Update the textInfo state ready for when speaking the next line. self.speakTextInfoState = state.copy() # Collapse to the end of this line, ready to read the next. try: self.reader.collapse(end=True) except RuntimeError: # This occurs in Microsoft Word when the range covers the end of the document. # without this exception to indicate that further collapsing is not possible, say all could enter an infinite loop. self.finish() return if not spoke: # This line didn't include a natural pause, so nothing was spoken. self.numBufferedLines += 1 if self.numBufferedLines < self.MAX_BUFFERED_LINES: # Move on to the next line. # We queue this to allow the user a chance to stop say all. queueHandler.queueFunction(queueHandler.eventQueue, self.nextLine) else: # We don't want to buffer too much. # Force speech. lineReached will resume things when speech catches up. speech.speakWithoutPauses(None) # The first buffered line has now started speaking. self.numBufferedLines -= 1
def readTextHelper_generator(cursor): if cursor==CURSOR_CARET: try: reader=api.getCaretObject().makeTextInfo(textInfos.POSITION_CARET) except (NotImplementedError, RuntimeError): return else: reader=api.getReviewPosition() lastSentIndex=0 lastReceivedIndex=0 cursorIndexMap={} keepReading=True speakTextInfoState=speech.SpeakTextInfoState(reader.obj) with SayAllProfileTrigger(): while True: if not reader.obj: # The object died, so we should too. return # lastReceivedIndex might be None if other speech was interspersed with this say all. # In this case, we want to send more text in case this was the last chunk spoken. if lastReceivedIndex is None or (lastSentIndex-lastReceivedIndex)<=10: if keepReading: bookmark=reader.bookmark index=lastSentIndex+1 delta=reader.move(textInfos.UNIT_READINGCHUNK,1,endPoint="end") if delta<=0: speech.speakWithoutPauses(None) keepReading=False continue speech.speakTextInfo(reader,unit=textInfos.UNIT_READINGCHUNK,reason=controlTypes.REASON_SAYALL,index=index,useCache=speakTextInfoState) lastSentIndex=index cursorIndexMap[index]=(bookmark,speakTextInfoState.copy()) try: reader.collapse(end=True) except RuntimeError: #MS Word when range covers end of document # Word specific: without this exception to indicate that further collapsing is not posible, say-all could enter an infinite loop. speech.speakWithoutPauses(None) keepReading=False else: # We'll wait for speech to catch up a bit before sending more text. if speech.speakWithoutPauses.lastSentIndex is None or (lastSentIndex-speech.speakWithoutPauses.lastSentIndex)>=10: # There is a large chunk of pending speech # Force speakWithoutPauses to send text to the synth so we can move on. speech.speakWithoutPauses(None) receivedIndex=speech.getLastSpeechIndex() if receivedIndex!=lastReceivedIndex and (lastReceivedIndex!=0 or receivedIndex!=None): lastReceivedIndex=receivedIndex bookmark,state=cursorIndexMap.get(receivedIndex,(None,None)) if state: state.updateObj() if bookmark is not None: updater=reader.obj.makeTextInfo(bookmark) if cursor==CURSOR_CARET: updater.updateCaret() if cursor!=CURSOR_CARET or config.conf["reviewCursor"]["followCaret"]: api.setReviewPosition(updater) elif not keepReading and lastReceivedIndex==lastSentIndex: # All text has been sent to the synth. # Turn the page and start again if the object supports it. if isinstance(reader.obj,textInfos.DocumentWithPageTurns): try: reader.obj.turnPage() except RuntimeError: break else: reader=reader.obj.makeTextInfo(textInfos.POSITION_FIRST) keepReading=True else: break while speech.isPaused: yield yield # Wait until the synth has actually finished speaking. # Otherwise, if there is a triggered profile with a different synth, # we will switch too early and truncate speech (even up to several lines). # Send another index and wait for it. index=lastSentIndex+1 speech.speak([speech.IndexCommand(index)]) while speech.getLastSpeechIndex()<index: yield yield # Some synths say they've handled the index slightly sooner than they actually have, # so wait a bit longer. for i in xrange(30): yield
def readTextHelper_generator(cursor): if cursor == CURSOR_CARET: try: reader = api.getCaretObject().makeTextInfo( textInfos.POSITION_CARET) except (NotImplementedError, RuntimeError): return else: reader = api.getReviewPosition() lastSentIndex = 0 lastReceivedIndex = 0 cursorIndexMap = {} keepReading = True speakTextInfoState = speech.SpeakTextInfoState(reader.obj) start = time.time() with SayAllProfileTrigger(): while True: if not reader.obj: # The object died, so we should too. return # lastReceivedIndex might be None if other speech was interspersed with this say all. # In this case, we want to send more text in case this was the last chunk spoken. if lastReceivedIndex is None or (lastSentIndex - lastReceivedIndex) <= 10: if keepReading: bookmark = reader.bookmark index = lastSentIndex + 1 delta = reader.move(textInfos.UNIT_READINGCHUNK, 1, endPoint="end") if delta <= 0: speech.speakWithoutPauses(None) keepReading = False continue speech.speakTextInfo(reader, unit=textInfos.UNIT_READINGCHUNK, reason=controlTypes.REASON_SAYALL, index=index, useCache=speakTextInfoState) lastSentIndex = index cursorIndexMap[index] = (bookmark, speakTextInfoState.copy()) try: reader.collapse(end=True) except RuntimeError: #MS Word when range covers end of document # Word specific: without this exception to indicate that further collapsing is not posible, say-all could enter an infinite loop. speech.speakWithoutPauses(None) keepReading = False else: # We'll wait for speech to catch up a bit before sending more text. if speech.speakWithoutPauses.lastSentIndex is None or ( lastSentIndex - speech.speakWithoutPauses.lastSentIndex) >= 10: # There is a large chunk of pending speech # Force speakWithoutPauses to send text to the synth so we can move on. speech.speakWithoutPauses(None) receivedIndex = speech.getLastSpeechIndex() if receivedIndex != lastReceivedIndex and ( lastReceivedIndex != 0 or receivedIndex != None): lastReceivedIndex = receivedIndex bookmark, state = cursorIndexMap.get(receivedIndex, (None, None)) if state: state.updateObj() if bookmark is not None: updater = reader.obj.makeTextInfo(bookmark) if cursor == CURSOR_CARET: updater.updateCaret() if cursor != CURSOR_CARET or config.conf["reviewCursor"][ "followCaret"]: api.setReviewPosition(updater, isCaret=cursor == CURSOR_CARET) elif not keepReading and lastReceivedIndex == lastSentIndex: # All text has been sent to the synth. # Turn the page and start again if the object supports it. if isinstance(reader.obj, textInfos.DocumentWithPageTurns): try: reader.obj.turnPage() except RuntimeError: break else: reader = reader.obj.makeTextInfo( textInfos.POSITION_FIRST) keepReading = True else: break while speech.isPaused: yield yield now = time.time() if (now - start) > int(min) * 60 + int(sec): speech.cancelSpeech() break # Wait until the synth has actually finished speaking. # Otherwise, if there is a triggered profile with a different synth, # we will switch too early and truncate speech (even up to several lines). # Send another index and wait for it. index = lastSentIndex + 1 speech.speak([speech.IndexCommand(index)]) while speech.getLastSpeechIndex() < index: yield yield # Some synths say they've handled the index slightly sooner than they actually have, # so wait a bit longer. for i in xrange(30): yield
def readTextHelper_generator(cursor): if cursor==CURSOR_CARET: try: reader=api.getCaretObject().makeTextInfo(textInfos.POSITION_CARET) except (NotImplementedError, RuntimeError): return else: reader=api.getReviewPosition() lastSentIndex=0 lastReceivedIndex=0 cursorIndexMap={} keepReading=True while True: if not reader.obj: # The object died, so we should too. return # lastReceivedIndex might be None if other speech was interspersed with this say all. # In this case, we want to send more text in case this was the last chunk spoken. if lastReceivedIndex is None or (lastSentIndex-lastReceivedIndex)<=10: if keepReading: bookmark=reader.bookmark index=lastSentIndex+1 delta=reader.move(textInfos.UNIT_READINGCHUNK,1,endPoint="end") if delta<=0: speech.speakWithoutPauses(None) keepReading=False continue speech.speakTextInfo(reader,unit=textInfos.UNIT_READINGCHUNK,reason=controlTypes.REASON_SAYALL,index=index) lastSentIndex=index cursorIndexMap[index]=bookmark try: reader.collapse(end=True) except RuntimeError: #MS Word when range covers end of document speech.speakWithoutPauses(None) keepReading=False else: # We'll wait for speech to catch up a bit before sending more text. if speech.speakWithoutPauses.lastSentIndex is None or (lastSentIndex-speech.speakWithoutPauses.lastSentIndex)>=10: # There is a large chunk of pending speech # Force speakWithoutPauses to send text to the synth so we can move on. speech.speakWithoutPauses(None) receivedIndex=speech.getLastSpeechIndex() if receivedIndex!=lastReceivedIndex and (lastReceivedIndex!=0 or receivedIndex!=None): lastReceivedIndex=receivedIndex bookmark=cursorIndexMap.get(receivedIndex,None) if bookmark is not None: updater=reader.obj.makeTextInfo(bookmark) if cursor==CURSOR_CARET: updater.updateCaret() if cursor!=CURSOR_CARET or config.conf["reviewCursor"]["followCaret"]: api.setReviewPosition(updater) elif not keepReading and lastReceivedIndex==lastSentIndex: # All text has been spoken. # Turn the page and start again if the object supports it. if isinstance(reader.obj,textInfos.DocumentWithPageTurns): try: reader.obj.turnPage() except RuntimeError: break else: reader=reader.obj.makeTextInfo(textInfos.POSITION_FIRST) keepReading=True else: break while speech.isPaused: yield yield