class SynthDriver(synthDriverHandler.SynthDriver): supportedSettings=(SynthDriver.VoiceSetting(), SynthDriver.VariantSetting(), SynthDriver.RateSetting(), BooleanSynthSetting("rateBoost", _("Rate boos&t")), SynthDriver.PitchSetting(), SynthDriver.InflectionSetting(), SynthDriver.VolumeSetting(), NumericSynthSetting("hsz", _("Head Size"), False), NumericSynthSetting("rgh", _("Roughness"), False), NumericSynthSetting("bth", _("Breathiness"), False), BooleanSynthSetting("backquoteVoiceTags", _("Enable backquote voice &tags"), False)) description='IBMTTS' name='ibmeci' speakingLanguage="" @classmethod def check(cls): return _ibmeci.eciCheck() def __init__(self): _ibmeci.initialize() # This information doesn't really need to be displayed, and makes IBMTTS unusable if the addon is not in the same drive as NVDA executable. # But display it only on debug mode in case of it can be useful log.debug("Using IBMTTS version %s" % _ibmeci.eciVersion()) lang = languageHandler.getLanguage() self.rate=50 self.speakingLanguage=lang self.variant="1" def speak(self,speechSequence): last = None defaultLanguage=self.language outlist = [] for item in speechSequence: if isinstance(item, string_types): s = self.processText(unicode(item)) outlist.append((_ibmeci.speak, (s,))) last = s elif isinstance(item,speech.IndexCommand): outlist.append((_ibmeci.index, (item.index,))) elif isinstance(item,speech.LangChangeCommand): l=None if item.lang in langsAnnotations: l = langsAnnotations[item.lang] elif item.lang and item.lang[0:2] in langsAnnotations: l = langsAnnotations[item.lang[0:2]] if l: if item.lang != self.speakingLanguage and item.lang != self.speakingLanguage[0:2]: outlist.append((_ibmeci.speak, (l,))) self.speakingLanguage=item.lang else: outlist.append((_ibmeci.speak, (langsAnnotations[defaultLanguage],))) self.speakingLanguage = defaultLanguage elif isinstance(item,speech.CharacterModeCommand): outlist.append((_ibmeci.speak, (b"`ts1" if item.state else "b`ts0",))) elif isinstance(item,speech.SpeechCommand): log.debugWarning("Unsupported speech command: %s"%item) else: log.error("Unknown speech: %s"%item) if last is not None and not last[-1] in punctuation: outlist.append((_ibmeci.speak, (b'`p1',))) outlist.append((_ibmeci.setEndStringMark, ())) outlist.append((_ibmeci.speak, (b"`ts0",))) outlist.append((_ibmeci.synth, ())) _ibmeci.synthQueue.put(outlist) _ibmeci.process() def processText(self,text): text = text.rstrip() if _ibmeci.params[9] in (65536, 65537): text = resub(english_fixes, text) if _ibmeci.params[9] in (131072, 131073): text = resub(spanish_fixes, text) if _ibmeci.params[9] in (196609, 196608): text = resub(french_fixes, text) text = text.replace('quil', 'qil') #Sometimes this string make everything buggy with IBMTTS in French #if not self._backquoteVoiceTags: text = text.replace(u'‵', ' ') if self._backquoteVoiceTags: text = "`pp0 `vv%d %s" % (self.getVParam(_ibmeci.vlm), text.replace('`', ' ')) #no embedded commands text = resub(anticrash_res, text) #this converts to ansi for anticrash. If this breaks with foreign langs, we can remove it. text = text.encode('mbcs', 'replace') else: #this converts to ansi for anticrash. If this breaks with foreign langs, we can remove it. text = text.encode('mbcs', 'replace') text = resub(anticrash_res, text) text = b"`pp0 `vv%d %s" % (self.getVParam(_ibmeci.vlm), text.replace(b'`', b' ')) #no embedded commands text = pause_re.sub(br'\1 `p1\2\3', text) text = time_re.sub(br'\1:\2 \3', text) # temporal fix: replace , with `" -" because IBMTTS seems ignore commas at the end. # if you know a better solution please let me know to update it. if text[-1] == b",": text = text[0:-1]+b" -" return text def pause(self,switch): _ibmeci.pause(switch) def terminate(self): _ibmeci.terminate() _backquoteVoiceTags=False def _get_backquoteVoiceTags(self): return self._backquoteVoiceTags def _set_backquoteVoiceTags(self, enable): if enable == self._backquoteVoiceTags: return self._backquoteVoiceTags = enable _rateBoost = False RATE_BOOST_MULTIPLIER = 1.6 def _get_rateBoost(self): return self._rateBoost def _set_rateBoost(self, enable): if enable != self._rateBoost: rate = self.rate self._rateBoost = enable self.rate = rate def _get_rate(self): val = self.getVParam(_ibmeci.rate) if self._rateBoost: val=int(round(val/self.RATE_BOOST_MULTIPLIER)) return self._paramToPercent(val, minRate, maxRate) def _set_rate(self,vl): val = self._percentToParam(vl, minRate, maxRate) if self._rateBoost: val = int(round(val *self.RATE_BOOST_MULTIPLIER)) self._rate = val self.setVParam(_ibmeci.rate, val) def _get_pitch(self): return self.getVParam(_ibmeci.pitch) def _set_pitch(self,vl): self.setVParam(_ibmeci.pitch,vl) def _get_volume(self): return self.getVParam(_ibmeci.vlm) def _set_volume(self,vl): self.setVParam(_ibmeci.vlm,int(vl)) def _set_inflection(self,vl): vl = int(vl) self.setVParam(_ibmeci.fluctuation,vl) def _get_inflection(self): return self.getVParam(_ibmeci.fluctuation) def _set_hsz(self,vl): vl = int(vl) self.setVParam(_ibmeci.hsz,vl) def _get_hsz(self): return self.getVParam(_ibmeci.hsz) def _set_rgh(self,vl): vl = int(vl) self.setVParam(_ibmeci.rgh,vl) def _get_rgh(self): return self.getVParam(_ibmeci.rgh) def _set_bth(self,vl): vl = int(vl) self.setVParam(_ibmeci.bth,vl) def _get_bth(self): return self.getVParam(_ibmeci.bth) def _getAvailableVoices(self): o = OrderedDict() for name in os.listdir(_ibmeci.ttsPath): if name.lower().endswith('.syn'): info = _ibmeci.langs[name.lower()[:3]] o[str(info[0])] = VoiceInfo(str(info[0]), info[1], info[2]) return o def _get_voice(self): return str(_ibmeci.params[9]) def _set_voice(self,vl): _ibmeci.set_voice(vl) def getVParam(self,pr): return _ibmeci.getVParam(pr) def setVParam(self, pr,vl): _ibmeci.setVParam(pr, vl) def _get_lastIndex(self): #fix? return _ibmeci.lastindex def cancel(self): _ibmeci.stop() def _getAvailableVariants(self): global variants return OrderedDict((str(id), synthDriverHandler.VoiceInfo(str(id), name)) for id, name in variants.items()) def _set_variant(self, v): global variants self._variant = v if int(v) in variants else "1" _ibmeci.setVariant(int(v)) self.setVParam(_ibmeci.rate, self._rate) # if 'ibmtts' in config.conf['speech']: # config.conf['speech']['ibmtts']['pitch'] = self.pitch def _get_variant(self): return self._variant
class SynthDriver(SynthDriver): name = "espeak" description = "eSpeak NG" supportedSettings=( SynthDriver.VoiceSetting(), SynthDriver.VariantSetting(), SynthDriver.RateSetting(), # Translators: This is the name of the rate boost voice toggle # which further increases the speaking rate when enabled. BooleanSynthSetting("rateBoost",_("Rate boos&t")), SynthDriver.PitchSetting(), SynthDriver.InflectionSetting(), SynthDriver.VolumeSetting(), ) @classmethod def check(cls): return True def __init__(self): _espeak.initialize() log.info("Using eSpeak version %s" % _espeak.info()) lang=languageHandler.getLanguage() _espeak.setVoiceByLanguage(lang) self._language=lang self._variantDict=_espeak.getVariantDict() self.variant="max" self.rate=30 self.pitch=40 self.inflection=75 def _get_language(self): return self._language PROSODY_ATTRS = { speech.PitchCommand: "pitch", speech.VolumeCommand: "volume", speech.RateCommand: "rate", } IPA_TO_ESPEAK = { u"θ": u"T", u"s": u"s", u"ˈ": u"'", } def _processText(self, text): text = unicode(text) # We need to make several replacements. return text.translate({ 0x1: None, # used for embedded commands 0x3C: u"<", # <: because of XML 0x3E: u">", # >: because of XML 0x5B: u" [", # [: [[ indicates phonemes }) def speak(self,speechSequence): defaultLanguage=self._language textList=[] langChanged=False prosody={} # We output malformed XML, as we might close an outer tag after opening an inner one; e.g. # <voice><prosody></voice></prosody>. # However, eSpeak doesn't seem to mind. for item in speechSequence: if isinstance(item,basestring): textList.append(self._processText(item)) elif isinstance(item,speech.IndexCommand): textList.append("<mark name=\"%d\" />"%item.index) elif isinstance(item,speech.CharacterModeCommand): textList.append("<say-as interpret-as=\"characters\">" if item.state else "</say-as>") elif isinstance(item,speech.LangChangeCommand): if langChanged: textList.append("</voice>") textList.append("<voice xml:lang=\"%s\">"%(item.lang if item.lang else defaultLanguage).replace('_','-')) langChanged=True elif isinstance(item,speech.BreakCommand): textList.append('<break time="%dms" />' % item.time) elif type(item) in self.PROSODY_ATTRS: if prosody: # Close previous prosody tag. textList.append("</prosody>") attr=self.PROSODY_ATTRS[type(item)] if item.multiplier==1: # Returning to normal. try: del prosody[attr] except KeyError: pass else: prosody[attr]=int(item.multiplier* 100) if not prosody: continue textList.append("<prosody") for attr,val in prosody.iteritems(): textList.append(' %s="%d%%"'%(attr,val)) textList.append(">") elif isinstance(item,speech.PhonemeCommand): # We can't use unicode.translate because we want to reject unknown characters. try: phonemes="".join([self.IPA_TO_ESPEAK[char] for char in item.ipa]) # There needs to be a space after the phoneme command. # Otherwise, eSpeak will announce a subsequent SSML tag instead of processing it. textList.append(u"[[%s]] "%phonemes) except KeyError: log.debugWarning("Unknown character in IPA string: %s"%item.ipa) if item.text: textList.append(self._processText(item.text)) elif isinstance(item,speech.SpeechCommand): log.debugWarning("Unsupported speech command: %s"%item) else: log.error("Unknown speech: %s"%item) # Close any open tags. if langChanged: textList.append("</voice>") if prosody: textList.append("</prosody>") text=u"".join(textList) _espeak.speak(text) def cancel(self): _espeak.stop() def pause(self,switch): _espeak.pause(switch) _rateBoost = False RATE_BOOST_MULTIPLIER = 3 def _get_rateBoost(self): return self._rateBoost def _set_rateBoost(self, enable): if enable == self._rateBoost: return rate = self.rate self._rateBoost = enable self.rate = rate def _get_rate(self): val=_espeak.getParameter(_espeak.espeakRATE,1) if self._rateBoost: val=int(val/self.RATE_BOOST_MULTIPLIER) return self._paramToPercent(val,_espeak.minRate,_espeak.maxRate) def _set_rate(self,rate): val=self._percentToParam(rate, _espeak.minRate, _espeak.maxRate) if self._rateBoost: val=int(val*self.RATE_BOOST_MULTIPLIER) _espeak.setParameter(_espeak.espeakRATE,val,0) def _get_pitch(self): val=_espeak.getParameter(_espeak.espeakPITCH,1) return self._paramToPercent(val,_espeak.minPitch,_espeak.maxPitch) def _set_pitch(self,pitch): val=self._percentToParam(pitch, _espeak.minPitch, _espeak.maxPitch) _espeak.setParameter(_espeak.espeakPITCH,val,0) def _get_inflection(self): val=_espeak.getParameter(_espeak.espeakRANGE,1) return self._paramToPercent(val,_espeak.minPitch,_espeak.maxPitch) def _set_inflection(self,val): val=self._percentToParam(val, _espeak.minPitch, _espeak.maxPitch) _espeak.setParameter(_espeak.espeakRANGE,val,0) def _get_volume(self): return _espeak.getParameter(_espeak.espeakVOLUME,1) def _set_volume(self,volume): _espeak.setParameter(_espeak.espeakVOLUME,volume,0) def _getAvailableVoices(self): voices=OrderedDict() for v in _espeak.getVoiceList(): l=v.languages[1:] # #5783: For backwards compatibility, voice identifies should always be lowercase identifier=os.path.basename(v.identifier).lower() voices[identifier]=VoiceInfo(identifier,v.name,l) return voices def _get_voice(self): curVoice=getattr(self,'_voice',None) if curVoice: return curVoice curVoice = _espeak.getCurrentVoice() if not curVoice: return "" # #5783: For backwards compatibility, voice identifies should always be lowercase return curVoice.identifier.split('+')[0].lower() def _set_voice(self, identifier): if not identifier: return # #5783: For backwards compatibility, voice identifies should always be lowercase identifier=identifier.lower() if "\\" in identifier: identifier=os.path.basename(identifier) self._voice=identifier try: _espeak.setVoiceAndVariant(voice=identifier,variant=self._variant) except: self._voice=None raise self._language=super(SynthDriver,self).language def _get_lastIndex(self): return _espeak.lastIndex def terminate(self): _espeak.terminate() def _get_variant(self): return self._variant def _set_variant(self,val): self._variant = val if val in self._variantDict else "max" _espeak.setVoiceAndVariant(variant=self._variant) def _getAvailableVariants(self): return OrderedDict((ID,VoiceInfo(ID, name)) for ID, name in self._variantDict.iteritems())
class SynthDriver(SynthDriver): name = "espeak" description = "eSpeak" supportedSettings = ( SynthDriver.VoiceSetting(), SynthDriver.VariantSetting(), SynthDriver.RateSetting(), # Translators: This is the name of the rate boost voice toggle # which further increases the speaking rate when enabled. BooleanSynthSetting("rateBoost", _("Rate boos&t")), SynthDriver.PitchSetting(), SynthDriver.InflectionSetting(), SynthDriver.VolumeSetting(), ) @classmethod def check(cls): return True def __init__(self): _espeak.initialize() log.info("Using eSpeak version %s" % _espeak.info()) lang = languageHandler.getLanguage() _espeak.setVoiceByLanguage(lang) self._language = lang self._variantDict = _espeak.getVariantDict() self.variant = "max" self.rate = 30 self.pitch = 40 self.inflection = 75 def _get_language(self): return self._language def speak(self, speechSequence): defaultLanguage = self._language textList = [] langChanged = False for item in speechSequence: if isinstance(item, basestring): s = unicode(item) # Replace \01, as this is used for embedded commands. #Also replace < and > as espeak handles xml s.translate({ ord(u'\01'): None, ord(u'<'): u'<', ord(u'>'): u'>' }) textList.append(s) elif isinstance(item, speech.IndexCommand): textList.append("<mark name=\"%d\" />" % item.index) elif isinstance(item, speech.CharacterModeCommand): textList.append("<say-as interpret-as=\"characters\">" if item. state else "</say-as>") elif isinstance(item, speech.LangChangeCommand): if langChanged: textList.append("</voice>") textList.append( "<voice xml:lang=\"%s\">" % (item.lang if item.lang else defaultLanguage).replace( '_', '-')) langChanged = True elif isinstance(item, speech.SpeechCommand): log.debugWarning("Unsupported speech command: %s" % item) else: log.error("Unknown speech: %s" % item) if langChanged: textList.append("</voice>") text = u"".join(textList) _espeak.speak(text) def cancel(self): _espeak.stop() def pause(self, switch): _espeak.pause(switch) _rateBoost = False RATE_BOOST_MULTIPLIER = 3 def _get_rateBoost(self): return self._rateBoost def _set_rateBoost(self, enable): if enable == self._rateBoost: return rate = self.rate self._rateBoost = enable self.rate = rate def _get_rate(self): val = _espeak.getParameter(_espeak.espeakRATE, 1) if self._rateBoost: val = int(val / self.RATE_BOOST_MULTIPLIER) return self._paramToPercent(val, _espeak.minRate, _espeak.maxRate) def _set_rate(self, rate): val = self._percentToParam(rate, _espeak.minRate, _espeak.maxRate) if self._rateBoost: val = int(val * self.RATE_BOOST_MULTIPLIER) _espeak.setParameter(_espeak.espeakRATE, val, 0) def _get_pitch(self): val = _espeak.getParameter(_espeak.espeakPITCH, 1) return self._paramToPercent(val, _espeak.minPitch, _espeak.maxPitch) def _set_pitch(self, pitch): val = self._percentToParam(pitch, _espeak.minPitch, _espeak.maxPitch) _espeak.setParameter(_espeak.espeakPITCH, val, 0) def _get_inflection(self): val = _espeak.getParameter(_espeak.espeakRANGE, 1) return self._paramToPercent(val, _espeak.minPitch, _espeak.maxPitch) def _set_inflection(self, val): val = self._percentToParam(val, _espeak.minPitch, _espeak.maxPitch) _espeak.setParameter(_espeak.espeakRANGE, val, 0) def _get_volume(self): return _espeak.getParameter(_espeak.espeakVOLUME, 1) def _set_volume(self, volume): _espeak.setParameter(_espeak.espeakVOLUME, volume, 0) def _getAvailableVoices(self): voices = OrderedDict() for v in _espeak.getVoiceList(): l = v.languages[1:] identifier = os.path.basename(v.identifier) voices[identifier] = VoiceInfo(identifier, v.name, l) return voices def _get_voice(self): curVoice = getattr(self, '_voice', None) if curVoice: return curVoice curVoice = _espeak.getCurrentVoice() if not curVoice: return "" return curVoice.identifier.split('+')[0] def _set_voice(self, identifier): if not identifier: return if "\\" in identifier: identifier = os.path.basename(identifier) self._voice = identifier try: _espeak.setVoiceAndVariant(voice=identifier, variant=self._variant) except: self._voice = None raise self._language = super(SynthDriver, self).language def _get_lastIndex(self): return _espeak.lastIndex def terminate(self): _espeak.terminate() def _get_variant(self): return self._variant def _set_variant(self, val): self._variant = val if val in self._variantDict else "max" _espeak.setVoiceAndVariant(variant=self._variant) def _getAvailableVariants(self): return OrderedDict((ID, VoiceInfo(ID, name)) for ID, name in self._variantDict.iteritems())