diff --git a/pyttsx/drivers/_espeak.py b/pyttsx/drivers/_espeak.py index 2e069ff..fc6fdcc 100644 --- a/pyttsx/drivers/_espeak.py +++ b/pyttsx/drivers/_espeak.py @@ -9,7 +9,8 @@ import ctypes from ctypes import cdll, c_int, c_char_p, c_wchar_p, POINTER, c_short, c_uint, c_long, c_void_p -from ctypes import CFUNCTYPE, byref, Structure, Union, c_wchar, c_ubyte, c_ulong +from ctypes import CFUNCTYPE, cast, byref, Structure, Union, c_wchar, c_ubyte, c_ulong +from ctypes import string_at, create_string_buffer import time def cfunc(name, dll, result, *args): @@ -339,7 +340,7 @@ def Synth_Mark(text, index_mark, end_position=0, flags=CHARS_AUTO): class VOICE(Structure): _fields_ = [ ('name', c_char_p), - ('languages', c_char_p), + ('languages', c_void_p), ('identifier', c_char_p), ('gender', c_ubyte), ('age', c_ubyte), @@ -354,7 +355,20 @@ def __repr__(self): for field in self._fields_: res.append('%s=%s' % (field[0], repr(getattr(self, field[0])))) return self.__class__.__name__ + '(' + ','.join(res) + ')' - + def getLanguages(self): + languages = [] + c_ubyte_p = POINTER(c_ubyte) + p = self.languages + while True: + priority = ctypes.cast(p, c_ubyte_p).contents.value + if priority == 0: + break + lang = string_at(p + 1) + langlen = len(lang) + languages.append((priority, lang)) + p += 1 + langlen + 1 + languages.sort(lambda x,y: cmp(x[0], y[0])) + return [ t[1] for t in languages ] cListVoices = cfunc('espeak_ListVoices', dll, POINTER(POINTER(VOICE)), ('voice_spec', POINTER(VOICE), 1)) @@ -379,6 +393,17 @@ def ListVoices(voice_spec=None): i += 1 return res +def ListLanguageVoices(language=None): + '''List the languages available for a given language. + + If the language is NULL, all voices are listed.''' + voice_spec_p = None + if language: + voice_spec = VOICE() + voice_spec.languages = cast(create_string_buffer(language), c_void_p) + voice_spec_p = byref(voice_spec) + return ListVoices(voice_spec_p) + SetVoiceByName = cfunc('espeak_SetVoiceByName', dll, c_int, ('name', c_char_p, 1)) SetVoiceByName.__doc__ = '''Searches for a voice with a matching "name" field. Language is not considered. @@ -457,4 +482,4 @@ def synth_cb(wav, numsample, events): #SetParameter(PITCH, 50, 0) print Synth(s) while IsPlaying(): - time.sleep(0.1) \ No newline at end of file + time.sleep(0.1) diff --git a/pyttsx/drivers/espeak.py b/pyttsx/drivers/espeak.py index 106b6d3..0842975 100644 --- a/pyttsx/drivers/espeak.py +++ b/pyttsx/drivers/espeak.py @@ -26,6 +26,8 @@ def buildDriver(proxy): class EspeakDriver(object): _moduleInitialized = False _defaultVoice = '' + genders = [None, 'male', 'female'] + def __init__(self, proxy): if not EspeakDriver._moduleInitialized: # espeak cannot initialize more than once per process and has @@ -57,26 +59,30 @@ def stop(self): if _espeak.IsPlaying(): self._stopping = True + def getVoices(self, language=None): + voices = [] + for v in _espeak.ListLanguageVoices(language): + kwargs = {} + kwargs['id'] = v.name + kwargs['name'] = v.name + if v.languages: + kwargs['languages'] = v.getLanguages() + kwargs['gender'] = self.genders[v.gender] + kwargs['age'] = v.age or None + voices.append(Voice(**kwargs)) + return voices + def getProperty(self, name): if name == 'voices': - voices = [] - for v in _espeak.ListVoices(None): - kwargs = {} - kwargs['id'] = v.name - kwargs['name'] = v.name - if v.languages: - kwargs['languages'] = [v.languages] - genders = [None, 'male', 'female'] - kwargs['gender'] = genders[v.gender] - kwargs['age'] = v.age or None - voices.append(Voice(**kwargs)) - return voices + return self.getVoices() elif name == 'voice': return _espeak.GetCurrentVoice().contents.name elif name == 'rate': return _espeak.GetParameter(_espeak.RATE) elif name == 'volume': return _espeak.GetParameter(_espeak.VOLUME)/100.0 + elif name.startswith('voices-'): + return self.getVoices(name[7:]) else: raise KeyError('unknown property %s' % name)