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 = CallbackCommand(self.stop, name="say-all:stop") speech.speakWithoutPauses( [EndUtteranceCommand(), cb, EndUtteranceCommand()])
def create_EndUtteranceCommand(self, expectedToBecomeIndex=None): """EndUtteranceCommand get converted into IndexCommands by speechManager. The expectedToBecomeIndex argument allow tracking that. @note: the expectedToBecomeIndex is tested to be ordered, contiguous, and unique with respect to other indexed commands to help to prevent errors in the tests. """ self._testCase.assertIsNotNone( expectedToBecomeIndex, "Did you forget to provide the 'expectedToBecomeIndex' argument?") self._assertStrictIndexOrder(expectedToBecomeIndex) e = EndUtteranceCommand() e.expectedIndexCommandIndex = expectedToBecomeIndex return e
def test_redundantSequenceAfterEndUtterance(self): """ Tests that redundant param change and index commands are not emitted as an extra utterance when the preceeding utterance contained param change commands and an EndUtterance command. See PR #11651 E.g. speaking a character. """ smi = SpeechManagerInteractions(self) seq = [ CharacterModeCommand(True), "a", smi.create_ExpectedIndex(1), EndUtteranceCommand(), ] with smi.expectation(): smi.speak(seq) # synth should receive the characterMode, the letter 'a' and an index command. smi.expect_synthSpeak(sequence=seq[:-1]) with smi.expectation(): # Previously, this would result in synth.speak receiving # a call with sequence: # [CharacterModeCommand(True), IndexCommand(2)] # This is a problem because it includes an index command but no speech. # This is inefficient, and also some SAPI5 synths such as Ivona will not # notify of this bookmark. smi.indexReached(1) smi.doneSpeaking() smi.pumpAll()
def test_onlySpaces(self): expected = repr([ 'space', EndUtteranceCommand(), 'tab', EndUtteranceCommand(), ]) output = _getSpellingSpeechWithoutCharMode( text=' \t', locale=None, useCharacterDescriptions=False, sayCapForCapitals=False, capPitchChange=0, beepForCapitals=False, ) self.assertEqual(repr(list(output)), expected)
def test_manySymbolNamesInARow(self): # Spelling a...b seq = (c for c in [ 'a', EndUtteranceCommand(), 'dot', EndUtteranceCommand(), 'dot', EndUtteranceCommand(), 'dot', EndUtteranceCommand(), 'b', EndUtteranceCommand() ]) expected = repr([ CharacterModeCommand(True), 'a', EndUtteranceCommand(), CharacterModeCommand(False), 'dot', EndUtteranceCommand(), 'dot', EndUtteranceCommand(), 'dot', EndUtteranceCommand(), CharacterModeCommand(True), 'b', EndUtteranceCommand() ]) output = _getSpellingSpeechAddCharMode(seq) self.assertEqual(repr(list(output)), expected)
def test_characterMode(self): expected = repr([ 'Alfa', EndUtteranceCommand(), ]) output = _getSpellingSpeechWithoutCharMode( text='a', locale='en', useCharacterDescriptions=True, sayCapForCapitals=False, capPitchChange=0, beepForCapitals=False, ) self.assertEqual(repr(list(output)), expected)
def test_languageDetection(self): config.conf['speech']['autoLanguageSwitching'] = True expected = repr([ LangChangeCommand('fr_FR'), 'a', EndUtteranceCommand(), ]) output = _getSpellingSpeechWithoutCharMode( text='a', locale='fr_FR', useCharacterDescriptions=False, sayCapForCapitals=False, capPitchChange=0, beepForCapitals=False, ) self.assertEqual(repr(list(output)), expected)
def test_cap(self): expected = repr([ PitchCommand(offset=30), BeepCommand(2000, 50, left=50, right=50), 'cap ', 'A', PitchCommand(), EndUtteranceCommand(), ]) output = _getSpellingSpeechWithoutCharMode( text='A', locale=None, useCharacterDescriptions=False, sayCapForCapitals=True, capPitchChange=30, beepForCapitals=True, ) self.assertEqual(repr(list(output)), expected)
def createInputSequences(): """Speech sequences that are input to 'speechWithoutPauses' when triggering the 'read-all' command on the wxPython wiki page. """ return [[ callbackCommand, lang_en, 'The purpose of the wxPyWiki is to provide documentation, examples, how-tos, etc. for helping people ', lang_default ], [ callbackCommand, lang_en, 'learn, understand and use ', lang_default ], [ callbackCommand, 'visited', 'link', '', lang_en, 'wxPython', lang_default, lang_en, '. Anything that falls within those guidelines is fair game. ', lang_default ], [ EndUtteranceCommand(), callbackCommand, lang_en, 'Note: To get to the main wxPython site click ', lang_default ]]
def test_symbolNamesAtStartAndEnd(self): # Spelling ¡hola! seq = (c for c in [ 'inverted exclamation point', EndUtteranceCommand(), 'h', EndUtteranceCommand(), 'o', EndUtteranceCommand(), 'l', EndUtteranceCommand(), 'a', EndUtteranceCommand(), 'bang', EndUtteranceCommand() ]) expected = repr([ 'inverted exclamation point', EndUtteranceCommand(), CharacterModeCommand(True), 'h', EndUtteranceCommand(), 'o', EndUtteranceCommand(), 'l', EndUtteranceCommand(), 'a', EndUtteranceCommand(), CharacterModeCommand(False), 'bang', EndUtteranceCommand() ]) output = _getSpellingSpeechAddCharMode(seq) self.assertEqual(repr(list(output)), expected)
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 = CallbackCommand(self.turnPage, name="say-all:turnPage") speech.speakWithoutPauses([cb, 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 = 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.OutputReason.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