Full Code of RapidWareTech/pyttsx for AI

master 1a84ee339719 cached
28 files
102.1 KB
25.3k tokens
140 symbols
1 requests
Download .txt
Repository: RapidWareTech/pyttsx
Branch: master
Commit: 1a84ee339719
Files: 28
Total size: 102.1 KB

Directory structure:
gitextract_nedunrbu/

├── .gitignore
├── LICENSE
├── MANIFEST.in
├── README.rst
├── docs/
│   ├── Makefile
│   ├── changelog.rst
│   ├── conf.py
│   ├── drivers.rst
│   ├── engine.rst
│   ├── index.rst
│   └── install.rst
├── pyttsx/
│   ├── __init__.py
│   ├── driver.py
│   ├── drivers/
│   │   ├── __init__.py
│   │   ├── _espeak.py
│   │   ├── dummy.py
│   │   ├── espeak.py
│   │   ├── nsss.py
│   │   └── sapi5.py
│   ├── engine.py
│   └── voice.py
├── setup.py
└── tests/
    ├── manual/
    │   └── run.py
    └── unit/
        ├── test_all.py
        ├── test_lifecycle.py
        ├── test_prop.py
        ├── test_say.py
        └── test_setup.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
*.pyc
build/
dist/
pyttsx.egg-info/
_build/


================================================
FILE: LICENSE
================================================
pyttsx Copyright (c) 2009, 2013 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.

================================================
FILE: MANIFEST.in
================================================
recursive-include docs *

================================================
FILE: README.rst
================================================
======
pyttsx
======

Cross-platform Python wrapper for text-to-speech synthesis

Help Wanted
===========

As you can probably tell, I have not had time or in some cases the resources (e.g., specific versions of OSes) to maintain pyttsx very well for some time now. If you are using pyttsx in your day to day work and would like to take over as maintainer of the project, please let me know.

Quickstart
==========

::

   import pyttsx
   engine = pyttsx.init()
   engine.say('Greetings!')
   engine.say('How are you today?')
   engine.runAndWait()

