def _processMpSpeech(text, language): # MathPlayer's default rate is 180 wpm. # Assume that 0% is 80 wpm and 100% is 450 wpm and scale accordingly. synth = getSynth() wpm = synth._percentToParam(synth.rate, 80, 450) breakMulti = 180.0 / wpm out = [] if language: out.append(LangChangeCommand(language)) resetProsody = set() for m in RE_MP_SPEECH.finditer(text): if m.lastgroup == "break": out.append(BreakCommand(time=int(m.group("break")) * breakMulti)) elif m.lastgroup == "char": out.extend((CharacterModeCommand(True), m.group("char"), CharacterModeCommand(False))) elif m.lastgroup == "comma": out.append(BreakCommand(time=100)) elif m.lastgroup in PROSODY_COMMANDS: command = PROSODY_COMMANDS[m.lastgroup] out.append(command(multiplier=int(m.group(m.lastgroup)) / 100.0)) resetProsody.add(command) elif m.lastgroup == "prosodyReset": for command in resetProsody: out.append(command(multiplier=1)) resetProsody.clear() elif m.lastgroup == "phonemeText": out.append( PhonemeCommand(m.group("ipa"), text=m.group("phonemeText"))) elif m.lastgroup == "content": out.append(m.group(0)) if language: out.append(LangChangeCommand(None)) return out
def messageWithLangDetection(msg: Dict[str, str]) -> None: """Pronounce text in a given language if enabled the setting for auto-switching languages of the synthesizer. After the speech, switche to the previous synthesizer, if the corresponding option is enabled. @param msg: language code and text to be spoken in the specified language @type msg: Dict[str, str] -> {'lang': str, 'text': str} """ switchSynth = config.conf[addonName][services[ config.conf[addonName]['active']].name]['switchsynth'] profile = next( filter(lambda x: x.lang == msg['lang'], (p for s, p in profiles)), None) if switchSynth and profile: profiles.rememberCurrent() profile.set() speechSequence = [] if config.conf['speech']['autoLanguageSwitching']: speechSequence.append(LangChangeCommand(msg['lang'])) if switchSynth and profile: speechSequence.append( CallbackCommand(callback=Thread( target=restoreSynthIfSpeechBeenCanceled).start)) speechSequence.append(msg['text']) if switchSynth and profile: speechSequence.append(CallbackCommand(callback=speech.cancelSpeech)) speech.speak(speechSequence) braille.handler.message(msg['text'])
def test_convertComplex(self): """Test converting a complex speech sequence to SSML. XML generation is already tested by TestXmlBalancer. However, SSML is what callers expect at the end of the day, so test converting a complex speech sequence to SSML. Depends on behavior tested by TestXmlBalancer. """ converter = speechXml.SsmlConverter("en_US") xml = converter.convertToXml([ "t1", PitchCommand(multiplier=2), VolumeCommand(multiplier=2), "t2", PitchCommand(), LangChangeCommand("de_DE"), CharacterModeCommand(True), IndexCommand(1), "c", CharacterModeCommand(False), PhonemeCommand("phIpa", text="phText") ]) self.assertEqual( xml, '<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xml:lang="en-US">' 't1' '<prosody pitch="200%" volume="200%">t2</prosody>' '<prosody volume="200%"><voice xml:lang="de-DE">' '<mark name="1"/><say-as interpret-as="characters">c</say-as>' '<phoneme alphabet="ipa" ph="phIpa">phText</phoneme>' '</voice></prosody></speak>')
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 _normalizeLangCommand(self, command: LangChangeCommand) -> LangChangeCommand: """ Checks if a LangChangeCommand language is compatible with eSpeak. If not, find a default mapping occurs in L{_defaultLangToLocale}. Otherwise, finds a language of a different dialect exists (e.g. ru-ru to ru). Returns an eSpeak compatible LangChangeCommand. """ lowerCaseAvailableLangs = set(lang.lower() for lang in self.availableLanguages) # Use default language if no command.lang is supplied langWithLocale = command.lang if command.lang else self._language langWithLocale = langWithLocale.lower().replace('_', '-') langWithoutLocale: Optional[str] = stripLocaleFromLangCode( langWithLocale) # Check for any language where the language code matches, regardless of dialect: e.g. ru-ru to ru matchingLangs = filter( lambda lang: stripLocaleFromLangCode(lang) == langWithoutLocale, lowerCaseAvailableLangs) anyLocaleMatchingLang = next(matchingLangs, None) # Check from a list of known default mapping locales: e.g. en to en-gb # Created due to eSpeak issue: https://github.com/espeak-ng/espeak-ng/issues/1200 knownDefaultLang = self._defaultLangToLocale.get( langWithoutLocale, None) if knownDefaultLang is not None and knownDefaultLang not in self.availableLanguages: # This means eSpeak has changed and we need to update the mapping log.error( f"Default mapping unknown to eSpeak {knownDefaultLang} not in {self.availableLanguages}" ) knownDefaultLang = None if langWithLocale in lowerCaseAvailableLangs: eSpeakLang = langWithLocale elif knownDefaultLang is not None: eSpeakLang = knownDefaultLang elif langWithoutLocale in lowerCaseAvailableLangs: eSpeakLang = langWithoutLocale elif anyLocaleMatchingLang is not None: eSpeakLang = anyLocaleMatchingLang else: log.debugWarning( f"Unable to find an eSpeak language for '{langWithLocale}'") eSpeakLang = None return LangChangeCommand(eSpeakLang)
def test_stopsSpeakingCase(self): callbackCommand = CallbackCommand(name="dummy", callback=None) lang_en = LangChangeCommand('en') lang_default = LangChangeCommand(None) 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 ]] expectedSpeech = repr([ [ callbackCommand, lang_en, 'The purpose of the wxPyWiki is to provide documentation, examples, how-tos, etc. ', ], 'spoke:True', 'spoke:False', [ lang_en, '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. ' ], 'spoke:True', [ # this sequence seems incorrect, however it persists the "old" behavior: # - it is missing a callback command # - it has no speech, just a meaningless pair of lang change commands lang_en, lang_default ], 'spoke:False' ]) oldSpeech = resetSpeakDest() for seq in createInputSequences(): spoke = old_speakWithoutPauses(seq) oldSpeech.append(f"spoke:{spoke}") self.maxDiff = 5000 # text comparison is quite long, and it is handy to be able to see it in the output. self.assertMultiLineEqual(repr(oldSpeech), expectedSpeech, "generated old speech vs expected") newSpeech = resetSpeakDest() _speakWithoutPauses = SpeechWithoutPauses(speak) for inSeq in createInputSequences(): spoke = _speakWithoutPauses.speakWithoutPauses(inSeq) newSpeech.append(f"spoke:{spoke}") self.assertMultiLineEqual(repr(newSpeech), expectedSpeech, "generated new speech vs expected")
def test_normalizeLangCommand(self): """Test cases for determining a supported eSpeak language from a LangChangeCommand.""" self.assertEqual( LangChangeCommand("en-gb"), SynthDriver._normalizeLangCommand(FakeESpeakSynthDriver, LangChangeCommand(None)), msg="Default language used if language code not provided") self.assertEqual(LangChangeCommand("fr-fr"), SynthDriver._normalizeLangCommand( FakeESpeakSynthDriver, LangChangeCommand("fr_FR")), msg="Language with locale used when available") self.assertEqual(LangChangeCommand("en-gb"), SynthDriver._normalizeLangCommand( FakeESpeakSynthDriver, LangChangeCommand("default")), msg="Default eSpeak language mappings used") self.assertEqual(LangChangeCommand("fr"), SynthDriver._normalizeLangCommand( FakeESpeakSynthDriver, LangChangeCommand("fr_FAKE")), msg="Language without locale used when available") self.assertEqual(LangChangeCommand("ta-ta"), SynthDriver._normalizeLangCommand( FakeESpeakSynthDriver, LangChangeCommand("ta-gb")), msg="Language with any locale used when available") with self.assertLogs(logHandler.log, level=logging.DEBUG) as logContext: self.assertEqual(LangChangeCommand(None), SynthDriver._normalizeLangCommand( FakeESpeakSynthDriver, LangChangeCommand("fake")), msg="No matching available language returns None") self.assertIn("Unable to find an eSpeak language for 'fake'", logContext.output[0])