first commit

This commit is contained in:
2025-04-04 13:23:15 -06:00
commit 216064f731
2103 changed files with 522593 additions and 0 deletions

View File

@@ -0,0 +1,32 @@
from .engine import Engine
import weakref
_activeEngines = weakref.WeakValueDictionary()
def init(driverName=None, debug=False):
'''
Constructs a new TTS engine instance or reuses the existing instance for
the driver name.
@param driverName: Name of the platform specific driver to use. If
None, selects the default driver for the operating system.
@type: str
@param debug: Debugging output enabled or not
@type debug: bool
@return: Engine instance
@rtype: L{engine.Engine}
'''
try:
eng = _activeEngines[driverName]
except KeyError:
eng = Engine(driverName, debug)
_activeEngines[driverName] = eng
return eng
def speak(text):
engine = init()
engine.say(text)
engine.runAndWait()

View File

@@ -0,0 +1,226 @@
import sys
import traceback
import weakref
import importlib
class DriverProxy(object):
'''
Proxy to a driver implementation.
@ivar _module: Module containing the driver implementation
@type _module: module
@ivar _engine: Reference to the engine that owns the driver
@type _engine: L{engine.Engine}
@ivar _queue: Queue of commands outstanding for the driver
@type _queue: list
@ivar _busy: True when the driver is busy processing a command, False when
not
@type _busy: bool
@ivar _name: Name associated with the current utterance
@type _name: str
@ivar _debug: Debugging output enabled or not
@type _debug: bool
@ivar _iterator: Driver iterator to invoke when in an external run loop
@type _iterator: iterator
'''
def __init__(self, engine, driverName, debug):
'''
Constructor.
@param engine: Reference to the engine that owns the driver
@type engine: L{engine.Engine}
@param driverName: Name of the driver module to use under drivers/ or
None to select the default for the platform
@type driverName: str
@param debug: Debugging output enabled or not
@type debug: bool
'''
if driverName is None:
# pick default driver for common platforms
if sys.platform == 'darwin':
driverName = 'nsss'
elif sys.platform == 'win32':
driverName = 'sapi5'
else:
driverName = 'espeak'
# import driver module
name = 'pyttsx3.drivers.%s' % driverName
self._module = importlib.import_module(name)
# build driver instance
self._driver = self._module.buildDriver(weakref.proxy(self))
# initialize refs
self._engine = engine
self._queue = []
self._busy = True
self._name = None
self._iterator = None
self._debug = debug
self._current_text = ''
def __del__(self):
try:
self._driver.destroy()
except (AttributeError, TypeError):
pass
def _push(self, mtd, args, name=None):
'''
Adds a command to the queue.
@param mtd: Method to invoke to process the command
@type mtd: method
@param args: Arguments to apply when invoking the method
@type args: tuple
@param name: Name associated with the command
@type name: str
'''
self._queue.append((mtd, args, name))
self._pump()
def _pump(self):
'''
Attempts to process the next command in the queue if one exists and the
driver is not currently busy.
'''
while (not self._busy) and len(self._queue):
cmd = self._queue.pop(0)
self._name = cmd[2]
try:
cmd[0](*cmd[1])
except Exception as e:
self.notify('error', exception=e)
if self._debug:
traceback.print_exc()
def notify(self, topic, **kwargs):
'''
Sends a notification to the engine from the driver.
@param topic: Notification topic
@type topic: str
@param kwargs: Arbitrary keyword arguments
@type kwargs: dict
'''
if 'name' not in kwargs or kwargs['name'] is None: # Avoid overwriting
kwargs['name'] = self._name
self._engine._notify(topic, **kwargs)
def setBusy(self, busy):
'''
Called by the driver to indicate it is busy.
@param busy: True when busy, false when idle
@type busy: bool
'''
self._busy = busy
if not self._busy:
self._pump()
def isBusy(self):
'''
@return: True if the driver is busy, false if not
@rtype: bool
'''
return self._busy
def say(self, text, name):
'''
Called by the engine to push a say command onto the queue.
@param text: Text to speak
@type text: unicode
@param name: Name to associate with the utterance
@type name: str
'''
self._current_text = text
self._push(self._driver.say, (text,), name)
def stop(self):
'''
Called by the engine to stop the current utterance and clear the queue
of commands.
'''
# clear queue up to first end loop command
while(True):
try:
mtd, args, name = self._queue[0]
except IndexError:
break
if(mtd == self._engine.endLoop):
break
self._queue.pop(0)
self._driver.stop()
def save_to_file(self, text, filename, name):
'''
Called by the engine to push a say command onto the queue.
@param text: Text to speak
@type text: unicode
@param name: Name to associate with the utterance
@type name: str
'''
self._push(self._driver.save_to_file, (text, filename), name)
def getProperty(self, name):
'''
Called by the engine to get a driver property value.
@param name: Name of the property
@type name: str
@return: Property value
@rtype: object
'''
return self._driver.getProperty(name)
def setProperty(self, name, value):
'''
Called by the engine to set a driver property value.
@param name: Name of the property
@type name: str
@param value: Property value
@type value: object
'''
self._push(self._driver.setProperty, (name, value))
def runAndWait(self):
'''
Called by the engine to start an event loop, process all commands in
the queue at the start of the loop, and then exit the loop.
'''
self._push(self._engine.endLoop, tuple())
self._driver.startLoop()
def startLoop(self, useDriverLoop):
'''
Called by the engine to start an event loop.
'''
if useDriverLoop:
self._driver.startLoop()
else:
self._iterator = self._driver.iterate()
def endLoop(self, useDriverLoop):
'''
Called by the engine to stop an event loop.
'''
self._queue = []
self._driver.stop()
if useDriverLoop:
self._driver.endLoop()
else:
self._iterator = None
self.setBusy(True)
def iterate(self):
'''
Called by the engine to iterate driver commands and notifications from
within an external event loop.
'''
try:
next(self._iterator)
except StopIteration:
pass

View File

@@ -0,0 +1,23 @@
'''
Utility functions to help with Python 2/3 compatibility
'''
from .. import six
def toUtf8(value):
'''
Takes in a value and converts it to a text (unicode) type. Then decodes that
type to a byte array encoded in utf-8. In 2.X the resulting object will be a
str and in 3.X the resulting object will be bytes. In both 2.X and 3.X any
object can be passed in and the object's __str__ will be used (or __repr__ if
__str__ is not defined) if the object is not already a text type.
'''
return six.text_type(value).encode('utf-8')
def fromUtf8(value):
'''
Takes in a byte array encoded as utf-8 and returns a text (unicode) type. In
2.X we expect a str type and return a unicde type. In 3.X we expect a bytes
type and return a str type.
'''
return value.decode('utf-8')

View File