See http://pyttsx.readthedocs.org/ 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)
* GitHub site for source, bugs, and q&a (https://github.com/parente/pyttsx)
* ReadTheDocs for docs (http://pyttsx.readthedocs.org)

See Also
========

https://github.com/parente/espeakbox - espeak in a 16.5 MB Docker container with a simple REST API

License
=======

Copyright (c) 2009, 2013 Peter Parente
All rights reserved.

http://creativecommons.org/licenses/BSD/


================================================
FILE: docs/Makefile
================================================
# 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 <target>' where <target> 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."


================================================
FILE: docs/changelog.rst
================================================
Changelog
---------

Version 1.2
~~~~~~~~~~~

* Added pip install instructions to doc.
* Fixed voice selection to use VoiceLocaleIdentifier on OS X instead of deprecated VoiceLanguage

Version 1.1
~~~~~~~~~~~

* Fixed compatibility with pip
* Fixed espeak crash when running on Natty (https://github.com/parente/pyttsx/issues/3)

Version 1.0
~~~~~~~~~~~

First release

================================================
FILE: docs/conf.py
================================================
# -*- 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, 2013 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.2'
# The full version, including alpha/beta/rc tags.
release = '1.2'

# 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
# "<project> v<release> 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 <link> 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


================================================
FILE: docs/drivers.rst
================================================
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

================================================
FILE: docs/engine.rst
================================================
.. 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.connect('started-utterance', onStart)
   engine.connect('started-word', onWord)
   engine.connect('finished-utterance', onEnd)
   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.connect('started-word', onWord)
   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.connect('started-utterance', onStart)
   engine.connect('started-word', onWord)
   engine.connect('finished-utterance', onEnd)
   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()

================================================
FILE: docs/index.rst
================================================
==================================
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

   install
   engine
   drivers
   changelog

.. rubric:: Project Links

* `Project home page at GitHub`__
* `Package listing in PyPI`__
* `Documentation at ReadTheDocs`__

__ https://github.com/parente/pyttsx
__ http://pypi.python.org/pypi/pyttsx
__ https://pyttsx.readthedocs.org/

================================================
FILE: docs/install.rst
================================================
Installing pyttsx
-----------------

Tested versions
~~~~~~~~~~~~~~~

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, Windows Vista, and Windows 7
* NSSpeechSynthesizer on Mac OS X 10.5 (Leopard), 10.6 (Snow Leopard), 10.7 (Lion), and 10.8 (Mountain Lion).
* `espeak`_ on 32-bit Ubuntu Desktop Edition 8.10 (Intrepid), 9.04 (Jaunty), 9.10 (Karmic), and 12.04 (Precise).

The :func:`pyttsx.init` documentation explains how to select a specific synthesizer by name as well as the default for each platform.

Using pip to install system-wide
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

If you have pip installed, you can use it to install pyttsx in the system site-packages folder.

On Windows
##########

First install the `pywin32-extensions <http://sourceforge.net/projects/pywin32/files/pywin32/>`_ package using its Windows installer. Then use pip to install pyttsx.

.. code-block:: bash

   $ pip install pyttsx

On OSX or Linux
###############

.. code-block:: bash

   $ sudo pip install pyttsx

Using pip to install in a virtualenv
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

If you have virtualenv_ installed with pip_, you can use pip to install a copy of pyttsx in the virtual environment folder.

On Windows
##########

You'll need to install the `pywin32-extensions <http://sourceforge.net/projects/pywin32/files/pywin32/>`_ package system-wide using its Windows installer. Then you'll need to give your virtualenv access to the system site-packages in order to install pyttsx.

.. code-block:: bash

   $ virtualenv --system-site-packages myproj
   New python executable in myproj/bin/python
   Installing setuptools............done.
   Installing pip...............done.
   $ myproj\Scripts\activate
   (myproj)$ pip install pyttsx

On OSX
######

Unless you wish to compile your own version of pyobjc (a lengthy process), you will need to give your virtualenv access to the system site-packages folder.

.. code-block:: bash

   $ virtualenv --system-site-packages myproj
   New python executable in myproj/bin/python
   Installing setuptools............done.
   Installing pip...............done.
   $ . myproj/bin/activate
   (myproj)$ pip install pyttsx
   ...
   Successfully installed pyttsx
   Cleaning up...

On Linux
########

pyttsx requires no Python dependencies on Linux. You can cut-off the pyttsx virtualenv from the system site-packages.

code-block:: bash

   $ virtualenv --no-site-packages myproj
   New python executable in myproj/bin/python
   Installing setuptools............done.
   Installing pip...............done.
   $ . myproj/bin/activate
   (myproj)$ pip install pyttsx
   ...
   Successfully installed pyttsx
   Cleaning up...


.. _espeak: http://espeak.sourceforge.net/
.. _virtualenv: https://pypi.python.org/pypi/virtualenv/1.10.1
.. _pip: https://pypi.python.org/pypi/pip

================================================
FILE: pyttsx/__init__.py
================================================
'''
pyttsx package.

Copyright (c) 2009, 2013 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 __future__ import absolute_import
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


================================================
FILE: pyttsx/driver.py
================================================
'''
Proxy for drivers.

Copyright (c) 2009, 2013 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
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 = 'pyttsx.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

    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
        '''
        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:
            next(self._iterator)
        except StopIteration:
            pass


================================================
FILE: pyttsx/drivers/__init__.py
================================================
'''
Speech driver implementations.

Copyright (c) 2009, 2013 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.
'''

================================================
FILE: pyttsx/drivers/_espeak.py
================================================
'''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.
'''

from __future__ import print_function
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 <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):
    flags |= CHARS_WCHAR
    text = str(text)
    return cSynth(text, len(text)*10, position, position_type, end_position, flags, None, None)

cSynth = cfunc('espeak_Synth', dll, c_int,
              ('text', c_wchar_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 greatrer 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
        print()
        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)


================================================
FILE: pyttsx/drivers/dummy.py
================================================
'''
Dummy driver that produces no output but gives all expected callbacks. Useful
for testing and as a model for real drivers.

Copyright (c) 2009, 2013 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 ..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 = [v for v in self._config['voices'] if v.id == value]
            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)


================================================
FILE: pyttsx/drivers/espeak.py
================================================
'''
espeak driver.

Copyright (c) 2009, 2013 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 __future__ import absolute_import
import ctypes
from . import _espeak
from ..voice import Voice
import time

def buildDriver(proxy):
    return EspeakDriver(proxy)

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_PLAYBACK, 1000)
            if rate == -1:
                raise RuntimeError('could not initialize espeak')
            EspeakDriver._defaultVoice = self.getProperty('voice')
            EspeakDriver._moduleInitialized = True
        _espeak.SetSynthCallback(self._onSynth)
        # make sure all props reset
        self.setProperty('voice', EspeakDriver._defaultVoice)
        self.setProperty('rate', 200)
        self.setProperty('volume', 1.0)
        self._proxy = proxy
        self._looping = True
        self._stopping = False

    def destroy(self):
        _espeak.SetSynthCallback(None)

    def say(self, text):
        self._proxy.setBusy(True)
        self._proxy.notify('started-utterance')
        _espeak.Synth(text, flags=_espeak.ENDPAUSE)

    def stop(self):
        if _espeak.IsPlaying():
            self._stopping = True

    def getProperty(self, name):
        if name == 'voices':
            voices = []
            for v in _espeak.ListVoices(None):
                kwargs = {}
                kwargs['id'] = v.name
                kwargs['name'] = v.name
                if v.languages:
                    kwargs['languages'] = [v.languages]
                genders = [None, 'male', 'female']
                kwargs['gender'] = genders[v.gender]
                kwargs['age'] = v.age or None
                voices.append(Voice(**kwargs))
            return voices
        elif name == 'voice':
            return _espeak.GetCurrentVoice().contents.name
        elif name == 'rate':
            return _espeak.GetParameter(_espeak.RATE)
        elif name == 'volume':
            return _espeak.GetParameter(_espeak.VOLUME)/100.0
        else:
            raise KeyError('unknown property %s' % name)

    def setProperty(self, name, value):
        if name == 'voice':
            if value is None: return
            try:
                _espeak.SetVoiceByName(value)
            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))
        else:
            raise KeyError('unknown property %s' % name)

    def startLoop(self):
        first = True
        self._looping = True
        while self._looping:
            if first:
                # kick the queue
                self._proxy.setBusy(False)
                first = False
            if self._stopping:
                # have to do the cancel on the main thread, not inside the
                # callback else deadlock
                _espeak.Cancel()
                self._stopping = False
                self._proxy.notify('finished-utterance', completed=False)
                self._proxy.setBusy(False)
            time.sleep(0.01)

    def endLoop(self):
        self._looping = False

    def iterate(self):
        self._proxy.setBusy(False)
        while 1:
            if self._stopping:
                # have to do the cancel on the main thread, not inside the
                # callback else deadlock
                _espeak.Cancel()
                self._stopping = False
                self._proxy.notify('finished-utterance', completed=False)
                self._proxy.setBusy(False)
            yield

    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:
                self._proxy.notify('started-word',
                    location=event.text_position-1,
                    length=event.length)
            elif event.type == _espeak.EVENT_MSG_TERMINATED:
                self._proxy.notify('finished-utterance', completed=True)
                self._proxy.setBusy(False)
            i += 1
        return 0


================================================
FILE: pyttsx/drivers/nsss.py
================================================
'''
NSSpeechSynthesizer driver.

Copyright (c) 2009, 2013 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 Foundation import *
from AppKit import NSSpeechSynthesizer
from PyObjCTools import AppHelper
from ..voice import Voice

def buildDriver(proxy):
    return NSSpeechDriver.alloc().initWithProxy(proxy)

class NSSpeechDriver(NSObject):
    def initWithProxy(self, proxy):
        self = super(NSSpeechDriver, self).init()
        if self:
            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):
        NSTimer.scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(
            0.0, self, 'onPumpFirst:', None, False)
        AppHelper.runConsoleEventLoop()

    def endLoop(self):
        AppHelper.stopEventLoop()

    def iterate(self):
        self._proxy.setBusy(False)
        yield

    def say(self, text):
        self._proxy.setBusy(True)
        self._completed = True
        self._proxy.notify('started-utterance')
        self._tts.startSpeakingString_(str(text))

    def stop(self):
        if self._proxy.isBusy():
            self._completed = False
        self._tts.stopSpeaking()

    def _toVoice(self, attr):
        try:
            lang = attr['VoiceLocaleIdentifier']
        except KeyError:
            lang = attr['VoiceLanguage']
        return Voice(attr['VoiceIdentifier'], attr['VoiceName'],
                     [lang], attr['VoiceGender'],
                     attr['VoiceAge'])

    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()
        else:
            raise KeyError('unknown property %s' % name)

    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)
        else:
            raise KeyError('unknown property %s' % name)

    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):
        self._proxy.notify('started-word', location=rng.location,
            length=rng.length)


