From 3028faf37250d151a7ab4639cb86d4f0ed4582ea Mon Sep 17 00:00:00 2001 From: Peter Parente Date: Thu, 30 Dec 2010 21:33:26 -0500 Subject: [PATCH] Move from bzr lp to github * fast-export / import failed me, so go back to lp for history * Wasn't much history anyway --- LICENSE | 13 + MANIFEST.in | 1 + README | 32 +++ docs/Makefile | 88 +++++++ docs/changelog.rst | 7 + docs/conf.py | 194 +++++++++++++++ docs/drivers.rst | 102 ++++++++ docs/engine.rst | 303 +++++++++++++++++++++++ docs/index.rst | 25 ++ docs/support.rst | 10 + ez_setup.py | 270 ++++++++++++++++++++ pyttsx/__init__.py | 41 ++++ pyttsx/driver.py | 224 +++++++++++++++++ pyttsx/drivers/__init__.py | 17 ++ pyttsx/drivers/_espeak.py | 460 +++++++++++++++++++++++++++++++++++ pyttsx/drivers/dummy.py | 192 +++++++++++++++ pyttsx/drivers/espeak.py | 148 +++++++++++ pyttsx/drivers/nsss.py | 111 +++++++++ pyttsx/drivers/sapi5.py | 152 ++++++++++++ pyttsx/engine.py | 218 +++++++++++++++++ pyttsx/voice.py | 31 +++ setup.py | 32 +++ tests/manual/run.py | 35 +++ tests/unit/test_all.py | 32 +++ tests/unit/test_lifecycle.py | 45 ++++ tests/unit/test_prop.py | 124 ++++++++++ tests/unit/test_say.py | 170 +++++++++++++ tests/unit/test_setup.py | 20 ++ 28 files changed, 3097 insertions(+) create mode 100644 LICENSE create mode 100644 MANIFEST.in create mode 100644 README create mode 100644 docs/Makefile create mode 100644 docs/changelog.rst create mode 100644 docs/conf.py create mode 100644 docs/drivers.rst create mode 100644 docs/engine.rst create mode 100644 docs/index.rst create mode 100644 docs/support.rst create mode 100644 ez_setup.py create mode 100644 pyttsx/__init__.py create mode 100644 pyttsx/driver.py create mode 100644 pyttsx/drivers/__init__.py create mode 100644 pyttsx/drivers/_espeak.py create mode 100644 pyttsx/drivers/dummy.py create mode 100644 pyttsx/drivers/espeak.py create mode 100644 pyttsx/drivers/nsss.py create mode 100644 pyttsx/drivers/sapi5.py create mode 100644 pyttsx/engine.py create mode 100644 pyttsx/voice.py create mode 100644 setup.py create mode 100644 tests/manual/run.py create mode 100644 tests/unit/test_all.py create mode 100644 tests/unit/test_lifecycle.py create mode 100644 tests/unit/test_prop.py create mode 100644 tests/unit/test_say.py create mode 100644 tests/unit/test_setup.py diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f661a34 --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +pyttsx Copyright (c) 2009 Peter Parente + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..bfe1181 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +recursive-include docs * \ No newline at end of file diff --git a/README b/README new file mode 100644 index 0000000..06a944a --- /dev/null +++ b/README @@ -0,0 +1,32 @@ +Quickstart +---------- + +import pyttsx +engine = pyttsx.init() +engine.say('Greetings!') +engine.say('How are you today?') +engine.runAndWait() + +See http://packages.python.org/pypi/pyttsx for documentation of the full API. + +Included drivers +---------------- + +* nsss - NSSpeechSynthesizer on Mac OS X 10.5 and higher +* sapi5 - SAPI5 on Windows XP, Windows Vista, and (untested) Windows 7 +* espeak - eSpeak on any distro / platform that can host the shared library + (e.g., Ubuntu / Fedora Linux) + +Contributing drivers +-------------------- + +Email the author if you have wrapped or are interested in wrapping another +text-to-speech engine for use with pyttsx. + +Project Links +------------- + +* Python Package Index for downloads (http://pypi.python.org/pyttsx) +* Launchpad site for source, bugs, and q&a (https://launchpad.net/pyttsx) +* Python Package Index for documentation (http://packages.python.org/pypi/pyttsx) + diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..aed2bea --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,88 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d _build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf _build/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) _build/html + @echo + @echo "Build finished. The HTML pages are in _build/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) _build/dirhtml + @echo + @echo "Build finished. The HTML pages are in _build/dirhtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) _build/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) _build/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) _build/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in _build/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) _build/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in _build/qthelp, like this:" + @echo "# qcollectiongenerator _build/qthelp/pyttsx.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile _build/qthelp/pyttsx.qhc" + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) _build/latex + @echo + @echo "Build finished; the LaTeX files are in _build/latex." + @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ + "run these through (pdf)latex." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) _build/changes + @echo + @echo "The overview file is in _build/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) _build/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in _build/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) _build/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in _build/doctest/output.txt." diff --git a/docs/changelog.rst b/docs/changelog.rst new file mode 100644 index 0000000..9e25069 --- /dev/null +++ b/docs/changelog.rst @@ -0,0 +1,7 @@ +Changelog +--------- + +Version 1.0 +~~~~~~~~~~~ + +First release. \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..e967410 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,194 @@ +# -*- coding: utf-8 -*- +# +# pyttsx documentation build configuration file, created by +# sphinx-quickstart on Sun Nov 1 09:40:19 2009. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.append(os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = [] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'pyttsx' +copyright = u'2009, Peter Parente' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '1.0' +# The full version, including alpha/beta/rc tags. +release = '1.0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of documents that shouldn't be included in the build. +#unused_docs = [] + +# List of directories, relative to source directory, that shouldn't be searched +# for source files. +exclude_trees = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. Major themes that come with +# Sphinx are currently 'default' and 'sphinxdoc'. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_use_modindex = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +html_show_sourcelink = False + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = '' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'pyttsxdoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'pyttsx.tex', u'pyttsx Documentation', + u'Peter Parente', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_use_modindex = True diff --git a/docs/drivers.rst b/docs/drivers.rst new file mode 100644 index 0000000..b2520ca --- /dev/null +++ b/docs/drivers.rst @@ -0,0 +1,102 @@ +Implementing drivers +-------------------- + +You can implement new drivers for the :mod:`pyttsx.Engine` by: + +#. Creating a Python module with the name of your new driver. +#. Implementing the required driver factory function and class in your module. +#. Using methods on a :class:`pyttsx.driver.DriverProxy` instance provided by the :class:`pyttsx.Engine` to control the event queue and notify applications about events. + +The Driver interface +~~~~~~~~~~~~~~~~~~~~ + +All drivers must implement the following factory function and driver interface. + +.. module:: pyttsx.drivers + :synopsis: The package containing the available driver implementations + +.. function:: buildDriver(proxy : pyttsx.driver.DriverProxy) -> pyttsx.drivers.DriverDelegate + + Instantiates delegate subclass declared in this module. + + :param proxy: Proxy instance provided by a :class:`pyttsx.Engine` instance. + +.. class:: DriverDelegate + + .. note:: The :class:`DriverDelegate` class is not actually declared in :mod:`pyttsx.drivers` and cannot server as a base class. It is only here for the purpose of documenting the interface all drivers must implement. + + .. method:: __init__(proxy : pyttsx.drivers.DriverProxy, *args, **kwargs) -> None + + Constructor. Must store the proxy reference. + + :param proxy: Proxy instance provided by the :func:`buildDriver` function. + + .. method:: destroy() -> + + Optional. Invoked by the :class:`pyttsx.driver.DriverProxy` when it is being destroyed so this delegate can clean up any synthesizer resources. If not implemented, the proxy proceeds safely. + + .. method:: endLoop() -> None + + Immediately ends a running driver event loop. + + .. method:: getProperty(name : string) -> object + + Immediately gets the named property value. At least those properties listed in the :meth:`pyttsx.Engine.getProperty` documentation must be supported. + + :param name: Name of the property to query. + :return: Value of the property at the time of this invocation. + + .. method:: say(text : unicode, name : string) -> None + + Immediately speaks an utterance. The speech must be output according to the current property values applied at the time of this invocation. Before this method returns, it must invoke :meth:`pyttsx.driver.DriverProxy.setBusy` with value :const:`True` to stall further processing of the command queue until the output completes or is interrupted. + + This method must trigger one and only one `started-utterance` notification when output begins, one `started-word` notification at the start of each word in the utterance, and a `finished-utterance` notification when output completes. + + :param text: Text to speak. + :param name: Name to associate with the utterance. Included in notifications about this utterance. + + .. method:: setProperty(name : string, value : object) -> None + + Immediately sets the named property value. At least those properties listed in the :meth:`pyttsx.Engine.setProperty` documentation must be supported. After setting the property, the driver must invoke :meth:`pyttsx.driver.DriverProxy.setBusy` with value :const:`False` to pump the command queue. + + :param name: Name of the property to change. + :param value: Value to set. + + .. method:: startLoop() + + Immediately starts an event loop. The loop is responsible for sending notifications about utterances and pumping the command queue by using methods on the :class:`pyttsx.driver.DriverProxy` object given to the factory function that created this object. + + .. method:: stop() + + Immediately stops the current utterance output. This method must trigger a `finished-utterance` notification if called during on-going output. It must trigger no notification if there is no ongoing output. + + After stopping the output and sending any required notification, the driver must invoke :meth:`pyttsx.driver.DriverProxy.setBusy` with value :const:`False` to pump the command queue. + +The DriverProxy interface +~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. module:: pyttsx.driver + :synopsis: The module containing the driver proxy implementation + +The :func:`pyttsx.drivers.buildDriver` factory receives an instance of a :class:`DriverProxy` class and provides it to the :class:`pyttsx.drivers.DriverDelegate` it constructs. The driver delegate can invoke the following public methods on the proxy instance. All other public methods found in the code are reserved for use by an :class:`pyttsx.Engine` instance. + +.. class:: DriverProxy + + .. method:: isBusy() -> bool + + Gets if the proxy is busy and cannot process the next command in the queue or not. + + :return: True means busy, False means idle. + + .. method:: notify(topic : string, **kwargs) -> None + + Fires a notification. + + :param topic: The name of the notification. + :kwargs: Name/value pairs associated with the topic. + + .. method:: setBusy(busy : bool) -> None + + Sets the proxy to busy so it cannot continue to pump the command queue or idle so it can process the next command. + + :param busy: True to set busy, false to set idle \ No newline at end of file diff --git a/docs/engine.rst b/docs/engine.rst new file mode 100644 index 0000000..46ad735 --- /dev/null +++ b/docs/engine.rst @@ -0,0 +1,303 @@ +.. module:: pyttsx + :synopsis: The root pyttsx package defining the engine factory function + +Using pyttsx +------------ + +An application invokes the :func:`pyttsx.init` factory function to get a reference to a :class:`pyttsx.Engine` instance. During construction, the engine initializes a :class:`pyttsx.driver.DriverProxy` object responsible for loading a speech engine driver implementation from the :mod:`pyttsx.drivers` module. After construction, an application uses the engine object to register and unregister event callbacks; produce and stop speech; get and set speech engine properties; and start and stop event loops. + +The Engine factory +~~~~~~~~~~~~~~~~~~ + +.. function:: init([driverName : string, debug : bool]) -> pyttsx.Engine + + Gets a reference to an engine instance that will use the given driver. If the requested driver is already in use by another engine instance, that engine is returned. Otherwise, a new engine is created. + + :param driverName: Name of the :mod:`pyttsx.drivers` module to load and use. Defaults to the best available driver for the platform, currently: + + * `sapi5` - SAPI5 on Windows + * `nsss` - NSSpeechSynthesizer on Mac OS X + * `espeak` - eSpeak on every other platform + + :param debug: Enable debug output or not. + :raises ImportError: When the requested driver is not found + :raises RuntimeError: When the driver fails to initialize + +The Engine interface +~~~~~~~~~~~~~~~~~~~~ + +.. module:: pyttsx.engine + :synopsis: The module containing the engine implementation + +.. class:: Engine + + Provides application access to text-to-speech synthesis. + + .. method:: connect(topic : string, cb : callable) -> dict + + Registers a callback for notifications on the given topic. + + :param topic: Name of the event to subscribe to. + :param cb: Function to invoke when the event fires. + :return: A token that the caller can use to unsubscribe the callback later. + + The following are the valid topics and their callback signatures. + + .. describe:: started-utterance + + Fired when the engine begins speaking an utterance. The associated callback must have the folowing signature. + + .. function:: onStartUtterance(name : string) -> None + + :param name: Name associated with the utterance. + + .. describe:: started-word + + Fired when the engine begins speaking a word. The associated callback must have the folowing signature. + + .. function:: onStartWord(name : string, location : integer, length : integer) + + :param name: Name associated with the utterance. + + .. describe:: finished-utterance + + Fired when the engine finishes speaking an utterance. The associated callback must have the folowing signature. + + .. function:: onFinishUtterance(name : string, completed : bool) -> None + + :param name: Name associated with the utterance. + :param completed: True if the utterance was output in its entirety or not. + + .. describe:: error + + Fired when the engine encounters an error. The associated callback must have the folowing signature. + + .. function:: onError(name : string, exception : Exception) -> None + + :param name: Name associated with the utterance that caused the error. + :param exception: Exception that was raised. + + .. method:: disconnect(token : dict) + + Unregisters a notification callback. + + :param token: Token returned by :meth:`connect` associated with the callback to be disconnected. + + .. method:: endLoop() -> None + + Ends a running event loop. If :meth:`startLoop` was called with `useDriverLoop` set to True, this method stops processing of engine commands and immediately exits the event loop. If it was called with False, this method stops processing of engine commands, but it is up to the caller to end the external event loop it started. + + :raises RuntimeError: When the loop is not running + + .. method:: getProperty(name : string) -> object + + Gets the current value of an engine property. + + :param name: Name of the property to query. + :return: Value of the property at the time of this invocation. + + The following property names are valid for all drivers. + + .. describe:: rate + + Integer speech rate in words per minute. Defaults to 200 word per minute. + + .. describe:: voice + + String identifier of the active voice. + + .. describe:: voices + + List of :class:`pyttsx.voice.Voice` descriptor objects. + + .. describe:: volume + + Floating point volume in the range of 0.0 to 1.0 inclusive. Defaults to 1.0. + + .. method:: isBusy() -> bool + + Gets if the engine is currently busy speaking an utterance or not. + + :return: True if speaking, false if not. + + .. method:: runAndWait() -> None + + Blocks while processing all currently queued commands. Invokes callbacks for engine notifications appropriately. Returns when all commands queued before this call are emptied from the queue. + + .. method:: say(text : unicode, name : string) -> None + + Queues a command to speak an utterance. The speech is output according to the properties set before this command in the queue. + + :param text: Text to speak. + :param name: Name to associate with the utterance. Included in notifications about this utterance. + + .. method:: setProperty(name, value) -> None + + Queues a command to set an engine property. The new property value affects all utterances queued after this command. + + :param name: Name of the property to change. + :param value: Value to set. + + The following property names are valid for all drivers. + + .. describe:: rate + + Integer speech rate in words per minute. + + .. describe:: voice + + String identifier of the active voice. + + .. describe:: volume + + Floating point volume in the range of 0.0 to 1.0 inclusive. + + .. method:: startLoop([useDriverLoop : bool]) -> None + + Starts running an event loop during which queued commands are processed and notifications are fired. + + :param useDriverLoop: True to use the loop provided by the selected driver. False to indicate the caller will enter its own loop after invoking this method. The caller's loop must pump events for the driver in use so that pyttsx notifications are delivered properly (e.g., SAPI5 requires a COM message pump). Defaults to True. + + .. method:: stop() -> None + + Stops the current utterance and clears the command queue. + +The Voice metadata +~~~~~~~~~~~~~~~~~~ + +.. module:: pyttsx.voice + :synopsis: The module containing the voice structure implementation + +.. class:: Voice + + Contains information about a speech synthesizer voice. + + .. attribute:: age + + Integer age of the voice in years. Defaults to :const:`None` if unknown. + + .. attribute:: gender + + String gender of the voice: `male`, `female`, or `neutral`. Defaults to :const:`None` if unknown. + + .. attribute:: id + + String identifier of the voice. Used to set the active voice via :meth:`pyttsx.engine.Engine.setPropertyValue`. This attribute is always defined. + + .. attribute:: languages + + List of string languages supported by this voice. Defaults to an empty list of unknown. + + .. attribute:: name + + Human readable name of the voice. Defaults to :const:`None` if unknown. + +Examples +~~~~~~~~ + +Speaking text +############# + +.. sourcecode:: python + + import pyttsx + engine = pyttsx.init() + engine.say('Sally sells seashells by the seashore.') + engine.say('The quick brown fox jumped over the lazy dog.') + engine.runAndWait() + +Listening for events +#################### + +.. sourcecode:: python + + import pyttsx + def onStart(name): + print 'starting', name + def onWord(name, location, length): + print 'word', name, location, length + def onEnd(name, completed): + print 'finishing', name, completed + engine = pyttsx.init() + engine.say('The quick brown fox jumped over the lazy dog.') + engine.runAndWait() + +Interrupting an utterance +######################### + +.. sourcecode:: python + + import pyttsx + def onWord(name, location, length): + print 'word', name, location, length + if location > 10: + engine.stop() + engine = pyttsx.init() + engine.say('The quick brown fox jumped over the lazy dog.') + engine.runAndWait() + +Changing voices +############### + +.. sourcecode:: python + + engine = pyttsx.init() + voices = engine.getProperty('voices') + for voice in voices: + engine.setProperty('voice', voice.id) + engine.say('The quick brown fox jumped over the lazy dog.') + engine.runAndWait() + +Changing speech rate +#################### + +.. sourcecode:: python + + engine = pyttsx.init() + rate = engine.getProperty('rate') + engine.setProperty('rate', rate+50) + engine.say('The quick brown fox jumped over the lazy dog.') + engine.runAndWait() + +Changing volume +############### + +.. sourcecode:: python + + engine = pyttsx.init() + volume = engine.getProperty('volume') + engine.setProperty('volume', volume-0.25) + engine.say('The quick brown fox jumped over the lazy dog.') + engine.runAndWait() + +Running a driver event loop +########################### + +.. sourcecode:: python + + engine = pyttsx.init() + def onStart(name): + print 'starting', name + def onWord(name, location, length): + print 'word', name, location, length + def onEnd(name, completed): + print 'finishing', name, completed + if name == 'fox': + engine.say('What a lazy dog!', 'dog') + elif name == 'dog': + engine.endLoop() + engine = pyttsx.init() + engine.say('The quick brown fox jumped over the lazy dog.', 'fox') + engine.startLoop() + +Using an external event loop +############################ + +.. sourcecode:: python + + engine = pyttsx.init() + engine.say('The quick brown fox jumped over the lazy dog.', 'fox') + engine.startLoop(False) + # engine.iterate() must be called inside externalLoop() + externalLoop() + engine.endLoop() \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..ad8989f --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,25 @@ +================================== +pyttsx - Text-to-speech x-platform +================================== + +This documentation describes the pyttsx Python package v |release| and was rendered on |today|. + +.. rubric:: Table of Contents + +.. toctree:: + :maxdepth: 2 + + support + engine + drivers + changelog + +.. rubric:: Project Links + +* `Project home page at Launchpad`__ +* `Package listing in PyPI`__ +* `Documentation in PyPI`__ + +__ https://launchpad.net/pyttsx +__ http://pypi.python.org/pypi/pyttsx +__ http://packages.python.org/pyttsx \ No newline at end of file diff --git a/docs/support.rst b/docs/support.rst new file mode 100644 index 0000000..a3032dc --- /dev/null +++ b/docs/support.rst @@ -0,0 +1,10 @@ +Supported synthesizers +---------------------- + +Version |version| of pyttsx includes drivers for the following text-to-speech synthesizers. Only operating systems on which a driver is tested and known to work are listed. The drivers may work on other systems. + +* SAPI5 on Windows XP and Windows Vista +* NSSpeechSynthesizer on Mac OS X 10.5 (Leopard) and 10.6 (Snow Leopard) +* espeak on Ubuntu Desktop Edition 8.10 (Intrepid), 9.04 (Jaunty), and 9.10 (Karmic) + +The :func:`pyttsx.init` documentation explains how to select a specific synthesizer by name as well as the default for each platform. \ No newline at end of file diff --git a/ez_setup.py b/ez_setup.py new file mode 100644 index 0000000..4848faf --- /dev/null +++ b/ez_setup.py @@ -0,0 +1,270 @@ +#!python +"""Bootstrap setuptools installation + +If you want to use setuptools in your package's setup.py, just include this +file in the same directory with it, and add this to the top of your setup.py:: + + from ez_setup import use_setuptools + use_setuptools() + +If you want to require a specific version of setuptools, set a download +mirror, or use an alternate download directory, you can do so by supplying +the appropriate options to ``use_setuptools()``. + +This file can also be run as a script to install or upgrade setuptools. +""" +import sys +DEFAULT_VERSION = "0.6c9" +DEFAULT_URL = "http://pypi.python.org/packages/%s/s/setuptools/" % sys.version[:3] + +md5_data = { + 'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca', + 'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb', + 'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b', + 'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a', + 'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618', + 'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac', + 'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5', + 'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4', + 'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c', + 'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b', + 'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27', + 'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277', + 'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa', + 'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e', + 'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e', + 'setuptools-0.6c4-py2.3.egg': 'b0b9131acab32022bfac7f44c5d7971f', + 'setuptools-0.6c4-py2.4.egg': '2a1f9656d4fbf3c97bf946c0a124e6e2', + 'setuptools-0.6c4-py2.5.egg': '8f5a052e32cdb9c72bcf4b5526f28afc', + 'setuptools-0.6c5-py2.3.egg': 'ee9fd80965da04f2f3e6b3576e9d8167', + 'setuptools-0.6c5-py2.4.egg': 'afe2adf1c01701ee841761f5bcd8aa64', + 'setuptools-0.6c5-py2.5.egg': 'a8d3f61494ccaa8714dfed37bccd3d5d', + 'setuptools-0.6c6-py2.3.egg': '35686b78116a668847237b69d549ec20', + 'setuptools-0.6c6-py2.4.egg': '3c56af57be3225019260a644430065ab', + 'setuptools-0.6c6-py2.5.egg': 'b2f8a7520709a5b34f80946de5f02f53', + 'setuptools-0.6c7-py2.3.egg': '209fdf9adc3a615e5115b725658e13e2', + 'setuptools-0.6c7-py2.4.egg': '5a8f954807d46a0fb67cf1f26c55a82e', + 'setuptools-0.6c7-py2.5.egg': '45d2ad28f9750e7434111fde831e8372', + 'setuptools-0.6c8-py2.3.egg': '50759d29b349db8cfd807ba8303f1902', + 'setuptools-0.6c8-py2.4.egg': 'cba38d74f7d483c06e9daa6070cce6de', + 'setuptools-0.6c8-py2.5.egg': '1721747ee329dc150590a58b3e1ac95b', + 'setuptools-0.6c9-py2.3.egg': 'a83c4020414807b496e4cfbe08507c03', + 'setuptools-0.6c9-py2.4.egg': '260a2be2e5388d66bdaee06abec6342a', + 'setuptools-0.6c9-py2.5.egg': 'fe67c3e5a17b12c0e7c541b7ea43a8e6', + 'setuptools-0.6c9-py2.6.egg': 'ca37b1ff16fa2ede6e19383e7b59245a', +} + +import sys, os +try: from hashlib import md5 +except ImportError: from md5 import md5 + +def _validate_md5(egg_name, data): + if egg_name in md5_data: + digest = md5(data).hexdigest() + if digest != md5_data[egg_name]: + print >>sys.stderr, ( + "md5 validation of %s failed! (Possible download problem?)" + % egg_name + ) + sys.exit(2) + return data + +def use_setuptools( + version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, + download_delay=15 +): + """Automatically find/download setuptools and make it available on sys.path + + `version` should be a valid setuptools version number that is available + as an egg for download under the `download_base` URL (which should end with + a '/'). `to_dir` is the directory where setuptools will be downloaded, if + it is not already available. If `download_delay` is specified, it should + be the number of seconds that will be paused before initiating a download, + should one be required. If an older version of setuptools is installed, + this routine will print a message to ``sys.stderr`` and raise SystemExit in + an attempt to abort the calling script. + """ + was_imported = 'pkg_resources' in sys.modules or 'setuptools' in sys.modules + def do_download(): + egg = download_setuptools(version, download_base, to_dir, download_delay) + sys.path.insert(0, egg) + import setuptools; setuptools.bootstrap_install_from = egg + try: + import pkg_resources + except ImportError: + return do_download() + try: + pkg_resources.require("setuptools>="+version); return + except pkg_resources.VersionConflict, e: + if was_imported: + print >>sys.stderr, ( + "The required version of setuptools (>=%s) is not available, and\n" + "can't be installed while this script is running. Please install\n" + " a more recent version first, using 'easy_install -U setuptools'." + "\n\n(Currently using %r)" + ) % (version, e.args[0]) + sys.exit(2) + else: + del pkg_resources, sys.modules['pkg_resources'] # reload ok + return do_download() + except pkg_resources.DistributionNotFound: + return do_download() + +def download_setuptools( + version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, + delay = 15 +): + """Download setuptools from a specified location and return its filename + + `version` should be a valid setuptools version number that is available + as an egg for download under the `download_base` URL (which should end + with a '/'). `to_dir` is the directory where the egg will be downloaded. + `delay` is the number of seconds to pause before an actual download attempt. + """ + import urllib2, shutil + egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3]) + url = download_base + egg_name + saveto = os.path.join(to_dir, egg_name) + src = dst = None + if not os.path.exists(saveto): # Avoid repeated downloads + try: + from distutils import log + if delay: + log.warn(""" +--------------------------------------------------------------------------- +This script requires setuptools version %s to run (even to display +help). I will attempt to download it for you (from +%s), but +you may need to enable firewall access for this script first. +I will start the download in %d seconds. + +(Note: if this machine does not have network access, please obtain the file + + %s + +and place it in this directory before rerunning this script.) +---------------------------------------------------------------------------""", + version, download_base, delay, url + ); from time import sleep; sleep(delay) + log.warn("Downloading %s", url) + src = urllib2.urlopen(url) + # Read/write all in one block, so we don't create a corrupt file + # if the download is interrupted. + data = _validate_md5(egg_name, src.read()) + dst = open(saveto,"wb"); dst.write(data) + finally: + if src: src.close() + if dst: dst.close() + return os.path.realpath(saveto) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +def main(argv, version=DEFAULT_VERSION): + """Install or upgrade setuptools and EasyInstall""" + try: + import setuptools + except ImportError: + egg = None + try: + egg = download_setuptools(version, delay=0) + sys.path.insert(0,egg) + from setuptools.command.easy_install import main + return main(list(argv)+[egg]) # we're done here + finally: + if egg and os.path.exists(egg): + os.unlink(egg) + else: + if setuptools.__version__ == '0.0.1': + print >>sys.stderr, ( + "You have an obsolete version of setuptools installed. Please\n" + "remove it from your system entirely before rerunning this script." + ) + sys.exit(2) + + req = "setuptools>="+version + import pkg_resources + try: + pkg_resources.require(req) + except pkg_resources.VersionConflict: + try: + from setuptools.command.easy_install import main + except ImportError: + from easy_install import main + main(list(argv)+[download_setuptools(delay=0)]) + sys.exit(0) # try to force an exit + else: + if argv: + from setuptools.command.easy_install import main + main(argv) + else: + print "Setuptools version",version,"or greater has been installed." + print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)' + +def update_md5(filenames): + """Update our built-in md5 registry""" + + import re + + for name in filenames: + base = os.path.basename(name) + f = open(name,'rb') + md5_data[base] = md5(f.read()).hexdigest() + f.close() + + data = [" %r: %r,\n" % it for it in md5_data.items()] + data.sort() + repl = "".join(data) + + import inspect + srcfile = inspect.getsourcefile(sys.modules[__name__]) + f = open(srcfile, 'rb'); src = f.read(); f.close() + + match = re.search("\nmd5_data = {\n([^}]+)}", src) + if not match: + print >>sys.stderr, "Internal error!" + sys.exit(2) + + src = src[:match.start(1)] + repl + src[match.end(1):] + f = open(srcfile,'w') + f.write(src) + f.close() + + +if __name__=='__main__': + if len(sys.argv)>2 and sys.argv[1]=='--md5update': + update_md5(sys.argv[2:]) + else: + main(sys.argv[1:]) \ No newline at end of file diff --git a/pyttsx/__init__.py b/pyttsx/__init__.py new file mode 100644 index 0000000..95c7875 --- /dev/null +++ b/pyttsx/__init__.py @@ -0,0 +1,41 @@ +''' +pyttsx package. + +Copyright (c) 2009 Peter Parente + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +''' +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 \ No newline at end of file diff --git a/pyttsx/driver.py b/pyttsx/driver.py new file mode 100644 index 0000000..33cb783 --- /dev/null +++ b/pyttsx/driver.py @@ -0,0 +1,224 @@ +''' +Proxy for drivers. + +Copyright (c) 2009 Peter Parente + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +''' +import sys +import traceback +import weakref + +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 = 'drivers.%s' % driverName + self._module = __import__(name, globals(), locals(), [driverName]) + # 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 + + 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, 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 + ''' + 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._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 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: + self._iterator.next() + except StopIteration: + pass \ No newline at end of file diff --git a/pyttsx/drivers/__init__.py b/pyttsx/drivers/__init__.py new file mode 100644 index 0000000..db332e4 --- /dev/null +++ b/pyttsx/drivers/__init__.py @@ -0,0 +1,17 @@ +''' +Speech driver implementations. + +Copyright (c) 2009 Peter Parente + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +''' \ No newline at end of file diff --git a/pyttsx/drivers/_espeak.py b/pyttsx/drivers/_espeak.py new file mode 100644 index 0000000..2e069ff --- /dev/null +++ b/pyttsx/drivers/_espeak.py @@ -0,0 +1,460 @@ +'''espeak.py a thin ctypes wrapper for the espeak dll + +Gary Bishop +July 2007 +Modified October 2007 for the version 2 interface to espeak and more pythonic interfaces + +Free for any use. +''' + +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 +import time + +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 = cdll.LoadLibrary('libespeak.so.1') + +# 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 and