@@ -0,0 +1,500 @@
from __future__ import print_function
import time
from ctypes import (CFUNCTYPE, POINTER, Structure, Union, c_char_p, c_int,
c_long, c_short, c_ubyte, c_uint, c_ulong, c_void_p,
c_wchar, cdll)
def cfunc(name, dll, result, *args):
"""build and apply a ctypes prototype complete with parameter flags"""
atypes = []
aflags = []
for arg in args:
atypes.append(arg[1])
aflags.append((arg[2], arg[0]) + arg[3:])
return CFUNCTYPE(result, *atypes)((name, dll), tuple(aflags))
dll = None
def load_library():
global dll
paths = [
# macOS paths
'/usr/local/lib/libespeak-ng.1.dylib',
'/usr/local/lib/libespeak.dylib',
# Linux paths
'libespeak-ng.so.1',
'/usr/local/lib/libespeak-ng.so.1',
'libespeak.so.1',
# Windows paths
r'C:\Program Files\eSpeak NG\libespeak-ng.dll',
r'C:\Program Files (x86)\eSpeak NG\libespeak-ng.dll'
]
for path in paths:
try:
dll = cdll.LoadLibrary(path)
return True
except Exception:
continue # Try the next path
return False
try:
if not load_library():
raise RuntimeError("This means you probably do not have eSpeak or eSpeak-ng installed!")
except Exception as exp:
raise
# constants and such from speak_lib.h
EVENT_LIST_TERMINATED = 0
EVENT_WORD = 1
EVENT_SENTENCE = 2
EVENT_MARK = 3
EVENT_PLAY = 4
EVENT_END = 5
EVENT_MSG_TERMINATED = 6
class numberORname(Union):
_fields_ = [
('number', c_int),
('name', c_char_p)
]
class EVENT(Structure):
_fields_ = [
('type', c_int),
('unique_identifier', c_uint),
('text_position', c_int),
('length', c_int),
('audio_position', c_int),
('sample', c_int),
('user_data', c_void_p),
('id', numberORname)
]
AUDIO_OUTPUT_PLAYBACK = 0
AUDIO_OUTPUT_RETRIEVAL = 1
AUDIO_OUTPUT_SYNCHRONOUS = 2
AUDIO_OUTPUT_SYNCH_PLAYBACK = 3
EE_OK = 0
EE_INTERNAL_ERROR = -1
EE_BUFFER_FULL = 1
EE_NOT_FOUND = 2
Initialize = cfunc('espeak_Initialize', dll, c_int,
('output', c_int, 1, AUDIO_OUTPUT_PLAYBACK),
('bufflength', c_int, 1, 100),
('path', c_char_p, 1, None),
('option', c_int, 1, 0))
Initialize.__doc__ = """Must be called before any synthesis functions are called.
output: the audio data can either be played by eSpeak or passed back by the SynthCallback function.
buflength: The length in mS of sound buffers passed to the SynthCallback function.
path: The directory which contains the espeak-data directory, or NULL for the default location.
options: bit 0: 1=allow espeakEVENT_PHONEME events.
Returns: sample rate in Hz, or -1 (EE_INTERNAL_ERROR)."""
t_espeak_callback = CFUNCTYPE(c_int, POINTER(c_short), c_int, POINTER(EVENT))
cSetSynthCallback = cfunc('espeak_SetSynthCallback', dll, None,
('SynthCallback', t_espeak_callback, 1))
SynthCallback = None
def SetSynthCallback(cb):
global SynthCallback
SynthCallback = t_espeak_callback(cb)
cSetSynthCallback(SynthCallback)
SetSynthCallback.__doc__ = """Must be called before any synthesis functions are called.
This specifies a function in the calling program which is called when a buffer of
speech sound data has been produced.
The callback function is of the form:
int SynthCallback(short *wav, int numsamples, espeak_EVENT *events);
wav: is the speech sound data which has been produced.
NULL indicates that the synthesis has been completed.
numsamples: is the number of entries in wav. This number may vary, may be less than
the value implied by the buflength parameter given in espeak_Initialize, and may
sometimes be zero (which does NOT indicate end of synthesis).
events: an array of espeak_EVENT items which indicate word and sentence events, and
also the occurance if <mark> and <audio> elements within the text.
Callback returns: 0=continue synthesis, 1=abort synthesis."""
t_UriCallback = CFUNCTYPE(c_int, c_int, c_char_p, c_char_p)
cSetUriCallback = cfunc('espeak_SetUriCallback', dll, None,
('UriCallback', t_UriCallback, 1))
UriCallback = None
def SetUriCallback(cb):
global UriCallback
UriCallback = t_UriCallback(UriCallback)
cSetUriCallback(UriCallback)
SetUriCallback.__doc__ = """This function must be called before synthesis functions are used, in order to deal with
<audio> tags. It specifies a callback function which is called when an <audio> element is
encountered and allows the calling program to indicate whether the sound file which
is specified in the <audio> element is available and is to be played.
The callback function is of the form:
int UriCallback(int type, const char *uri, const char *base);
type: type of callback event. Currently only 1= <audio> element
uri: the "src" attribute from the <audio> element
base: the "xml:base" attribute (if any) from the <speak> element
Return: 1=don't play the sound, but speak the text alternative.
0=place a PLAY event in the event list at the point where the <audio> element
occurs. The calling program can then play the sound at that point."""
# a few manifest constants
CHARS_AUTO = 0
CHARS_UTF8 = 1
CHARS_8BIT = 2
CHARS_WCHAR = 3
SSML = 0x10
PHONEMES = 0x100
ENDPAUSE = 0x1000
KEEP_NAMEDATA = 0x2000
POS_CHARACTER = 1
POS_WORD = 2
POS_SENTENCE = 3
def Synth(text, position=0, position_type=POS_CHARACTER, end_position=0, flags=0, user_data=None):
return cSynth(text, len(text) * 10, position, position_type, end_position, flags, None, user_data)
cSynth = cfunc('espeak_Synth', dll, c_int,
('text', c_char_p, 1),
('size', c_long, 1),
('position', c_uint, 1, 0),
('position_type', c_int, 1, POS_CHARACTER),
('end_position', c_uint, 1, 0),
('flags', c_uint, 1, CHARS_AUTO),
('unique_identifier', POINTER(c_uint), 1, None),
('user_data', c_void_p, 1, None))
Synth.__doc__ = """Synthesize speech for the specified text. The speech sound data is passed to the calling
program in buffers by means of the callback function specified by espeak_SetSynthCallback(). The command is asynchronous: it is internally buffered and returns as soon as possible. If espeak_Initialize was previously called with AUDIO_OUTPUT_PLAYBACK as argument, the sound data are played by eSpeak.
text: The text to be spoken, terminated by a zero character. It may be either 8-bit characters,
wide characters (wchar_t), or UTF8 encoding. Which of these is determined by the "flags"
parameter.
size: Equal to (or greater than) the size of the text data, in bytes. This is used in order
to allocate internal storage space for the text. This value is not used for
AUDIO_OUTPUT_SYNCHRONOUS mode.
position: The position in the text where speaking starts. Zero indicates speak from the
start of the text.
position_type: Determines whether "position" is a number of characters, words, or sentences.
Values:
end_position: If set, this gives a character position at which speaking will stop. A value
of zero indicates no end position.
flags: These may be OR'd together:
Type of character codes, one of:
espeak.CHARS_UTF8 UTF8 encoding
espeak.CHARS_8BIT The 8 bit ISO-8859 character set for the particular language.
espeak.CHARS_AUTO 8 bit or UTF8 (this is the default)
espeak.CHARS_WCHAR Wide characters (wchar_t)
espeak.SSML Elements within < > are treated as SSML elements, or if not recognised are ignored.
espeak.PHONEMES Text within [[ ]] is treated as phonemes codes (in espeak's Hirschenbaum encoding).
espeak.ENDPAUSE If set then a sentence pause is added at the end of the text. If not set then
this pause is suppressed.
unique_identifier: message identifier; helpful for identifying later
data supplied to the callback.
user_data: pointer which will be passed to the callback function.
Return: EE_OK: operation achieved
EE_BUFFER_FULL: the command can not be buffered;
you may try after a while to call the function again.
EE_INTERNAL_ERROR."""
def Synth_Mark(text, index_mark, end_position=0, flags=CHARS_AUTO):
cSynth_Mark(text, len(text) + 1, index_mark, end_position, flags)
cSynth_Mark = cfunc('espeak_Synth_Mark', dll, c_int,
('text', c_char_p, 1),
('size', c_ulong, 1),
('index_mark', c_char_p, 1),
('end_position', c_uint, 1, 0),
('flags', c_uint, 1, CHARS_AUTO),
('unique_identifier', POINTER(c_uint), 1, None),
('user_data', c_void_p, 1, None))
Synth_Mark.__doc__ = """Synthesize speech for the specified text. Similar to espeak_Synth() but the start position is
specified by the name of a <mark> element in the text.
index_mark: The "name" attribute of a <mark> element within the text which specified the
point at which synthesis starts. UTF8 string.
For the other parameters, see espeak_Synth()
Return: EE_OK: operation achieved
EE_BUFFER_FULL: the command can not be buffered;
you may try after a while to call the function again.
EE_INTERNAL_ERROR."""
Key = cfunc('espeak_Key', dll, c_int,
('key_name', c_char_p, 1))
Key.__doc__ = """Speak the name of a keyboard key.
Currently this just speaks the "key_name" as given
Return: EE_OK: operation achieved
EE_BUFFER_FULL: the command can not be buffered;
you may try after a while to call the function again.
EE_INTERNAL_ERROR."""
Char = cfunc('espeak_Char', dll, c_int,
('character', c_wchar, 1))
Char.__doc__ = """Speak the name of the given character
Return: EE_OK: operation achieved
EE_BUFFER_FULL: the command can not be buffered;
you may try after a while to call the function again.
EE_INTERNAL_ERROR."""
# Speech Parameters
SILENCE = 0 # internal use
RATE = 1
VOLUME = 2
PITCH = 3
RANGE = 4
PUNCTUATION = 5
CAPITALS = 6
EMPHASIS = 7 # internal use
LINELENGTH = 8 # internal use
PUNCT_NONE = 0
PUNCT_ALL = 1
PUNCT_SOME = 2
SetParameter = cfunc('espeak_SetParameter', dll, c_int,
('parameter', c_int, 1),
('value', c_int, 1),
('relative', c_int, 1, 0))
SetParameter.__doc__ = """Sets the value of the specified parameter.
relative=0 Sets the absolute value of the parameter.
relative=1 Sets a relative value of the parameter.
parameter:
espeak.RATE: speaking speed in word per minute.
espeak.VOLUME: volume in range 0-100 0=silence
espeak.PITCH: base pitch, range 0-100. 50=normal
espeak.RANGE: pitch range, range 0-100. 0-monotone, 50=normal
espeak.PUNCTUATION: which punctuation characters to announce:
value in espeak_PUNCT_TYPE (none, all, some),
see espeak_GetParameter() to specify which characters are announced.
espeak.CAPITALS: announce capital letters by:
0=none,
1=sound icon,
2=spelling,
3 or higher, by raising pitch. This values gives the amount in Hz by which the pitch
of a word raised to indicate it has a capital letter.
Return: EE_OK: operation achieved
EE_BUFFER_FULL: the command can not be buffered;
you may try after a while to call the function again.
EE_INTERNAL_ERROR."""
GetParameter = cfunc('espeak_GetParameter', dll, c_int,
('parameter', c_int, 1))
GetParameter.__doc__ = """current=0 Returns the default value of the specified parameter.
current=1 Returns the current value of the specified parameter, as set by SetParameter()"""
SetPunctuationList = cfunc('espeak_SetPunctuationList', dll, c_int,
('punctlist', c_wchar, 1))
SetPunctuationList.__doc__ = """Specified a list of punctuation characters whose names are
to be spoken when the value of the Punctuation parameter is set to "some".
punctlist: A list of character codes, terminated by a zero character.
Return: EE_OK: operation achieved
EE_BUFFER_FULL: the command can not be buffered;
you may try after a while to call the function again.
EE_INTERNAL_ERROR."""
SetPhonemeTrace = cfunc('espeak_SetPhonemeTrace', dll, None,
('value', c_int, 1),
('stream', c_void_p, 1))
SetPhonemeTrace.__doc__ = """Controls the output of phoneme symbols for the text
value=0 No phoneme output (default)
value=1 Output the translated phoneme symbols for the text
value=2 as (1), but also output a trace of how the translation was done (matching rules and list entries)
stream output stream for the phoneme symbols (and trace). If stream=NULL then it uses stdout."""
CompileDictionary = cfunc('espeak_CompileDictionary', dll, None,
('path', c_char_p, 1),
('log', c_void_p, 1))
CompileDictionary.__doc__ = """Compile pronunciation dictionary for a language which corresponds to the currently
selected voice. The required voice should be selected before calling this function.
path: The directory which contains the language's '_rules' and '_list' files.
'path' should end with a path separator character ('/').
log: Stream for error reports and statistics information. If log=NULL then stderr will be used."""
class VOICE(Structure):
_fields_ = [
('name', c_char_p),
('languages', c_char_p),
('identifier', c_char_p),
('gender', c_ubyte),
('age', c_ubyte),
('variant', c_ubyte),
('xx1', c_ubyte),
('score', c_int),
('spare', c_void_p),
]
def __repr__(self):
"""Print the fields"""
res = []
for field in self._fields_:
res.append('%s=%s' % (field[0], repr(getattr(self, field[0]))))
return self.__class__.__name__ + '(' + ','.join(res) + ')'
cListVoices = cfunc('espeak_ListVoices', dll, POINTER(POINTER(VOICE)),
('voice_spec', POINTER(VOICE), 1))
cListVoices.__doc__ = """Reads the voice files from espeak-data/voices and creates an array of espeak_VOICE pointers.
The list is terminated by a NULL pointer
If voice_spec is NULL then all voices are listed.
If voice spec is given, then only the voices which are compatible with the voice_spec
are listed, and they are listed in preference order."""
def ListVoices(voice_spec=None):
"""Reads the voice files from espeak-data/voices and returns a list of VOICE objects.
If voice_spec is None then all voices are listed.
If voice spec is given, then only the voices which are compatible with the voice_spec
are listed, and they are listed in preference order."""
ppv = cListVoices(voice_spec)
res = []
i = 0
while ppv[i]:
res.append(ppv[i][0])
i += 1
return res
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.
"name" is a UTF8 string.
Return: EE_OK: operation achieved
EE_BUFFER_FULL: the command can not be buffered;
you may try after a while to call the function again.
EE_INTERNAL_ERROR."""
SetVoiceByProperties = cfunc('espeak_SetVoiceByProperties', dll, c_int,
('voice_spec', POINTER(VOICE), 1))
SetVoiceByProperties.__doc__ = """An espeak_VOICE structure is used to pass criteria to select a voice. Any of the following
fields may be set:
name NULL, or a voice name
languages NULL, or a single language string (with optional dialect), eg. "en-uk", or "en"
gender 0=not specified, 1=male, 2=female
age 0=not specified, or an age in years
variant After a list of candidates is produced, scored and sorted, "variant" is used to index
that list and choose a voice.
variant=0 takes the top voice (i.e. best match). variant=1 takes the next voice, etc"""
GetCurrentVoice = cfunc('espeak_GetCurrentVoice', dll, POINTER(VOICE),
)
GetCurrentVoice.__doc__ = """Returns the espeak_VOICE data for the currently selected voice.
This is not affected by temporary voice changes caused by SSML elements such as <voice> and <s>"""
Cancel = cfunc('espeak_Cancel', dll, c_int)
Cancel.__doc__ = """Stop immediately synthesis and audio output of the current text. When this
function returns, the audio output is fully stopped and the synthesizer is ready to
synthesize a new message.
Return: EE_OK: operation achieved
EE_INTERNAL_ERROR."""
IsPlaying = cfunc('espeak_IsPlaying', dll, c_int)
IsPlaying.__doc__ = """Returns 1 if audio is played, 0 otherwise."""
Synchronize = cfunc('espeak_Synchronize', dll, c_int)
Synchronize.__doc__ = """This function returns when all data have been spoken.
Return: EE_OK: operation achieved
EE_INTERNAL_ERROR."""
Terminate = cfunc('espeak_Terminate', dll, c_int)
Terminate.__doc__ = """last function to be called.
Return: EE_OK: operation achieved
EE_INTERNAL_ERROR."""
Info = cfunc('espeak_Info', dll, c_char_p, ('ptr', c_void_p, 1, 0))
Info.__doc__ = """Returns the version number string.
The parameter is for future use, and should be set to NULL"""
if __name__ == '__main__':
def synth_cb(wav, numsample, events):
print(numsample, end="")
i = 0
while True:
if events[i].type == EVENT_LIST_TERMINATED:
break
print(events[i].type, end="")
i += 1
return 0
samplerate = Initialize(output=AUDIO_OUTPUT_PLAYBACK)
SetSynthCallback(synth_cb)
s = 'This is a test, only a test. '
uid = c_uint(0)
# print 'pitch=',GetParameter(PITCH)
# SetParameter(PITCH, 50, 0)
print(Synth(s))
while IsPlaying():
time.sleep(0.1)

View File

@@ -0,0 +1,175 @@
from ..voice import Voice
import time
def buildDriver(proxy):
'''
Builds a new instance of a driver and returns it for use by the driver
proxy.
@param proxy: Proxy creating the driver
@type proxy: L{driver.DriverProxy}
'''
return DummyDriver(proxy)
class DummyDriver(object):
'''
Dummy speech engine implementation. Documents the interface, notifications,
properties, and sequencing responsibilities of a driver implementation.
@ivar _proxy: Driver proxy that manages this instance
@type _proxy: L{driver.DriverProxy}
@ivar _config: Dummy configuration
@type _config: dict
@ivar _looping: True when in the dummy event loop, False when not
@ivar _looping: bool
'''
def __init__(self, proxy):
'''
Constructs the driver.
@param proxy: Proxy creating the driver
@type proxy: L{driver.DriverProxy}
'''
self._proxy = proxy
self._looping = False
# hold config values as if we had a real tts implementation that
# supported them
voices = [
Voice('dummy.voice1', 'John Doe', ['en-US', 'en-GB'], 'male', 'adult'),
Voice('dummy.voice2', 'Jane Doe', ['en-US', 'en-GB'], 'female', 'adult'),
Voice('dummy.voice3', 'Jimmy Doe', ['en-US', 'en-GB'], 'male', 10)
]
self._config = {
'rate' : 200,
'volume' : 1.0,
'voice' : voices[0],
'voices' : voices
}
def destroy(self):
'''
Optional method that will be called when the driver proxy is being
destroyed. Can cleanup any resources to make sure the engine terminates
properly.
'''
pass
def startLoop(self):
'''
Starts a blocking run loop in which driver callbacks are properly
invoked.
@precondition: There was no previous successful call to L{startLoop}
without an intervening call to L{stopLoop}.
'''
first = True
self._looping = True
while self._looping:
if first:
self._proxy.setBusy(False)
first = False
time.sleep(0.5)
def endLoop(self):
'''
Stops a previously started run loop.
@precondition: A previous call to L{startLoop} suceeded and there was
no intervening call to L{endLoop}.
'''
self._looping = False
def iterate(self):
'''
Iterates from within an external run loop.
'''
self._proxy.setBusy(False)
yield
def say(self, text):
'''
Speaks the given text. Generates the following notifications during
output:
started-utterance: When speech output has started
started-word: When a word is about to be spoken. Includes the character
"location" of the start of the word in the original utterance text
and the "length" of the word in characters.
finished-utterance: When speech output has finished. Includes a flag
indicating if the entire utterance was "completed" or not.
The proxy automatically adds any "name" associated with the utterance
to the notifications on behalf of the driver.
When starting to output an utterance, the driver must inform its proxy
that it is busy by invoking L{driver.DriverProxy.setBusy} with a flag
of True. When the utterance completes or is interrupted, the driver
inform the proxy that it is no longer busy by invoking
L{driver.DriverProxy.setBusy} with a flag of False.
@param text: Unicode text to speak
@type text: unicode
'''
self._proxy.setBusy(True)
self._proxy.notify('started-utterance')
i = 0
for word in text.split(' '):
self._proxy.notify('started-word', location=i, length=len(word))
try:
i = text.index(' ', i+1)+1
except Exception:
pass
self._proxy.notify('finished-utterance', completed=True)
self._proxy.setBusy(False)
def stop(self):
'''
Stops any current output. If an utterance was being spoken, the driver
is still responsible for sending the closing finished-utterance
notification documented above and resetting the busy state of the
proxy.
'''
pass
def getProperty(self, name):
'''
Gets a property value of the speech engine. The suppoted properties
and their values are:
voices: List of L{voice.Voice} objects supported by the driver
voice: String ID of the current voice
rate: Integer speech rate in words per minute
volume: Floating point volume of speech in the range [0.0, 1.0]
@param name: Property name
@type name: str
@raise KeyError: When the property name is unknown
'''
try:
return self._config[name]
except KeyError:
raise KeyError('unknown property %s' % name)
def setProperty(self, name, value):
'''
Sets one of the supported property values of the speech engine listed
above. If a value is invalid, attempts to clip it / coerce so it is
valid before giving up and firing an exception.
@param name: Property name
@type name: str
@param value: Property value
@type value: object
@raise KeyError: When the property name is unknown
@raise ValueError: When the value cannot be coerced to fit the property
'''
if name == 'voice':
v = filter(lambda v: v.id == value, self._config['voices'])
self._config['voice'] = v[0]
elif name == 'rate':
self._config['rate'] = value
elif name == 'volume':
self._config['volume'] = value
else:
raise KeyError('unknown property %s' % name)

View File

@@ -0,0 +1,233 @@
import os
import wave
import platform
import ctypes
import time
import subprocess
from tempfile import NamedTemporaryFile
if platform.system() == 'Windows':
import winsound
from ..voice import Voice
from . import _espeak, fromUtf8, toUtf8
# noinspection PyPep8Naming
def buildDriver(proxy):
return EspeakDriver(proxy)
# noinspection PyPep8Naming
class EspeakDriver(object):
_moduleInitialized = False
_defaultVoice = ''
def __init__(self, proxy):
if not EspeakDriver._moduleInitialized:
# espeak cannot initialize more than once per process and has
# issues when terminating from python (assert error on close)
# so just keep it alive and init once
rate = _espeak.Initialize(_espeak.AUDIO_OUTPUT_RETRIEVAL, 1000)
if rate == -1:
raise RuntimeError('could not initialize espeak')
EspeakDriver._defaultVoice = 'default'
EspeakDriver._moduleInitialized = True
self._proxy = proxy
self._looping = False
self._stopping = False
self._speaking = False
self._text_to_say = None
self._data_buffer = b''
self._numerise_buffer = []
_espeak.SetSynthCallback(self._onSynth)
self.setProperty('voice', EspeakDriver._defaultVoice)
self.setProperty('rate', 200)
self.setProperty('volume', 1.0)
def numerise(self, data):
self._numerise_buffer.append(data)
return ctypes.c_void_p(len(self._numerise_buffer))
def decode_numeric(self, data):
return self._numerise_buffer[int(data) - 1]
@staticmethod
def destroy():
_espeak.SetSynthCallback(None)
def stop(self):
if _espeak.IsPlaying():
self._stopping = True
_espeak.Cancel()
@staticmethod
def getProperty(name: str):
if name == 'voices':
voices = []
for v in _espeak.ListVoices(None):
kwargs = {'id': fromUtf8(v.name), 'name': fromUtf8(v.name)}
if v.languages:
try:
language_code_bytes = v.languages[1:]
language_code = language_code_bytes.decode('utf-8', errors='ignore')
kwargs['languages'] = [language_code]
except UnicodeDecodeError as e:
kwargs['languages'] = ["Unknown"]
genders = [None, 'male', 'female']
kwargs['gender'] = genders[v.gender]
kwargs['age'] = v.age or None
voices.append(Voice(**kwargs))
return voices
elif name == 'voice':
voice = _espeak.GetCurrentVoice()
return fromUtf8(voice.contents.name)
elif name == 'rate':
return _espeak.GetParameter(_espeak.RATE)
elif name == 'volume':
return _espeak.GetParameter(_espeak.VOLUME) / 100.0
elif name == 'pitch':
return _espeak.GetParameter(_espeak.PITCH)
else:
raise KeyError('unknown property %s' % name)
@staticmethod
def setProperty(name: str, value):
if name == 'voice':
if value is None:
return
try:
utf8Value = toUtf8(value)
_espeak.SetVoiceByName(utf8Value)
except ctypes.ArgumentError as e:
raise ValueError(str(e))
elif name == 'rate':
try:
_espeak.SetParameter(_espeak.RATE, value, 0)
except ctypes.ArgumentError as e:
raise ValueError(str(e))
elif name == 'volume':
try:
_espeak.SetParameter(
_espeak.VOLUME, int(round(value * 100, 2)), 0)
except TypeError as e:
raise ValueError(str(e))
elif name == 'pitch':
try:
_espeak.SetParameter(
_espeak.PITCH, int(value), 0
)
except TypeError as e:
raise ValueError(str(e))
else:
raise KeyError('unknown property %s' % name)
def save_to_file(self, text, filename):
code = self.numerise(filename)
_espeak.Synth(toUtf8(text), flags=_espeak.ENDPAUSE | _espeak.CHARS_UTF8, user_data=code)
def _start_synthesis(self, text):
self._proxy.setBusy(True)
self._proxy.notify('started-utterance')
self._speaking = True
self._data_buffer = b'' # Ensure buffer is cleared before starting
try:
_espeak.Synth(toUtf8(text), flags=_espeak.ENDPAUSE | _espeak.CHARS_UTF8)
except Exception as e:
self._proxy.setBusy(False)
self._proxy.notify('error', exception=e)
raise
def _onSynth(self, wav, numsamples, events):
i = 0
while True:
event = events[i]
if event.type == _espeak.EVENT_LIST_TERMINATED:
break
if event.type == _espeak.EVENT_WORD:
if self._text_to_say:
start_index = event.text_position-1
end_index = start_index + event.length
word = self._text_to_say[start_index:end_index]
else:
word = "Unknown"
self._proxy.notify('started-word', name=word, location=event.text_position, length=event.length)
elif event.type == _espeak.EVENT_END:
stream = NamedTemporaryFile(delete=False, suffix='.wav')
try:
with wave.open(stream, 'wb') as f:
f.setnchannels(1)
f.setsampwidth(2)
f.setframerate(22050.0)
f.writeframes(self._data_buffer)
self._data_buffer = b''
if event.user_data:
os.system(f'ffmpeg -y -i {stream.name} {self.decode_numeric(event.user_data)} -loglevel quiet')
else:
if platform.system() == 'Darwin': # macOS
try:
result = subprocess.run(['afplay', stream.name], check=True, capture_output=True, text=True)
except subprocess.CalledProcessError as e:
raise RuntimeError(f"[EspeakDriver._onSynth] Mac afplay failed with error: {e}")
elif platform.system() == 'Linux':
os.system(f'aplay {stream.name} -q')
elif platform.system() == 'Windows':
winsound.PlaySound(stream.name, winsound.SND_FILENAME) # Blocking playback
except Exception as e:
raise RuntimeError(f"Error during playback: {e}")
finally:
try:
stream.close() # Ensure the file is closed
os.remove(stream.name)
except Exception as e:
raise RuntimeError(f"Error deleting temporary WAV file: {e}")
self._proxy.notify('finished-utterance', completed=True)
self._proxy.setBusy(False)
self.endLoop() # End the loop here
break # Exit the loop after handling the termination event
i += 1
if numsamples > 0:
self._data_buffer += ctypes.string_at(wav, numsamples * ctypes.sizeof(ctypes.c_short))
return 0
def endLoop(self):
self._looping = False
def startLoop(self):
first = True
self._looping = True
while self._looping:
if not self._looping:
break
if first:
self._proxy.setBusy(False)
first = False
if self._text_to_say:
self._start_synthesis(self._text_to_say)
self.iterate()
time.sleep(0.01)
def iterate(self):
if not self._looping:
return
if self._stopping:
_espeak.Cancel()
self._stopping = False
self._proxy.notify('finished-utterance', completed=False)
self._proxy.setBusy(False)
self.endLoop()
def say(self, text):
self._text_to_say = text

View File

@@ -0,0 +1,165 @@
# noinspection PyUnresolvedReferences
import objc
from AppKit import NSSpeechSynthesizer
from Foundation import *
from PyObjCTools import AppHelper
# noinspection PyProtectedMember
from PyObjCTools.AppHelper import PyObjCAppHelperRunLoopStopper
from ..voice import Voice
# noinspection PyUnresolvedReferences
class RunLoopStopper(PyObjCAppHelperRunLoopStopper):
"""
Overrides PyObjCAppHelperRunLoopStopper to terminate after endLoop.
"""
def __init__(self):
self.shouldStop = False
def init(self):
return objc.super(RunLoopStopper, self).init()
def stop(self):
self.shouldStop = True
# noinspection PyPep8Naming
def buildDriver(proxy):
return NSSpeechDriver.alloc().initWithProxy(proxy)
# noinspection PyUnresolvedReferences,PyPep8Naming,PyUnusedLocal
class NSSpeechDriver(NSObject):
def __init__(self):
self._proxy = None
self._tts = None
self._completed = False
self._current_text = ''
@objc.python_method
def initWithProxy(self, proxy):
try:
proxy_attr = objc.super(NSSpeechDriver, self).init()
except AttributeError:
proxy_attr = self
if proxy_attr:
self._proxy = proxy
self._tts = NSSpeechSynthesizer.alloc().initWithVoice_(None)
self._tts.setDelegate_(self)
# default rate
self._tts.setRate_(200)
self._completed = True
return self
def destroy(self):
self._tts.setDelegate_(None)
del self._tts
def onPumpFirst_(self, timer):
self._proxy.setBusy(False)
def startLoop(self):
# https://github.com/ronaldoussoren/pyobjc/blob/mater/pyobjc-framework-Cocoa/Lib/PyObjCTools/AppHelper.py#L243C25-L243C25 # noqa
NSTimer.scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(
0.0, self, 'onPumpFirst:', None, False
)
runLoop = NSRunLoop.currentRunLoop()
stopper = RunLoopStopper.alloc().init()
PyObjCAppHelperRunLoopStopper.addRunLoopStopper_toRunLoop_(stopper, runLoop)
while stopper.shouldRun():
nextfire = runLoop.limitDateForMode_(NSDefaultRunLoopMode)
soon = NSDate.dateWithTimeIntervalSinceNow_(0) # maxTimeout in runConsoleEventLoop
if nextfire is not None:
nextfire = soon.earlierDate_(nextfire)
if not runLoop.runMode_beforeDate_(NSDefaultRunLoopMode, nextfire):
stopper.stop()
break
PyObjCAppHelperRunLoopStopper.removeRunLoopStopperFromRunLoop_(runLoop)
@staticmethod
def endLoop():
AppHelper.stopEventLoop()
def iterate(self):
self._proxy.setBusy(False)
yield
@objc.python_method
def say(self, text):
self._proxy.setBusy(True)
self._completed = True
self._proxy.notify('started-utterance')
self._current_text = text
self._tts.startSpeakingString_(text)
def stop(self):
if self._proxy.isBusy():
self._completed = False
self._tts.stopSpeaking()
@objc.python_method
def _toVoice(self, attr):
return Voice(attr.get('VoiceIdentifier'), attr.get('VoiceName'),
[attr.get('VoiceLocaleIdentifier', attr.get('VoiceLanguage'))], attr.get('VoiceGender'),
attr.get('VoiceAge'))
@objc.python_method
def getProperty(self, name):
if name == 'voices':
return [self._toVoice(NSSpeechSynthesizer.attributesForVoice_(v))
for v in list(NSSpeechSynthesizer.availableVoices())]
elif name == 'voice':
return self._tts.voice()
elif name == 'rate':
return self._tts.rate()
elif name == 'volume':
return self._tts.volume()
elif name == "pitch":
print("Pitch adjustment not supported when using NSSS")
else:
raise KeyError('unknown property %s' % name)
@objc.python_method
def setProperty(self, name, value):
if name == 'voice':
# vol/rate gets reset, so store and restore it
vol = self._tts.volume()
rate = self._tts.rate()
self._tts.setVoice_(value)
self._tts.setRate_(rate)
self._tts.setVolume_(vol)
elif name == 'rate':
self._tts.setRate_(value)
elif name == 'volume':
self._tts.setVolume_(value)
elif name == 'pitch':
print("Pitch adjustment not supported when using NSSS")
else:
raise KeyError('unknown property %s' % name)
@objc.python_method
def save_to_file(self, text, filename):
self._proxy.setBusy(True)
self._completed = True
url = Foundation.NSURL.fileURLWithPath_(filename)
self._tts.startSpeakingString_toURL_(text, url)
def speechSynthesizer_didFinishSpeaking_(self, tts, success):
if not self._completed:
success = False
else:
success = True
self._proxy.notify('finished-utterance', completed=success)
self._proxy.setBusy(False)
def speechSynthesizer_willSpeakWord_ofString_(self, tts, rng, text):
if self._current_text:
current_word = self._current_text[rng.location:rng.location + rng.length]
else:
current_word = "Unknown"
self._proxy.notify('started-word', name=current_word, location=rng.location,
length=rng.length)

View File

@@ -0,0 +1,187 @@
# noinspection PyUnresolvedReferences
import comtypes.client # Importing comtypes.client will make the gen subpackage
try:
from comtypes.gen import SpeechLib # comtypes
except ImportError:
# Generate the SpeechLib lib and any associated files
engine = comtypes.client.CreateObject("SAPI.SpVoice")
stream = comtypes.client.CreateObject("SAPI.SpFileStream")
# noinspection PyUnresolvedReferences
from comtypes.gen import SpeechLib
# noinspection PyUnresolvedReferences
import math
import os
import time
import weakref
import pythoncom
from ..voice import Voice
from . import fromUtf8, toUtf8
# common voices
MSSAM = 'HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Speech\\Voices\\Tokens\\MSSam'
MSMARY = 'HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Speech\\Voices\\Tokens\\MSMary'
MSMIKE = 'HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Speech\\Voices\\Tokens\\MSMike'
# coeffs for wpm conversion
E_REG = {MSSAM: (137.89, 1.11),
MSMARY: (156.63, 1.11),
MSMIKE: (154.37, 1.11)}
# noinspection PyPep8Naming
def buildDriver(proxy):
return SAPI5Driver(proxy)
# noinspection PyPep8Naming,PyShadowingNames
class SAPI5Driver(object):
def __init__(self, proxy):
self._tts = comtypes.client.CreateObject('SAPI.SPVoice')
# all events
self._tts.EventInterests = 33790
self._event_sink = SAPI5DriverEventSink()
self._event_sink.setDriver(weakref.proxy(self))
self._advise = comtypes.client.GetEvents(self._tts, self._event_sink)
self._proxy = proxy
self._looping = False
self._speaking = False
self._stopping = False
self._current_text = ''
# initial rate
self._rateWpm = 200
self.setProperty('voice', self.getProperty('voice'))
def destroy(self):
self._tts.EventInterests = 0
def say(self, text):
self._proxy.setBusy(True)
self._proxy.notify('started-utterance')
self._speaking = True
self._current_text = text
# call this async otherwise this blocks the callbacks
# see SpeechVoiceSpeakFlags: https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ms720892%28v%3dvs.85%29
# and Speak : https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ms723609(v=vs.85)
self._tts.Speak(fromUtf8(toUtf8(text)), 1) # -> stream_number as described in the remarks of the documentation
def stop(self):
if not self._speaking:
return
self._proxy.setBusy(True)
self._stopping = True
self._tts.Speak('', 3)
def save_to_file(self, text, filename):
cwd = os.getcwd()
stream = comtypes.client.CreateObject('SAPI.SPFileStream')
stream.Open(filename, SpeechLib.SSFMCreateForWrite)
temp_stream = self._tts.AudioOutputStream
self._tts.AudioOutputStream = stream
self._tts.Speak(fromUtf8(toUtf8(text)))
self._tts.AudioOutputStream = temp_stream
stream.close()
os.chdir(cwd)
@staticmethod
def _toVoice(attr):
return Voice(attr.Id, attr.GetDescription())
def _tokenFromId(self, id_):
tokens = self._tts.GetVoices()
for token in tokens:
if token.Id == id_:
return token
raise ValueError('unknown voice id %s', id_)
def getProperty(self, name):
if name == 'voices':
return [self._toVoice(attr) for attr in self._tts.GetVoices()]
elif name == 'voice':
return self._tts.Voice.Id
elif name == 'rate':
return self._rateWpm
elif name == 'volume':
return self._tts.Volume / 100.0
elif name == 'pitch':
print("Pitch adjustment not supported when using SAPI5")
else:
raise KeyError('unknown property %s' % name)
def setProperty(self, name, value):
if name == 'voice':
token = self._tokenFromId(value)
self._tts.Voice = token
a, b = E_REG.get(value, E_REG[MSMARY])
self._tts.Rate = int(math.log(self._rateWpm / a, b))
elif name == 'rate':
id_ = self._tts.Voice.Id
a, b = E_REG.get(id_, E_REG[MSMARY])
try:
self._tts.Rate = int(math.log(value / a, b))
except TypeError as e:
raise ValueError(str(e))
self._rateWpm = value
elif name == 'volume':
try:
self._tts.Volume = int(round(value * 100, 2))
except TypeError as e:
raise ValueError(str(e))
elif name == 'pitch':
print("Pitch adjustment not supported when using SAPI5")
else:
raise KeyError('unknown property %s' % name)
def startLoop(self):
first = True
self._looping = True
while self._looping:
if first:
self._proxy.setBusy(False)
first = False
pythoncom.PumpWaitingMessages()
time.sleep(0.05)
def endLoop(self):
self._looping = False
def iterate(self):
self._proxy.setBusy(False)
while 1:
pythoncom.PumpWaitingMessages()
yield
# noinspection PyPep8Naming,PyProtectedMember,PyUnusedLocal,PyShadowingNames
class SAPI5DriverEventSink(object):
def __init__(self):
self._driver = None
def setDriver(self, driver):
self._driver = driver
def _ISpeechVoiceEvents_StartStream(self, stream_number, stream_position):
self._driver._proxy.notify(
'started-word', location=stream_number, length=stream_position)
def _ISpeechVoiceEvents_EndStream(self, stream_number, stream_position):
d = self._driver
if d._speaking:
d._proxy.notify('finished-utterance', completed=not d._stopping)
d._speaking = False
d._stopping = False
d._proxy.setBusy(False)
d.endLoop() # hangs if you dont have this
def _ISpeechVoiceEvents_Word(self, stream_number, stream_position, char, length):
current_text = self._driver._current_text
if current_text:
current_word = current_text[char:char + length]
else:
current_word = "Unknown"
self._driver._proxy.notify(
'started-word', name=current_word, location=char, length=length)

View File

@@ -0,0 +1,220 @@
from . import driver
import traceback
import weakref
class Engine(object):
"""
@ivar proxy: Proxy to a driver implementation
@type proxy: L{DriverProxy}
@ivar _connects: Array of subscriptions
@type _connects: list
@ivar _inLoop: Running an event loop or not
@type _inLoop: bool
@ivar _driverLoop: Using a driver event loop or not
@type _driverLoop: bool
@ivar _debug: Print exceptions or not
@type _debug: bool
"""
def __init__(self, driverName=None, debug=False):
"""
Constructs a new TTS engine instance.
@param driverName: Name of the platform specific driver to use. If
None, selects the default driver for the operating system.
@type: str
@param debug: Debugging output enabled or not
@type debug: bool
"""
self.proxy = driver.DriverProxy(weakref.proxy(self), driverName, debug)
# initialize other vars
self._connects = {}
self._inLoop = False
self._driverLoop = True
self._debug = debug
def _notify(self, topic, **kwargs):
"""
Invokes callbacks for an event topic.
@param topic: String event name
@type topic: str
@param kwargs: Values associated with the event
@type kwargs: dict
"""
for cb in self._connects.get(topic, []):
try:
cb(**kwargs)
except Exception:
if self._debug:
traceback.print_exc()
def connect(self, topic, cb):
"""
Registers a callback for an event topic. Valid topics and their
associated values:
started-utterance: name=<str>
started-word: name=<str>, location=<int>, length=<int>
finished-utterance: name=<str>, completed=<bool>
error: name=<str>, exception=<exception>
@param topic: Event topic name
@type topic: str
@param cb: Callback function
@type cb: callable
@return: Token to use to unregister
@rtype: dict
"""
arr = self._connects.setdefault(topic, [])
arr.append(cb)
return {'topic': topic, 'cb': cb}
def disconnect(self, token):
"""
Unregisters a callback for an event topic.
@param token: Token of the callback to unregister
@type token: dict
"""
topic = token['topic']
try:
arr = self._connects[topic]
except KeyError:
return
arr.remove(token['cb'])
if len(arr) == 0:
del self._connects[topic]
def say(self, text, name=None):
"""
Adds an utterance to speak to the event queue.
@param text: Text to speak
@type text: unicode
@param name: Name to associate with this utterance. Included in
notifications about this utterance.
@type name: str
"""
if text == None:
return "Argument value can't be none or empty"
else:
self.proxy.say(text, name)
def stop(self):
"""
Stops the current utterance and clears the event queue.
"""
self.proxy.stop()
def save_to_file(self, text, filename, name=None):
'''
Adds an utterance to speak to the event queue.
@param text: Text to speak
@type text: unicode
@param filename: the name of file to save.
@param name: Name to associate with this utterance. Included in
notifications about this utterance.
@type name: str
'''
self.proxy.save_to_file(text, filename, name)
def isBusy(self):
"""
@return: True if an utterance is currently being spoken, false if not
@rtype: bool
"""
return self.proxy.isBusy()
def getProperty(self, name):
"""
Gets the current value of a property. Valid names and values include:
voices: List of L{voice.Voice} objects supported by the driver
voice: String ID of the current voice
rate: Integer speech rate in words per minute
volume: Floating point volume of speech in the range [0.0, 1.0]
Numeric values outside the valid range supported by the driver are
clipped.
@param name: Name of the property to fetch
@type name: str
@return: Value associated with the property
@rtype: object
@raise KeyError: When the property name is unknown
"""
return self.proxy.getProperty(name)
def setProperty(self, name, value):
"""
Adds a property value to set to the event queue. Valid names and values
include:
voice: String ID of the voice
rate: Integer speech rate in words per minute
volume: Floating point volume of speech in the range [0.0, 1.0]
Numeric values outside the valid range supported by the driver are
clipped.
@param name: Name of the property to fetch
@type name: str
@param: Value to set for the property
@rtype: object
@raise KeyError: When the property name is unknown
"""
self.proxy.setProperty(name, value)
def runAndWait(self):
"""
Runs an event loop until all commands queued up until this method call
complete. Blocks during the event loop and returns when the queue is
cleared.
@raise RuntimeError: When the loop is already running
"""
if self._inLoop:
raise RuntimeError('run loop already started')
self._inLoop = True
self._driverLoop = True
self.proxy.runAndWait()
def startLoop(self, useDriverLoop=True):
"""
Starts an event loop to process queued commands and callbacks.
@param useDriverLoop: If True, uses the run loop provided by the driver
(the default). If False, assumes the caller will enter its own
run loop which will pump any events for the TTS engine properly.
@type useDriverLoop: bool
@raise RuntimeError: When the loop is already running
"""
if self._inLoop:
raise RuntimeError('run loop already started')
self._inLoop = True
self._driverLoop = useDriverLoop
self.proxy.startLoop(self._driverLoop)
def endLoop(self):
"""
Stops a running event loop.
@raise RuntimeError: When the loop is not running
"""
if not self._inLoop:
raise RuntimeError('run loop not started')
self.proxy.endLoop(self._driverLoop)
self._inLoop = False
def iterate(self):
"""
Must be called regularly when using an external event loop.
"""
if not self._inLoop:
raise RuntimeError('run loop not started')
elif self._driverLoop:
raise RuntimeError('iterate not valid in driver run loop')
self.proxy.iterate()

View File

@@ -0,0 +1,836 @@
from __future__ import absolute_import
import functools
import itertools
import operator
import sys
import types
__author__ = "Benjamin Peterson <benjamin@python.org>"
__version__ = "1.9.0"
# Useful for very coarse version differentiation.
PY2 = sys.version_info[0] == 2
PY3 = sys.version_info[0] == 3
if PY3:
string_types = str,
integer_types = int,
class_types = type,
text_type = str
binary_type = bytes
MAXSIZE = sys.maxsize
else:
string_types = basestring,
integer_types = (int, long)
class_types = (type, types.ClassType)
text_type = unicode
binary_type = str
if sys.platform.startswith("java"):
# Jython always uses 32 bits.
MAXSIZE = int((1 << 31) - 1)
else:
# It's possible to have sizeof(long) != sizeof(Py_ssize_t).
class X(object):
def __len__(self):
return 1 << 31
try:
len(X())
except OverflowError:
# 32-bit
MAXSIZE = int((1 << 31) - 1)
else:
# 64-bit
MAXSIZE = int((1 << 63) - 1)
del X
def _add_doc(func, doc):
"""Add documentation to a function."""
func.__doc__ = doc
def _import_module(name):
"""Import module, returning the module after the last dot."""
__import__(name)
return sys.modules[name]
class _LazyDescr(object):
def __init__(self, name):
self.name = name
def __get__(self, obj, tp):
result = self._resolve()
setattr(obj, self.name, result) # Invokes __set__.
try:
# This is a bit ugly, but it avoids running this again by
# removing this descriptor.
delattr(obj.__class__, self.name)
except AttributeError:
pass
return result
class MovedModule(_LazyDescr):
def __init__(self, name, old, new=None):
super(MovedModule, self).__init__(name)
if PY3:
if new is None:
new = name
self.mod = new
else:
self.mod = old
def _resolve(self):
return _import_module(self.mod)
def __getattr__(self, attr):
_module = self._resolve()
value = getattr(_module, attr)
setattr(self, attr, value)
return value
class _LazyModule(types.ModuleType):
def __init__(self, name):
super(_LazyModule, self).__init__(name)
self.__doc__ = self.__class__.__doc__
def __dir__(self):
attrs = ["__doc__", "__name__"]
attrs += [attr.name for attr in self._moved_attributes]
return attrs
# Subclasses should override this
_moved_attributes = []
class MovedAttribute(_LazyDescr):
def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
super(MovedAttribute, self).__init__(name)
if PY3:
if new_mod is None:
new_mod = name
self.mod = new_mod
if new_attr is None:
if old_attr is None:
new_attr = name
else:
new_attr = old_attr
self.attr = new_attr
else:
self.mod = old_mod
if old_attr is None:
old_attr = name
self.attr = old_attr
def _resolve(self):
module = _import_module(self.mod)
return getattr(module, self.attr)
class _SixMetaPathImporter(object):
"""
A meta path importer to import six.moves and its submodules.
This class implements a PEP302 finder and loader. It should be compatible
with Python 2.5 and all existing versions of Python3
"""
def __init__(self, six_module_name):
self.name = six_module_name
self.known_modules = {}
def _add_module(self, mod, *fullnames):
for fullname in fullnames:
self.known_modules[self.name + "." + fullname] = mod
def _get_module(self, fullname):
return self.known_modules[self.name + "." + fullname]
def find_module(self, fullname, path=None):
if fullname in self.known_modules:
return self
return None
def __get_module(self, fullname):
try:
return self.known_modules[fullname]
except KeyError:
raise ImportError("This loader does not know module " + fullname)
def load_module(self, fullname):
try:
# in case of a reload
return sys.modules[fullname]
except KeyError:
pass
mod = self.__get_module(fullname)
if isinstance(mod, MovedModule):
mod = mod._resolve()
else:
mod.__loader__ = self
sys.modules[fullname] = mod
return mod
def is_package(self, fullname):
"""
Return true, if the named module is a package.
We need this method to get correct spec objects with
Python 3.4 (see PEP451)
"""
return hasattr(self.__get_module(fullname), "__path__")
def get_code(self, fullname):
"""Return None
Required, if is_package is implemented"""
self.__get_module(fullname) # eventually raises ImportError
return None
get_source = get_code # same as get_code
_importer = _SixMetaPathImporter(__name__)
class _MovedItems(_LazyModule):
"""Lazy loading of moved objects"""
__path__ = [] # mark as package
_moved_attributes = [
MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
MovedAttribute("filterfalse", "itertools", "itertools",
"ifilterfalse", "filterfalse"),
MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"),
MovedAttribute("intern", "__builtin__", "sys"),
MovedAttribute("map", "itertools", "builtins", "imap", "map"),
MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"),
MovedAttribute("reload_module", "__builtin__", "imp", "reload"),
MovedAttribute("reduce", "__builtin__", "functools"),
MovedAttribute("shlex_quote", "pipes", "shlex", "quote"),
MovedAttribute("StringIO", "StringIO", "io"),
MovedAttribute("UserDict", "UserDict", "collections"),
MovedAttribute("UserList", "UserList", "collections"),
MovedAttribute("UserString", "UserString", "collections"),
MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
MovedAttribute("zip_longest", "itertools", "itertools",
"izip_longest", "zip_longest"),
MovedModule("builtins", "__builtin__"),
MovedModule("configparser", "ConfigParser"),
MovedModule("copyreg", "copy_reg"),
MovedModule("dbm_gnu", "gdbm", "dbm.gnu"),
MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"),
MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
MovedModule("http_cookies", "Cookie", "http.cookies"),
MovedModule("html_entities", "htmlentitydefs", "html.entities"),
MovedModule("html_parser", "HTMLParser", "html.parser"),
MovedModule("http_client", "httplib", "http.client"),
MovedModule("email_mime_multipart", "email.MIMEMultipart",
"email.mime.multipart"),
MovedModule("email_mime_nonmultipart",
"email.MIMENonMultipart", "email.mime.nonmultipart"),
MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"),
MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
MovedModule("cPickle", "cPickle", "pickle"),
MovedModule("queue", "Queue"),
MovedModule("reprlib", "repr"),
MovedModule("socketserver", "SocketServer"),
MovedModule("_thread", "thread", "_thread"),
MovedModule("tkinter", "Tkinter"),
MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
MovedModule("tkinter_scrolledtext", "ScrolledText",
"tkinter.scrolledtext"),
MovedModule("tkinter_simpledialog", "SimpleDialog",
"tkinter.simpledialog"),
MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"),
MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
MovedModule("tkinter_colorchooser", "tkColorChooser",
"tkinter.colorchooser"),
MovedModule("tkinter_commondialog", "tkCommonDialog",
"tkinter.commondialog"),
MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
MovedModule("tkinter_font", "tkFont", "tkinter.font"),
MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
"tkinter.simpledialog"),
MovedModule("urllib_parse", __name__ +
".moves.urllib_parse", "urllib.parse"),
MovedModule("urllib_error", __name__ +
".moves.urllib_error", "urllib.error"),
MovedModule("urllib", __name__ + ".moves.urllib",
__name__ + ".moves.urllib"),
MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"),
MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"),
MovedModule("winreg", "_winreg"),
]
for attr in _moved_attributes:
setattr(_MovedItems, attr.name, attr)
if isinstance(attr, MovedModule):
_importer._add_module(attr, "moves." + attr.name)
del attr
_MovedItems._moved_attributes = _moved_attributes
moves = _MovedItems(__name__ + ".moves")
_importer._add_module(moves, "moves")
class Module_six_moves_urllib_parse(_LazyModule):
"""Lazy loading of moved objects in six.moves.urllib_parse"""
_urllib_parse_moved_attributes = [
MovedAttribute("ParseResult", "urlparse", "urllib.parse"),
MovedAttribute("SplitResult", "urlparse", "urllib.parse"),
MovedAttribute("parse_qs", "urlparse", "urllib.parse"),
MovedAttribute("parse_qsl", "urlparse", "urllib.parse"),
MovedAttribute("urldefrag", "urlparse", "urllib.parse"),
MovedAttribute("urljoin", "urlparse", "urllib.parse"),
MovedAttribute("urlparse", "urlparse", "urllib.parse"),
MovedAttribute("urlsplit", "urlparse", "urllib.parse"),
MovedAttribute("urlunparse", "urlparse", "urllib.parse"),
MovedAttribute("urlunsplit", "urlparse", "urllib.parse"),
MovedAttribute("quote", "urllib", "urllib.parse"),
MovedAttribute("quote_plus", "urllib", "urllib.parse"),
MovedAttribute("unquote", "urllib", "urllib.parse"),
MovedAttribute("unquote_plus", "urllib", "urllib.parse"),
MovedAttribute("urlencode", "urllib", "urllib.parse"),
MovedAttribute("splitquery", "urllib", "urllib.parse"),
MovedAttribute("splittag", "urllib", "urllib.parse"),
MovedAttribute("splituser", "urllib", "urllib.parse"),
MovedAttribute("uses_fragment", "urlparse", "urllib.parse"),
MovedAttribute("uses_netloc", "urlparse", "urllib.parse"),
MovedAttribute("uses_params", "urlparse", "urllib.parse"),
MovedAttribute("uses_query", "urlparse", "urllib.parse"),
MovedAttribute("uses_relative", "urlparse", "urllib.parse"),
]
for attr in _urllib_parse_moved_attributes:
setattr(Module_six_moves_urllib_parse, attr.name, attr)
del attr
Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes
_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"),
"moves.urllib_parse", "moves.urllib.parse")
class Module_six_moves_urllib_error(_LazyModule):
"""Lazy loading of moved objects in six.moves.urllib_error"""
_urllib_error_moved_attributes = [
MovedAttribute("URLError", "urllib2", "urllib.error"),
MovedAttribute("HTTPError", "urllib2", "urllib.error"),
MovedAttribute("ContentTooShortError", "urllib", "urllib.error"),
]
for attr in _urllib_error_moved_attributes:
setattr(Module_six_moves_urllib_error, attr.name, attr)
del attr
Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes
_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"),
"moves.urllib_error", "moves.urllib.error")
class Module_six_moves_urllib_request(_LazyModule):
"""Lazy loading of moved objects in six.moves.urllib_request"""
_urllib_request_moved_attributes = [
MovedAttribute("urlopen", "urllib2", "urllib.request"),
MovedAttribute("install_opener", "urllib2", "urllib.request"),
MovedAttribute("build_opener", "urllib2", "urllib.request"),
MovedAttribute("pathname2url", "urllib", "urllib.request"),
MovedAttribute("url2pathname", "urllib", "urllib.request"),
MovedAttribute("getproxies", "urllib", "urllib.request"),
MovedAttribute("Request", "urllib2", "urllib.request"),
MovedAttribute("OpenerDirector", "urllib2", "urllib.request"),
MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"),
MovedAttribute("ProxyHandler", "urllib2", "urllib.request"),
MovedAttribute("BaseHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"),
MovedAttribute("HTTPPasswordMgrWithDefaultRealm",
"urllib2", "urllib.request"),
MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"),
MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"),
MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"),
MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"),
MovedAttribute("FileHandler", "urllib2", "urllib.request"),
MovedAttribute("FTPHandler", "urllib2", "urllib.request"),
MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"),
MovedAttribute("UnknownHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"),
MovedAttribute("urlretrieve", "urllib", "urllib.request"),
MovedAttribute("urlcleanup", "urllib", "urllib.request"),
MovedAttribute("URLopener", "urllib", "urllib.request"),
MovedAttribute("FancyURLopener", "urllib", "urllib.request"),
MovedAttribute("proxy_bypass", "urllib", "urllib.request"),
]
for attr in _urllib_request_moved_attributes:
setattr(Module_six_moves_urllib_request, attr.name, attr)
del attr
Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes
_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"),
"moves.urllib_request", "moves.urllib.request")
class Module_six_moves_urllib_response(_LazyModule):
"""Lazy loading of moved objects in six.moves.urllib_response"""
_urllib_response_moved_attributes = [
MovedAttribute("addbase", "urllib", "urllib.response"),
MovedAttribute("addclosehook", "urllib", "urllib.response"),
MovedAttribute("addinfo", "urllib", "urllib.response"),
MovedAttribute("addinfourl", "urllib", "urllib.response"),
]
for attr in _urllib_response_moved_attributes:
setattr(Module_six_moves_urllib_response, attr.name, attr)
del attr
Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes
_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"),
"moves.urllib_response", "moves.urllib.response")
class Module_six_moves_urllib_robotparser(_LazyModule):
"""Lazy loading of moved objects in six.moves.urllib_robotparser"""
_urllib_robotparser_moved_attributes = [
MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"),
]
for attr in _urllib_robotparser_moved_attributes:
setattr(Module_six_moves_urllib_robotparser, attr.name, attr)
del attr
Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes
_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"),
"moves.urllib_robotparser", "moves.urllib.robotparser")
class Module_six_moves_urllib(types.ModuleType):
"""Create a six.moves.urllib namespace that resembles the Python 3 namespace"""
__path__ = [] # mark as package
parse = _importer._get_module("moves.urllib_parse")
error = _importer._get_module("moves.urllib_error")
request = _importer._get_module("moves.urllib_request")
response = _importer._get_module("moves.urllib_response")
robotparser = _importer._get_module("moves.urllib_robotparser")
def __dir__(self):
return ['parse', 'error', 'request', 'response', 'robotparser']
_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"),
"moves.urllib")
def add_move(move):
"""Add an item to six.moves."""
setattr(_MovedItems, move.name, move)
def remove_move(name):
"""Remove item from six.moves."""
try:
delattr(_MovedItems, name)
except AttributeError:
try:
del moves.__dict__[name]
except KeyError:
raise AttributeError("no such move, %r" % (name,))
if PY3:
_meth_func = "__func__"
_meth_self = "__self__"
_func_closure = "__closure__"
_func_code = "__code__"
_func_defaults = "__defaults__"
_func_globals = "__globals__"
else:
_meth_func = "im_func"
_meth_self = "im_self"
_func_closure = "func_closure"
_func_code = "func_code"
_func_defaults = "func_defaults"
_func_globals = "func_globals"
try:
advance_iterator = next
except NameError:
def advance_iterator(it):
return it.next()
next = advance_iterator
try:
callable = callable
except NameError:
def callable(obj):
return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
if PY3:
def get_unbound_function(unbound):
return unbound
create_bound_method = types.MethodType
Iterator = object
else:
def get_unbound_function(unbound):
return unbound.im_func
def create_bound_method(func, obj):
return types.MethodType(func, obj, obj.__class__)
class Iterator(object):
def next(self):
return type(self).__next__(self)
callable = callable
_add_doc(get_unbound_function,
"""Get the function out of a possibly unbound function""")
get_method_function = operator.attrgetter(_meth_func)
get_method_self = operator.attrgetter(_meth_self)
get_function_closure = operator.attrgetter(_func_closure)
get_function_code = operator.attrgetter(_func_code)
get_function_defaults = operator.attrgetter(_func_defaults)
get_function_globals = operator.attrgetter(_func_globals)
if PY3:
def iterkeys(d, **kw):
return iter(d.keys(**kw))
def itervalues(d, **kw):
return iter(d.values(**kw))
def iteritems(d, **kw):
return iter(d.items(**kw))
def iterlists(d, **kw):
return iter(d.lists(**kw))
viewkeys = operator.methodcaller("keys")
viewvalues = operator.methodcaller("values")
viewitems = operator.methodcaller("items")
else:
def iterkeys(d, **kw):
return iter(d.iterkeys(**kw))
def itervalues(d, **kw):
return iter(d.itervalues(**kw))
def iteritems(d, **kw):
return iter(d.iteritems(**kw))
def iterlists(d, **kw):
return iter(d.iterlists(**kw))
viewkeys = operator.methodcaller("viewkeys")
viewvalues = operator.methodcaller("viewvalues")
viewitems = operator.methodcaller("viewitems")
_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.")
_add_doc(itervalues, "Return an iterator over the values of a dictionary.")
_add_doc(iteritems,
"Return an iterator over the (key, value) pairs of a dictionary.")
_add_doc(iterlists,
"Return an iterator over the (key, [values]) pairs of a dictionary.")
if PY3:
def b(s):
return s.encode("latin-1")
def u(s):
return s
unichr = chr
if sys.version_info[1] <= 1:
def int2byte(i):
return bytes((i,))
else:
# This is about 2x faster than the implementation above on 3.2+
int2byte = operator.methodcaller("to_bytes", 1, "big")
byte2int = operator.itemgetter(0)
indexbytes = operator.getitem
iterbytes = iter
import io
StringIO = io.StringIO
BytesIO = io.BytesIO
_assertCountEqual = "assertCountEqual"
_assertRaisesRegex = "assertRaisesRegex"
_assertRegex = "assertRegex"
else:
def b(s):
return s
# Workaround for standalone backslash
def u(s):
return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape")
unichr = unichr
int2byte = chr
def byte2int(bs):
return ord(bs[0])
def indexbytes(buf, i):
return ord(buf[i])
iterbytes = functools.partial(itertools.imap, ord)
import StringIO
StringIO = BytesIO = StringIO.StringIO
_assertCountEqual = "assertItemsEqual"
_assertRaisesRegex = "assertRaisesRegexp"
_assertRegex = "assertRegexpMatches"
_add_doc(b, """Byte literal""")
_add_doc(u, """Text literal""")
def assertCountEqual(self, *args, **kwargs):
return getattr(self, _assertCountEqual)(*args, **kwargs)
def assertRaisesRegex(self, *args, **kwargs):
return getattr(self, _assertRaisesRegex)(*args, **kwargs)
def assertRegex(self, *args, **kwargs):
return getattr(self, _assertRegex)(*args, **kwargs)
if PY3:
exec_ = getattr(moves.builtins, "exec")
def reraise(tp, value, tb=None):
if value is None:
value = tp()
if value.__traceback__ is not tb:
raise value.with_traceback(tb)
raise value
else:
def exec_(_code_, _globs_=None, _locs_=None):
"""Execute code in a namespace."""
if _globs_ is None:
frame = sys._getframe(1)
_globs_ = frame.f_globals
if _locs_ is None:
_locs_ = frame.f_locals
del frame
elif _locs_ is None:
_locs_ = _globs_
exec("""exec _code_ in _globs_, _locs_""")
exec_("""def reraise(tp, value, tb=None):
raise tp, value, tb
""")
if sys.version_info[:2] == (3, 2):
exec_("""def raise_from(value, from_value):
if from_value is None:
raise value
raise value from from_value
""")
elif sys.version_info[:2] > (3, 2):
exec_("""def raise_from(value, from_value):
raise value from from_value
""")
else:
def raise_from(value, from_value):
raise value
print_ = getattr(moves.builtins, "print", None)
if print_ is None:
def print_(*args, **kwargs):
"""The new-style print function for Python 2.4 and 2.5."""
fp = kwargs.pop("file", sys.stdout)
if fp is None:
return
def write(data):
if not isinstance(data, basestring):
data = str(data)
# If the file has an encoding, encode unicode with it.
if (isinstance(fp, file) and
isinstance(data, unicode) and
fp.encoding is not None):
errors = getattr(fp, "errors", None)
if errors is None:
errors = "strict"
data = data.encode(fp.encoding, errors)
fp.write(data)
want_unicode = False
sep = kwargs.pop("sep", None)
if sep is not None:
if isinstance(sep, unicode):
want_unicode = True
elif not isinstance(sep, str):
raise TypeError("sep must be None or a string")
end = kwargs.pop("end", None)
if end is not None:
if isinstance(end, unicode):
want_unicode = True
elif not isinstance(end, str):
raise TypeError("end must be None or a string")
if kwargs:
raise TypeError("invalid keyword arguments to print()")
if not want_unicode:
for arg in args:
if isinstance(arg, unicode):
want_unicode = True
break
if want_unicode:
newline = unicode("\n")
space = unicode(" ")
else:
newline = "\n"
space = " "
if sep is None:
sep = space
if end is None:
end = newline
for i, arg in enumerate(args):
if i:
write(sep)
write(arg)
write(end)
if sys.version_info[:2] < (3, 3):
_print = print_
def print_(*args, **kwargs):
fp = kwargs.get("file", sys.stdout)
flush = kwargs.pop("flush", False)
_print(*args, **kwargs)
if flush and fp is not None:
fp.flush()
_add_doc(reraise, """Reraise an exception.""")
if sys.version_info[0:2] < (3, 4):
def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS,
updated=functools.WRAPPER_UPDATES):
def wrapper(f):
f = functools.wraps(wrapped, assigned, updated)(f)
f.__wrapped__ = wrapped
return f
return wrapper
else:
wraps = functools.wraps
def with_metaclass(meta, *bases):
"""Create a base class with a metaclass."""
# This requires a bit of explanation: the basic idea is to make a dummy
# metaclass for one level of class instantiation that replaces itself with
# the actual metaclass.
class metaclass(meta):
def __new__(cls, name, this_bases, d):
return meta(name, bases, d)
return type.__new__(metaclass, 'temporary_class', (), {})
def add_metaclass(metaclass):
"""Class decorator for creating a class with a metaclass."""
def wrapper(cls):
orig_vars = cls.__dict__.copy()
slots = orig_vars.get('__slots__')
if slots is not None:
if isinstance(slots, str):
slots = [slots]
for slots_var in slots:
orig_vars.pop(slots_var)
orig_vars.pop('__dict__', None)
orig_vars.pop('__weakref__', None)
return metaclass(cls.__name__, cls.__bases__, orig_vars)
return wrapper
def python_2_unicode_compatible(klass):
"""
A decorator that defines __unicode__ and __str__ methods under Python 2.
Under Python 3 it does nothing.
To support Python 2 and 3 with a single code base, define a __str__ method
returning text and apply this decorator to the class.
"""
if PY2:
if '__str__' not in klass.__dict__:
raise ValueError("@python_2_unicode_compatible cannot be applied "
"to %s because it doesn't define __str__()." %
klass.__name__)
klass.__unicode__ = klass.__str__
klass.__str__ = lambda self: self.__unicode__().encode('utf-8')
return klass
# Complete the moves implementation.
# This code is at the end of this module to speed up module loading.
# Turn this module into a package.
__path__ = [] # required for PEP 302 and PEP 451
__package__ = __name__ # see PEP 366 @ReservedAssignment
if globals().get("__spec__") is not None:
__spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable
# Remove other six meta path importers, since they cause problems. This can
# happen if six is removed from sys.modules and then reloaded. (Setuptools does
# this for some reason.)
if sys.meta_path:
for i, importer in enumerate(sys.meta_path):
# Here's some real nastiness: Another "instance" of the six module might
# be floating around. Therefore, we can't use isinstance() to check for
# the six meta path importer, since the other six instance will have
# inserted an importer with different class.
if (type(importer).__name__ == "_SixMetaPathImporter" and
importer.name == __name__):
del sys.meta_path[i]
break
del i, importer
# Finally, add the importer to the meta path import hook.
sys.meta_path.append(_importer)

View File

@@ -0,0 +1,14 @@
class Voice(object):
def __init__(self, id, name=None, languages=[], gender=None, age=None):
self.id = id
self.name = name
self.languages = languages
self.gender = gender
self.age = age
def __str__(self):
return """<Voice id=%(id)s
name=%(name)s
languages=%(languages)s
gender=%(gender)s
age=%(age)s>""" % self.__dict__