================================================
FILE: pyttsx/drivers/sapi5.py
================================================
'''
SAPI 5+ driver.

Copyright (c) 2009, 2013 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 comtypes.client
import win32com.client
import pythoncom
import time
import math
import weakref
from ..voice import Voice

# 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)}

def buildDriver(proxy):
    return SAPI5Driver(proxy)

class SAPI5Driver(object):
    def __init__(self, proxy):
        self._tts = win32com.client.Dispatch('SAPI.SPVoice')
        #self._tts = comtypes.client.CreateObject('SAPI.SPVoice')
        # all events
        self._tts.EventInterests = 33790
        self._advise = win32com.client.WithEvents(self._tts,
            SAPI5DriverEventSink)
        self._advise.setDriver(weakref.proxy(self))
        #self._debug = comtypes.client.ShowEvents(self._tts)
        #self._advise = comtypes.client.GetEvents(self._tts, self)
        self._proxy = proxy
        self._looping = False
        self._speaking = False
        self._stopping = False
        # 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._tts.Speak(str(text), 19)

    def stop(self):
        if not self._speaking:
            return
        self._proxy.setBusy(True)
        self._stopping = True
        self._tts.Speak('', 3)

    def _toVoice(self, 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
        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))
        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

class SAPI5DriverEventSink(object):
    def __init__(self):
        self._driver = None

    def setDriver(self, driver):
        self._driver = driver

    def OnWord(self, stream, pos, char, length):
        self._driver._proxy.notify('started-word', location=char, length=length)

    def OnEndStream(self, stream, pos):
        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)


================================================
FILE: pyttsx/engine.py
================================================
'''
Speech engine front-end.

Copyright (c) 2009, 2013 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 __future__ import absolute_import
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 as e:
                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 sepak
        @type text: unicode
        @param name: Name to associate with this utterance. Included in
            notifications about this utterance.
        @type name: str
        '''
        self.proxy.say(text, name)

    def stop(self):
        '''
        Stops the current utterance and clears the event queue.
        '''
        self.proxy.stop()

    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()


================================================
FILE: pyttsx/voice.py
================================================
'''
Voice metadata definition.

Copyright (c) 2009, 2013 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.
'''
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__

================================================
FILE: setup.py
================================================
'''
pyttsx setup script.

Copyright (c) 2009, 2013 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 platform
from setuptools import setup

install_requires = []
if platform.system() == 'Windows':
    install_requires = [
        'win32com'
    ]
elif platform.system() == 'Darwin':
    install_requires = [
        'pyobjc>=2.4'
    ]

setup(name='pyttsx',
      version='1.2',
      description='pyttsx - cross platform text-to-speech',
      long_description='pyttsx is a Python package supporting common text-to-speech engines on Mac OS X, Windows, and Linux.',
      author='Peter Parente',
      author_email='parente@cs.unc.edu',
      url='https://github.com/parente/pyttsx',
      download_url='http://pypi.python.org/pypi/pyttsx',
      license='BSD License',
      packages=['pyttsx', 'pyttsx.drivers'],
      install_requires=install_requires
)

================================================
FILE: tests/manual/run.py
================================================
import os
import sys
sys.path.insert(0, os.path.join('..', '..'))
import pyttsx
import time

count = 0

def started(name):
    print 'started', name

def word(location, length, name):
    print 'word', location, length, name

def finished(completed, name):
    print 'finished', completed, name
    engine.endLoop()

engine = pyttsx.Engine()
engine.connect('started-utterance', started)
engine.connect('finished-utterance', finished)
engine.connect('started-word', word)
print engine.getProperty('rate')
print engine.getProperty('volume')
print engine.getProperty('voice')
print [v.id for v in engine.getProperty('voices')]
#engine.stop()
engine.say('Hello out there. This is a test.', 'utter1')
engine.say('Hello out there again.', 'utter2')
#engine.setProperty('voice', vs[2].id)
#engine.say('This is another one.', 'utter2')
#engine.runAndWait()
engine.startLoop()
#engine.startLoop()
time.sleep(2)

================================================
FILE: tests/unit/test_all.py
================================================
'''
Runs all unit tests.

Copyright (c) 2009, 2013 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 unittest
import test_say
import test_prop
import test_lifecycle

def suite():
    suite = unittest.TestSuite([
        test_lifecycle.suite(),
        test_prop.suite(),
        test_say.suite()
    ])
    return suite

if __name__ == '__main__':
    unittest.TextTestRunner(verbosity=2).run(suite())

================================================
FILE: tests/unit/test_lifecycle.py
================================================
'''
Tests lifecycle.

Copyright (c) 2009, 2013 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 unittest
import test_setup
import pyttsx

class TestLifecycle(unittest.TestCase):
    def setUp(self):
        self.engine = pyttsx.init()

    def tearDown(self):
        del self.engine

    def testSeparateDrivers(self):
        self.engine2 = pyttsx.init('dummy')
        self.assert_(self.engine != self.engine2)
        del self.engine2

    def testReuseDriver(self):
        self.engine2 = pyttsx.init()
        self.assert_(self.engine == self.engine2)
        del self.engine2

def suite():
    suite = unittest.TestLoader().loadTestsFromTestCase(TestLifecycle)
    #suite = unittest.TestLoader().loadTestsFromName('testBadVoice', TestProperties)
    return suite

if __name__ == '__main__':
    unittest.TextTestRunner(verbosity=2).run(suite())

================================================
FILE: tests/unit/test_prop.py
================================================
'''
Tests properties.

Copyright (c) 2009, 2013 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 unittest
import test_setup
import pyttsx

class TestProperties(unittest.TestCase):
    def setUp(self):
        self.engine = pyttsx.init(debug=False)

    def tearDown(self):
        del self.engine

    def testDefaults(self):
        voices = self.engine.getProperty('voices')
        drate = 200
        dvolume = 1.0

        rate = self.engine.getProperty('rate')
        self.assert_(drate == rate)
        volume = self.engine.getProperty('volume')
        self.assert_(dvolume == volume)
        voice = self.engine.getProperty('voice')
        self.assert_(voice in [v.id for v in voices])

    def testSetRate(self):
        for rate in xrange(100, 400, 10):
            self.engine.setProperty('rate', rate)
            self.engine.runAndWait()
            grate = self.engine.getProperty('rate')
            self.assert_(rate == grate)

    def testSetVoice(self):
        voices = self.engine.getProperty('voices')
        for voice in voices:
            self.engine.setProperty('voice', voice.id)
            self.engine.runAndWait()
            gvoice = self.engine.getProperty('voice')
            self.assert_(voice.id == gvoice)

    def testSetVolume(self):
        for volume in xrange(0, 100, 1):
            volume /= 100.0
            self.engine.setProperty('volume', volume)
            self.engine.runAndWait()
            gvolume = self.engine.getProperty('volume')
            self.assertAlmostEqual(volume, gvolume, 4)

    def testSetMultiple(self):
        voices = self.engine.getProperty('voices')
        self.engine.setProperty('volume', 0.5)
        self.engine.setProperty('rate', 300)
        self.engine.setProperty('voice', voices[0].id)
        self.engine.runAndWait()
        gvoice = self.engine.getProperty('voice')
        self.assert_(gvoice == voices[0].id)
        gvolume = self.engine.getProperty('volume')
        self.assertAlmostEqual(gvolume, 0.5, 4)
        grate = self.engine.getProperty('rate')
        self.assert_(grate == 300)

    def testBadVolume(self):
        errors = []
        def errback(exception, name):
            errors.append(exception)
        tok = self.engine.connect('error', errback)
        self.engine.setProperty('volume', 'foobar')
        self.engine.setProperty('volume', None)
        self.engine.setProperty('volume', object())
        self.engine.runAndWait()
        self.engine.disconnect(tok)
        for error in errors:
            self.assert_(isinstance(error, ValueError))

    def testBadRate(self):
        errors = []
        def errback(exception, name):
            errors.append(exception)
        tok = self.engine.connect('error', errback)
        self.engine.setProperty('rate', 'foobar')
        self.engine.setProperty('rate', None)
        self.engine.setProperty('rate', object())
        self.engine.runAndWait()
        self.engine.disconnect(tok)
        for error in errors:
            self.assert_(isinstance(error, ValueError))

    def testBadVoice(self):
        errors = []
        def errback(exception, name):
            errors.append(exception)
        tok = self.engine.connect('error', errback)
        self.engine.setProperty('voice', 'foobar')
        self.engine.setProperty('voice', 100)
        self.engine.setProperty('voice', 1.0)
        self.engine.setProperty('voice', None)
        self.engine.setProperty('voice', object())
        self.engine.runAndWait()
        self.engine.disconnect(tok)
        for error in errors:
            self.assert_(isinstance(error, ValueError))

def suite():
    suite = unittest.TestLoader().loadTestsFromTestCase(TestProperties)
    #suite = unittest.TestLoader().loadTestsFromName('testBadVoice', TestProperties)
    return suite

if __name__ == '__main__':
    unittest.TextTestRunner(verbosity=2).run(suite())

================================================
FILE: tests/unit/test_say.py
================================================
'''
Tests say.

Copyright (c) 2009, 2013 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 unittest
import test_setup
import pyttsx
import itertools

class TestSay(unittest.TestCase):
    utters = ['This is the first utterance',
              'The second is an utterance as well']
    names = ['utter1', 'utter2']

    def setUp(self):
        self.correct = []
        for utter, name in zip(self.utters, self.names):
            events = [{'type' : 'started-utterance'}]
            last = 0
            for word in utter.split(' '):
                event = {'type' : 'started-word'}
                event['length'] = len(word)
                event['location'] = last
                events.append(event)
                last += len(word) + 1
            events.append({'type' : 'finished-utterance', 'completed' : True})
            for event in events:
                event['name'] = name
            self.correct.append(events)

        self.events = []
        self.engine = pyttsx.init(debug=False)
        self.engine.connect('started-utterance', self._onUtterStart)
        self.engine.connect('started-word', self._onUtterWord)
        self.engine.connect('finished-utterance', self._onUtterEnd)
        self.engine.connect('error', self._onUtterError)

    def tearDown(self):
        del self.engine

    def _onUtterStart(self, **kwargs):
        event = {'type' : 'started-utterance'}
        event.update(kwargs)
        self.events.append(event)

    def _onUtterWord(self, **kwargs):
        event = {'type' : 'started-word'}
        event.update(kwargs)
        self.events.append(event)

    def _onUtterEnd(self, **kwargs):
        event = {'type' : 'finished-utterance'}
        event.update(kwargs)
        self.events.append(event)

    def _onUtterError(self, **kwargs):
        event = {'type' : 'error'}
        event.update(kwargs)
        self.events.append(event)

    def testSay(self):
        self.engine.say(self.utters[0], self.names[0])
        self.engine.runAndWait()

        # number of events check
        self.assert_(len(self.events) == len(self.correct[0]))
        # event data check
        for cevent, tevent in zip(self.correct[0], self.events):
            self.assert_(cevent == tevent)

    def testMultipleSay(self):
        self.engine.say(self.utters[0], self.names[0])
        self.engine.say(self.utters[1], self.names[1])
        self.engine.runAndWait()
        # number of events check
        self.assert_(len(self.events) == len(self.correct[0]) + len(self.correct[1]))
        # event data check
        correct = itertools.chain(*self.correct)
        for cevent, tevent in zip(correct, self.events):
            self.assert_(cevent == tevent)

    def testSayTypes(self):
        self.engine.say(1.0)
        self.engine.say(None)
        self.engine.say(object())
        self.engine.runAndWait()
        # event data check
        errors = filter(lambda e: e['type'] == 'error', self.events)
        self.assert_(len(errors) == 0)

    def testStop(self):
        tok = None
        def _onWord(**kwargs):
            self.engine.stop()
            self.engine.disconnect(tok)
        tok = self.engine.connect('started-word', _onWord)
        self.engine.say(self.utters[0], self.names[0])
        self.engine.runAndWait()
        # make sure it stopped short
        self.assert_(len(self.events) < len(self.correct[0]))
        end = self.events[-1]
        self.assert_(not end['completed'])

    def testStopBeforeSay(self):
        self.engine.stop()
        self.testSay()

    def testMultipleStopBeforeSay(self):
        self.engine.stop()
        self.engine.stop()
        self.testSay()

    def testStartEndLoop(self):
        def _onEnd(**kwargs):
            self.engine.endLoop()
        self.engine.connect('finished-utterance', _onEnd)
        self.engine.say(self.utters[0], self.names[0])
        self.engine.startLoop()
        # number of events check
        self.assert_(len(self.events) == len(self.correct[0]))
        # event data check
        for cevent, tevent in zip(self.correct[0], self.events):
            self.assert_(cevent == tevent)

    def testExternalLoop(self):
        def _onEnd(**kwargs):
            self.engine.endLoop()

        # kill the engine built by setUp
        del self.engine
        self.engine = pyttsx.init('dummy')
        self.engine.connect('started-utterance', self._onUtterStart)
        self.engine.connect('started-word', self._onUtterWord)
        self.engine.connect('finished-utterance', self._onUtterEnd)
        self.engine.connect('error', self._onUtterError)
        self.engine.connect('finished-utterance', _onEnd)
        self.engine.say(self.utters[0], self.names[0])
        self.engine.startLoop(False)
        self.engine.iterate()
        # number of events check
        self.assert_(len(self.events) == len(self.correct[0]))
        # event data check
        for cevent, tevent in zip(self.correct[0], self.events):
            self.assert_(cevent == tevent)

    def testMultipleRuns(self):
        self.testSay()
        self.events = []
        self.testSay()

def suite():
    suite = unittest.TestLoader().loadTestsFromTestCase(TestSay)
    #suite = unittest.TestLoader().loadTestsFromName('testExternalLoop', TestSay)
    return suite

if __name__ == '__main__':
    unittest.TextTestRunner(verbosity=2).run(suite())

================================================
FILE: tests/unit/test_setup.py
================================================
'''
Configures the test fixture.

Copyright (c) 2009, 2013 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 os
sys.path.insert(0, os.path.join('..', '..'))
Download .txt
gitextract_nedunrbu/

├── .gitignore
├── LICENSE
├── MANIFEST.in
├── README.rst
├── docs/
│   ├── Makefile
│   ├── changelog.rst
│   ├── conf.py
│   ├── drivers.rst
│   ├── engine.rst
│   ├── index.rst
│   └── install.rst
├── pyttsx/
│   ├── __init__.py
│   ├── driver.py
│   ├── drivers/
│   │   ├── __init__.py
│   │   ├── _espeak.py
│   │   ├── dummy.py
│   │   ├── espeak.py
│   │   ├── nsss.py
│   │   └── sapi5.py
│   ├── engine.py
│   └── voice.py
├── setup.py
└── tests/
    ├── manual/
    │   └── run.py
    └── unit/
        ├── test_all.py
        ├── test_lifecycle.py
        ├── test_prop.py
        ├── test_say.py
        └── test_setup.py
Download .txt
SYMBOL INDEX (140 symbols across 14 files)

FILE: pyttsx/__init__.py
  function init (line 24) | def init(driverName=None, debug=False):

FILE: pyttsx/driver.py
  class DriverProxy (line 23) | class DriverProxy(object):
    method __init__ (line 43) | def __init__(self, engine, driverName, debug):
    method __del__ (line 76) | def __del__(self):
    method _push (line 82) | def _push(self, mtd, args, name=None):
    method _pump (line 96) | def _pump(self):
    method notify (line 110) | def notify(self, topic, **kwargs):
    method setBusy (line 122) | def setBusy(self, busy):
    method isBusy (line 133) | def isBusy(self):
    method say (line 140) | def say(self, text, name):
    method stop (line 151) | def stop(self):
    method getProperty (line 166) | def getProperty(self, name):
    method setProperty (line 177) | def setProperty(self, name, value):
    method runAndWait (line 188) | def runAndWait(self):
    method startLoop (line 196) | def startLoop(self, useDriverLoop):
    method endLoop (line 205) | def endLoop(self, useDriverLoop):
    method iterate (line 217) | def iterate(self):

FILE: pyttsx/drivers/_espeak.py
  function cfunc (line 16) | def cfunc(name, dll, result, *args):
  class numberORname (line 37) | class numberORname(Union):
  class EVENT (line 43) | class EVENT(Structure):
  function SetSynthCallback (line 83) | def SetSynthCallback(cb):
  function SetUriCallback (line 115) | def SetUriCallback(cb):
  function Synth (line 155) | def Synth(text, position=0, position_type=POS_CHARACTER, end_position=0,...
  function Synth_Mark (line 213) | def Synth_Mark(text, index_mark, end_position=0, flags=CHARS_AUTO):
  class VOICE (line 340) | class VOICE(Structure):
    method __repr__ (line 352) | def __repr__(self):
  function ListVoices (line 369) | def ListVoices(voice_spec=None):
  function synth_cb (line 442) | def synth_cb(wav, numsample, events):

FILE: pyttsx/drivers/dummy.py
  function buildDriver (line 22) | def buildDriver(proxy):
  class DummyDriver (line 32) | class DummyDriver(object):
    method __init__ (line 44) | def __init__(self, proxy):
    method destroy (line 67) | def destroy(self):
    method startLoop (line 75) | def startLoop(self):
    method endLoop (line 91) | def endLoop(self):
    method iterate (line 100) | def iterate(self):
    method say (line 107) | def say(self, text):
    method stop (line 143) | def stop(self):
    method getProperty (line 152) | def getProperty(self, name):
    method setProperty (line 171) | def setProperty(self, name, value):

FILE: pyttsx/drivers/espeak.py
  function buildDriver (line 24) | def buildDriver(proxy):
  class EspeakDriver (line 27) | class EspeakDriver(object):
    method __init__ (line 30) | def __init__(self, proxy):
    method destroy (line 49) | def destroy(self):
    method say (line 52) | def say(self, text):
    method stop (line 57) | def stop(self):
    method getProperty (line 61) | def getProperty(self, name):
    method setProperty (line 84) | def setProperty(self, name, value):
    method startLoop (line 104) | def startLoop(self):
    method endLoop (line 121) | def endLoop(self):
    method iterate (line 124) | def iterate(self):
    method _onSynth (line 136) | def _onSynth(self, wav, numsamples, events):

FILE: pyttsx/drivers/nsss.py
  function buildDriver (line 23) | def buildDriver(proxy):
  class NSSpeechDriver (line 26) | class NSSpeechDriver(NSObject):
    method initWithProxy (line 27) | def initWithProxy(self, proxy):
    method destroy (line 38) | def destroy(self):
    method onPumpFirst_ (line 42) | def onPumpFirst_(self, timer):
    method startLoop (line 45) | def startLoop(self):
    method endLoop (line 50) | def endLoop(self):
    method iterate (line 53) | def iterate(self):
    method say (line 57) | def say(self, text):
    method stop (line 63) | def stop(self):
    method _toVoice (line 68) | def _toVoice(self, attr):
    method getProperty (line 77) | def getProperty(self, name):
    method setProperty (line 90) | def setProperty(self, name, value):
    method speechSynthesizer_didFinishSpeaking_ (line 105) | def speechSynthesizer_didFinishSpeaking_(self, tts, success):
    method speechSynthesizer_willSpeakWord_ofString_ (line 113) | def speechSynthesizer_willSpeakWord_ofString_(self, tts, rng, text):

FILE: pyttsx/drivers/sapi5.py
  function buildDriver (line 36) | def buildDriver(proxy):
  class SAPI5Driver (line 39) | class SAPI5Driver(object):
    method __init__ (line 40) | def __init__(self, proxy):
    method destroy (line 58) | def destroy(self):
    method say (line 61) | def say(self, text):
    method stop (line 67) | def stop(self):
    method _toVoice (line 74) | def _toVoice(self, attr):
    method _tokenFromId (line 77) | def _tokenFromId(self, id):
    method getProperty (line 83) | def getProperty(self, name):
    method setProperty (line 95) | def setProperty(self, name, value):
    method startLoop (line 117) | def startLoop(self):
    method endLoop (line 127) | def endLoop(self):
    method iterate (line 130) | def iterate(self):
  class SAPI5DriverEventSink (line 136) | class SAPI5DriverEventSink(object):
    method __init__ (line 137) | def __init__(self):
    method setDriver (line 140) | def setDriver(self, driver):
    method OnWord (line 143) | def OnWord(self, stream, pos, char, length):
    method OnEndStream (line 146) | def OnEndStream(self, stream, pos):

FILE: pyttsx/engine.py
  class Engine (line 23) | class Engine(object):
    method __init__ (line 36) | def __init__(self, driverName=None, debug=False):
    method _notify (line 53) | def _notify(self, topic, **kwargs):
    method connect (line 68) | def connect(self, topic, cb):
    method disconnect (line 89) | def disconnect(self, token):
    method say (line 105) | def say(self, text, name=None):
    method stop (line 117) | def stop(self):
    method isBusy (line 123) | def isBusy(self):
    method getProperty (line 130) | def getProperty(self, name):
    method setProperty (line 150) | def setProperty(self, name, value):
    method runAndWait (line 170) | def runAndWait(self):
    method startLoop (line 184) | def startLoop(self, useDriverLoop=True):
    method endLoop (line 200) | def endLoop(self):
    method iterate (line 211) | def iterate(self):

FILE: pyttsx/voice.py
  class Voice (line 18) | class Voice(object):
    method __init__ (line 19) | def __init__(self, id, name=None, languages=[], gender=None, age=None):
    method __str__ (line 26) | def __str__(self):

FILE: tests/manual/run.py
  function started (line 9) | def started(name):
  function word (line 12) | def word(location, length, name):
  function finished (line 15) | def finished(completed, name):

FILE: tests/unit/test_all.py
  function suite (line 23) | def suite():

FILE: tests/unit/test_lifecycle.py
  class TestLifecycle (line 22) | class TestLifecycle(unittest.TestCase):
    method setUp (line 23) | def setUp(self):
    method tearDown (line 26) | def tearDown(self):
    method testSeparateDrivers (line 29) | def testSeparateDrivers(self):
    method testReuseDriver (line 34) | def testReuseDriver(self):
  function suite (line 39) | def suite():

FILE: tests/unit/test_prop.py
  class TestProperties (line 22) | class TestProperties(unittest.TestCase):
    method setUp (line 23) | def setUp(self):
    method tearDown (line 26) | def tearDown(self):
    method testDefaults (line 29) | def testDefaults(self):
    method testSetRate (line 41) | def testSetRate(self):
    method testSetVoice (line 48) | def testSetVoice(self):
    method testSetVolume (line 56) | def testSetVolume(self):
    method testSetMultiple (line 64) | def testSetMultiple(self):
    method testBadVolume (line 77) | def testBadVolume(self):
    method testBadRate (line 90) | def testBadRate(self):
    method testBadVoice (line 103) | def testBadVoice(self):
  function suite (line 118) | def suite():

FILE: tests/unit/test_say.py
  class TestSay (line 23) | class TestSay(unittest.TestCase):
    method setUp (line 28) | def setUp(self):
    method tearDown (line 51) | def tearDown(self):
    method _onUtterStart (line 54) | def _onUtterStart(self, **kwargs):
    method _onUtterWord (line 59) | def _onUtterWord(self, **kwargs):
    method _onUtterEnd (line 64) | def _onUtterEnd(self, **kwargs):
    method _onUtterError (line 69) | def _onUtterError(self, **kwargs):
    method testSay (line 74) | def testSay(self):
    method testMultipleSay (line 84) | def testMultipleSay(self):
    method testSayTypes (line 95) | def testSayTypes(self):
    method testStop (line 104) | def testStop(self):
    method testStopBeforeSay (line 117) | def testStopBeforeSay(self):
    method testMultipleStopBeforeSay (line 121) | def testMultipleStopBeforeSay(self):
    method testStartEndLoop (line 126) | def testStartEndLoop(self):
    method testExternalLoop (line 138) | def testExternalLoop(self):
    method testMultipleRuns (line 159) | def testMultipleRuns(self):
  function suite (line 164) | def suite():
Condensed preview — 28 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (109K chars).
[
  {
    "path": ".gitignore",
    "chars": 44,
    "preview": "*.pyc\nbuild/\ndist/\npyttsx.egg-info/\n_build/\n"
  },
  {
    "path": "LICENSE",
    "chars": 740,
    "preview": "pyttsx Copyright (c) 2009, 2013 Peter Parente\n\nPermission to use, copy, modify, and distribute this software for any\npur"
  },
  {
    "path": "MANIFEST.in",
    "chars": 24,
    "preview": "recursive-include docs *"
  },
  {
    "path": "README.rst",
    "chars": 1522,
    "preview": "======\npyttsx\n======\n\nCross-platform Python wrapper for text-to-speech synthesis\n\nHelp Wanted\n===========\n\nAs you can pr"
  },
  {
    "path": "docs/Makefile",
    "chars": 2993,
    "preview": "# Makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHINXBUILD "
  },
  {
    "path": "docs/changelog.rst",
    "chars": 386,
    "preview": "Changelog\r\n---------\r\n\r\nVersion 1.2\r\n~~~~~~~~~~~\r\n\r\n* Added pip install instructions to doc.\r\n* Fixed voice selection to"
  },
  {
    "path": "docs/conf.py",
    "chars": 6293,
    "preview": "# -*- coding: utf-8 -*-\n#\n# pyttsx documentation build configuration file, created by\n# sphinx-quickstart on Sun Nov  1 "
  },
  {
    "path": "docs/drivers.rst",
    "chars": 5191,
    "preview": "Implementing drivers\n--------------------\n\nYou can implement new drivers for the :mod:`pyttsx.Engine` by:\n\n#. Creating a"
  },
  {
    "path": "docs/engine.rst",
    "chars": 10349,
    "preview": ".. module:: pyttsx\n   :synopsis: The root pyttsx package defining the engine factory function\n\nUsing pyttsx\n------------"
  },
  {
    "path": "docs/index.rst",
    "chars": 570,
    "preview": "==================================\r\npyttsx - Text-to-speech x-platform\r\n==================================\r\n\r\nThis docum"
  },
  {
    "path": "docs/install.rst",
    "chars": 2994,
    "preview": "Installing pyttsx\n-----------------\n\nTested versions\n~~~~~~~~~~~~~~~\n\nVersion |version| of pyttsx includes drivers for t"
  },
  {
    "path": "pyttsx/__init__.py",
    "chars": 1494,
    "preview": "'''\npyttsx package.\n\nCopyright (c) 2009, 2013 Peter Parente\n\nPermission to use, copy, modify, and distribute this softwa"
  },
  {
    "path": "pyttsx/driver.py",
    "chars": 6986,
    "preview": "'''\nProxy for drivers.\n\nCopyright (c) 2009, 2013 Peter Parente\n\nPermission to use, copy, modify, and distribute this sof"
  },
  {
    "path": "pyttsx/drivers/__init__.py",
    "chars": 773,
    "preview": "'''\nSpeech driver implementations.\n\nCopyright (c) 2009, 2013 Peter Parente\n\nPermission to use, copy, modify, and distrib"
  },
  {
    "path": "pyttsx/drivers/_espeak.py",
    "chars": 17966,
    "preview": "'''espeak.py a thin ctypes wrapper for the espeak dll\n\nGary Bishop\nJuly 2007\nModified October 2007 for the version 2 int"
  },
  {
    "path": "pyttsx/drivers/dummy.py",
    "chars": 6871,
    "preview": "'''\nDummy driver that produces no output but gives all expected callbacks. Useful\nfor testing and as a model for real dr"
  },
  {
    "path": "pyttsx/drivers/espeak.py",
    "chars": 5493,
    "preview": "'''\nespeak driver.\n\nCopyright (c) 2009, 2013 Peter Parente\n\nPermission to use, copy, modify, and distribute this softwar"
  },
  {
    "path": "pyttsx/drivers/nsss.py",
    "chars": 3939,
    "preview": "'''\nNSSpeechSynthesizer driver.\n\nCopyright (c) 2009, 2013 Peter Parente\n\nPermission to use, copy, modify, and distribute"
  },
  {
    "path": "pyttsx/drivers/sapi5.py",
    "chars": 5011,
    "preview": "'''\nSAPI 5+ driver.\n\nCopyright (c) 2009, 2013 Peter Parente\n\nPermission to use, copy, modify, and distribute this softwa"
  },
  {
    "path": "pyttsx/engine.py",
    "chars": 7246,
    "preview": "'''\nSpeech engine front-end.\n\nCopyright (c) 2009, 2013 Peter Parente\n\nPermission to use, copy, modify, and distribute th"
  },
  {
    "path": "pyttsx/voice.py",
    "chars": 1186,
    "preview": "'''\nVoice metadata definition.\n\nCopyright (c) 2009, 2013 Peter Parente\n\nPermission to use, copy, modify, and distribute "
  },
  {
    "path": "setup.py",
    "chars": 1567,
    "preview": "'''\r\npyttsx setup script.\r\n\r\nCopyright (c) 2009, 2013 Peter Parente\r\n\r\nPermission to use, copy, modify, and distribute t"
  },
  {
    "path": "tests/manual/run.py",
    "chars": 901,
    "preview": "import os\nimport sys\nsys.path.insert(0, os.path.join('..', '..'))\nimport pyttsx\nimport time\n\ncount = 0\n\ndef started(name"
  },
  {
    "path": "tests/unit/test_all.py",
    "chars": 1071,
    "preview": "'''\nRuns all unit tests.\n\nCopyright (c) 2009, 2013 Peter Parente\n\nPermission to use, copy, modify, and distribute this s"
  },
  {
    "path": "tests/unit/test_lifecycle.py",
    "chars": 1522,
    "preview": "'''\nTests lifecycle.\n\nCopyright (c) 2009, 2013 Peter Parente\n\nPermission to use, copy, modify, and distribute this softw"
  },
  {
    "path": "tests/unit/test_prop.py",
    "chars": 4557,
    "preview": "'''\nTests properties.\n\nCopyright (c) 2009, 2013 Peter Parente\n\nPermission to use, copy, modify, and distribute this soft"
  },
  {
    "path": "tests/unit/test_say.py",
    "chars": 6047,
    "preview": "'''\nTests say.\n\nCopyright (c) 2009, 2013 Peter Parente\n\nPermission to use, copy, modify, and distribute this software fo"
  },
  {
    "path": "tests/unit/test_setup.py",
    "chars": 837,
    "preview": "'''\nConfigures the test fixture.\n\nCopyright (c) 2009, 2013 Peter Parente\n\nPermission to use, copy, modify, and distribut"
  }
]

About this extraction

This page contains the full source code of the RapidWareTech/pyttsx GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 28 files (102.1 KB), approximately 25.3k tokens, and a symbol index with 140 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!