[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[PATCH 5/6] move src/python to src/api/python
From: |
william hubbs |
Subject: |
[PATCH 5/6] move src/python to src/api/python |
Date: |
Wed, 15 Sep 2010 20:56:04 -0000 |
From: William Hubbs <address@hidden>
To: address@hidden
---
configure.ac | 6 +-
src/Makefile.am | 6 +-
src/api/Makefile.am | 4 +
src/api/python/ChangeLog | 474 +++++++++++
src/api/python/Makefile.am | 2 +
src/api/python/README | 10 +
src/api/python/speechd/Makefile.am | 21 +
src/api/python/speechd/__init__.py | 18 +
src/api/python/speechd/_test.py | 136 +++
src/api/python/speechd/client.py | 1125 +++++++++++++++++++++++++
src/api/python/speechd/paths.py.in | 1 +
src/api/python/speechd_config/Makefile.am | 27 +
src/api/python/speechd_config/__init__.py | 18 +
src/api/python/speechd_config/config.py | 924 ++++++++++++++++++++
src/api/python/speechd_config/paths.py.in | 4 +
src/api/python/speechd_config/spd-conf | 10 +
src/api/python/speechd_config/speechd.desktop | 11 +
src/api/python/speechd_config/test.wav | Bin 0 -> 17410 bytes
src/python/ChangeLog | 474 -----------
src/python/Makefile.am | 2 -
src/python/README | 10 -
src/python/speechd/Makefile.am | 21 -
src/python/speechd/__init__.py | 18 -
src/python/speechd/_test.py | 136 ---
src/python/speechd/client.py | 1125 -------------------------
src/python/speechd/paths.py.in | 1 -
src/python/speechd_config/Makefile.am | 27 -
src/python/speechd_config/__init__.py | 18 -
src/python/speechd_config/config.py | 924 --------------------
src/python/speechd_config/paths.py.in | 4 -
src/python/speechd_config/spd-conf | 10 -
src/python/speechd_config/speechd.desktop | 11 -
src/python/speechd_config/test.wav | Bin 17410 -> 0 bytes
33 files changed, 2789 insertions(+), 2789 deletions(-)
create mode 100644 src/api/python/ChangeLog
create mode 100644 src/api/python/Makefile.am
create mode 100644 src/api/python/README
create mode 100644 src/api/python/speechd/Makefile.am
create mode 100644 src/api/python/speechd/__init__.py
create mode 100644 src/api/python/speechd/_test.py
create mode 100644 src/api/python/speechd/client.py
create mode 100644 src/api/python/speechd/paths.py.in
create mode 100644 src/api/python/speechd_config/Makefile.am
create mode 100644 src/api/python/speechd_config/__init__.py
create mode 100644 src/api/python/speechd_config/config.py
create mode 100644 src/api/python/speechd_config/paths.py.in
create mode 100644 src/api/python/speechd_config/spd-conf
create mode 100644 src/api/python/speechd_config/speechd.desktop
create mode 100644 src/api/python/speechd_config/test.wav
delete mode 100644 src/python/ChangeLog
delete mode 100644 src/python/Makefile.am
delete mode 100644 src/python/README
delete mode 100644 src/python/speechd/Makefile.am
delete mode 100644 src/python/speechd/__init__.py
delete mode 100644 src/python/speechd/_test.py
delete mode 100644 src/python/speechd/client.py
delete mode 100644 src/python/speechd/paths.py.in
delete mode 100644 src/python/speechd_config/Makefile.am
delete mode 100644 src/python/speechd_config/__init__.py
delete mode 100644 src/python/speechd_config/config.py
delete mode 100644 src/python/speechd_config/paths.py.in
delete mode 100644 src/python/speechd_config/spd-conf
delete mode 100644 src/python/speechd_config/speechd.desktop
delete mode 100644 src/python/speechd_config/test.wav
diff --git a/configure.ac b/configure.ac
index 7902cf6..89448f3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -424,6 +424,9 @@ AC_CONFIG_FILES([Makefile
src/Makefile
src/api/Makefile
src/api/c/Makefile
+ src/api/python/Makefile
+ src/api/python/speechd/Makefile
+ src/api/python/speechd_config/Makefile
src/audio/Makefile
src/audio/static_plugins.c
src/clients/Makefile
@@ -431,9 +434,6 @@ AC_CONFIG_FILES([Makefile
src/clients/spdsend/Makefile
src/common/Makefile
src/modules/Makefile
- src/python/Makefile
- src/python/speechd/Makefile
- src/python/speechd_config/Makefile
src/server/Makefile
src/tests/Makefile])
AC_OUTPUT
diff --git a/src/Makefile.am b/src/Makefile.am
index edefd62..47d134e 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -2,9 +2,5 @@
SUBDIRS=common server audio modules api clients tests
-if HAVE_PYTHON
-SUBDIRS += python
-endif
-
-DIST_SUBDIRS=common server audio modules api clients tests python
+DIST_SUBDIRS=common server audio modules api clients tests
diff --git a/src/api/Makefile.am b/src/api/Makefile.am
index 2a90e9e..9f3ddfe 100644
--- a/src/api/Makefile.am
+++ b/src/api/Makefile.am
@@ -2,5 +2,9 @@
SUBDIRS= c
+if HAVE_PYTHON
+SUBDIRS += python
+endif
+
EXTRA_DIST = cl guile
diff --git a/src/api/python/ChangeLog b/src/api/python/ChangeLog
new file mode 100644
index 0000000..419f6b2
--- /dev/null
+++ b/src/api/python/ChangeLog
@@ -0,0 +1,474 @@
+2008-12-17 Hynek Hanke <hanke at mach>
+
+ * Makefile.in: Create conf/desktop/ directory for
+ installation.
+
+2008-12-10 Hynek Hanke <hanke at mach>
+
+ * speechd_config/config.py (Tests.diagnostics.decide_to_test): Set port
in
+ ~/.profile and place speechd.desktop in ~/.config/autostart
+
+ * Makefile.in (all): Handle speechd.desktop.
+
+2008-11-26 Hynek Hanke <hanke at mach>
+
+ * Makefile.in (maintainer-clean): Maintainer-clean does clean.
+
+2008-10-15 Hynek Hanke <hanke at mach>
+
+ * Makefile.in: Respect ${DESTDIR}.
+
+2008-08-11 Hynek Hanke <hanke at brailcom.org>
+
+ * speechd_config/config.py (Tests.audio_try_play): Use proper
os.path.join
+ for constructing path to the testing wav file.
+
+2008-07-31 Hynek Hanke <hanke at brailcom.org>
+
+ * speechd_config/config.py (Tests.diagnostics): Only try hearing
+ test if Speech Dispatcher was started successfuly.
+ (Tests.diagnostics.decide_to_test): Do not ask whether to
+ start/restart but first try to stop and then subsequently
+ start via /etc/init.d/ (this covers both cases).
+
+2008-07-14 Hynek Hanke <hanke at brailcom.org>
+
+ * Makefile.in (clean): Delete speechd_config/paths.py
+ (install): Include prefix correctly.
+ (sysconfdir): Define sysconfdir.
+
+2008-07-12 Hynek Hanke <hanke at brailcom.org>
+
+ * speechd_config/config.py (Tests.diagnostics.decide_to_test):
+ AudioOutput method is the proper parameter name, not
+ DefaultAudioOutput.
+
+2008-07-11 Hynek Hanke <hanke at brailcom.org>
+
+ * speechd_config/config.py (Options.__init__): Added basic
+ description into help message.
+
+ * Makefile.in (all): Typo in directory paths.
+
+ * speechd_config/config.py (Options.__init__): Include summary
+ message into help.
+
+2008-07-10 Hynek Hanke <hanke at brailcom.org>
+
+ * speechd_config/config.py (Options): Restructured.
+ test_spd_say: New test.
+ Handle SPEECHD_PORT correctly.
+ (Tests.write_diagnostics_results): New method.
+ Use TMPDIR correctly.
+ (Configuration.
+ (Configure.speechd_start_system): Allow also restart.
+ Bugfixes.
+
+2008-07-09 Hynek Hanke <hanke at brailcom.org>
+
+ * speechd_config/config.py (Tests.diagnostics.decide_to_test):
+ Also include configuration into debugging archive.
+
+ * speechd_config/spd-conf: Do not call main() since it will
+ be called automatically on import.
+
+ * speechd_config/config.py: Typo.
+
+ * Makefile.in (all): speechd_config/paths.py generation moved
+ from install.
+
+ * setup.py (speechd_config) New module.
+
+ * Makefile.in (speechd_config): New module.
+ Write necessary paths into speech_config/conf_path.py
+
+2008-06-27 Hynek Hanke <hanke at brailcom.org>
+
+ * speechd/client.py (SSIPClient.set_debug): New method.
+ (SSIPClient.set_debug_destination): New method.
+ (SSIPClient.set_debug): New method.
+ (SSIPClient.set_debug_destination): New method.
+
+2008-02-19 Tomas Cerha <cerha at brailcom.org>
+
+ * README: New file.
+
+ * speechd/client.py (SSIPClient.__init__): Docstring improved.
+
+2008-02-04 Hynek Hanke <hanke at mach>
+
+ * Makefile.in: New file. Propagate $prefix correctly.
+
+2008-01-29 Tomas Cerha <cerha at brailcom.org>
+
+ * speechd/client.py (SSIPClient.__init__): Use the environment
+ variable `SPEECHD_HOST' if defined.
+
+2007-11-25 Tomas Cerha <cerha at brailcom.org>
+
+ * speechd/client.py: Handle TypeError while SPEECHD_PORT value
+ conversion.
+
+2007-11-22 Tomas Cerha <cerha at brailcom.org>
+
+ * speechd/client.py (SSIPClient.__init__): Use the environment
+ variable `SPEECHD_PORT' if defined.
+
+2007-07-11 Tomas Cerha <cerha at brailcom.org>
+
+ * speechd/client.py (SSIPClient.list_synthesis_voices.split):
+ Convert empty strings to `None'. Docstring improved.
+
+2007-07-10 Tomas Cerha <cerha at brailcom.org>
+
+ * speechd/_test.py (AutomaticTest.test_callbacks): Test also the
+ `END' callback, which doesn't currently work with Flite.
+
+2007-07-04 Tomas Cerha <cerha at brailcom.org>
+
+ * speechd/client.py (SSIPClient.resume, SSIPClient.pause): Send
+ the right SSIP commands.
+
+2007-07-03 Tomas Cerha <cerha at brailcom.org>
+
+ * speechd/client.py (_SSIP_Connection._recv_response): Raise
+ exception if the communication thread is not alive.
+
+2007-07-02 Tomas Cerha <cerha at brailcom.org>
+
+ * speechd/client.py (SSIPCommunicationError): New class.
+ (_SSIP_Connection._recv_response): Quit if the communication
+ thread is not alive.
+ (_SSIP_Connection.send_command, _SSIP_Connection.send_data): Raise
+ `SSIPCommunicationError' on socket error.
+
+2007-07-02 Hynek Hanke <hanke at syrr.buchal.name>
+
+ * speechd/_test.py: Voice list test uncommented.
+
+2007-06-26 Tomas Cerha <cerha at brailcom.org>
+
+ * speechd/_test.py (VoiceTest.test_lists): New test.
+
+ * speechd/client.py (SSIPClient.list_output_modules)
+ (SSIPClient.list_synthesis_voices, SSIPClient.set_output_module)
+ (SSIPClient.set_synthesis_voice): New methods.
+
+2007-05-03 Hynek Hanke <hanke at brailcom.org>
+
+ * Makefile (clean): Remove build.
+
+2007-02-21 Tomas Cerha <cerha at brailcom.org>
+
+ * speechd/_test.py (AutomaticTest.test_callbacks): Added warning.
+
+2007-02-17 Hynek Hanke <hanke at brailcom.org>
+
+ * speechd/_test.py (AutomaticTest.test_callbacks): Removed comment
+ about Scope.SELF not working.
+ Added TODO comment about fixing this test.
+
+2007-02-05 Tomas Cerha <cerha at brailcom.org>
+
+ * speechd/client.py (_SSIP_Connection.__init__): Thread name
+ changed.
+ (SSIPClient.__init__): Allocate lock.
+ (SSIPClient._callback_handler): Lock before accessing
+ `self._callbacks'.
+ (SSIPClient.speak): Added more doc. Lock before accessing
+ `self._callbacks'.
+
+2007-01-29 Tomas Cerha <cerha at brailcom.org>
+
+ * speechd/client.py (SSIPClient._callback_handler)
+ (SSIPClient.speak): Removed prints.
+
+ * speechd/_test.py (SSIPClientTest.check_callbacks): Wait for
+ callbacks after canceling messages.
+ (TestSuite): Class removed.
+ (tests): Instance removed.
+ (SSIPClientTest): Class split into `AutomaticTest' and `VoiceTest'.
+ (_SSIPClientTest, AutomaticTest, VoiceTest): New classes.
+ (SpeakerTest): Class removed.
+ (get_tests): Function removed.
+
+2007-01-26 Tomas Cerha <cerha at brailcom.org>
+
+ * speechd/_test.py (SSIPClientTest.check_callbacks): New test.
+ (SSIPClientTest.check_notification): Temporarily commented out.
+
+ * speechd/client.py
+ (_SSIP_Connection._CALLBACK_TYPE_MAP): New constant.
+ (_SSIP_Connection.__init__): Initialize `self._callback' instead
+ of `self._callbacks'.
+ (_SSIP_Connection._communication): Docstring reformatted. The
+ whole event dispatching rewritten to use `_CALLBACK_TYPE_MAP' and
+ use a single callback function.
+ (_SSIP_Connection.send_command): Docstring typo fixed.
+ (_SSIP_Connection.send_data): Docstring typo fixed.
+ (_SSIP_Connection.send_data): Return also server response data.
+ (_SSIP_Connection.set_callback): Argument `events' removed.
+ Docstring updated and extended.
+ (SSIPClient.__init__): Find out and store the client id. Register
+ callback and turn on notifications for all supported event types.
+ (SSIPClient._callback_handler): New method.
+ (SSIPClient.speak): New keyword arguments `callback' and
+ `event_types'. Docstring extended. Convert `text' to `utf-8' if
+ it is a unicode instance.
+ (SSIPClient.char): Convert `char' to `utf-8' if it is a unicode
+ instance.
+ (SSIPClient.set_notification): Method removed.
+ (SSIPClient.close): Only operate on `self._conn' if it is defined.
+
+2006-11-05 Hynek Hanke <hanke at brailcom.org>
+
+ * speechd/client.py: IMPORTANT: On initialization of the
+ connection, new lateral thread is started to handle the
+ communication. This thread is terminated when the connection is
+ closed.
+ (_SSIP_Connection._com_buffer): New attribute.
+ (_SSIP_Connection._callbacks): New attribute.
+ (_SSIP_Connection._ssip_reply_semaphore): New attribute.
+ (_SSIP_Connection._communication_thread): New attribute.
+ (_SSIP_Connection.send_command): 'or isinstance(args[0], int)'
+ added back again. This is valid SSIP.
+ (SSIPClient.set_notification): New public API function.
+
+ * speechd/_test.py
+ (SSIPClientTest.check_notification.my_callback): New test.
+
+ * speechd/client.py (_SSIP_Connection.__init__): Start the
+ self._communication method in a new thread.
+ (_SSIP_Connection._recv_response): 1->True
+ (_SSIP_Connection._recv_message): Renamed from _recv_response.
+ (SSIPClient.__del__): New destructor.
+
+2006-08-09 Hynek Hanke <hanke at brailcom.org>
+
+ * setup.py: Use /usr/bin/env instead of /usr/bin/python
+
+ * Makefile (install): Install with correct prefix.
+
+2006-07-13 Hynek Hanke <hanke at brailcom.org>
+
+ * Makefile: Only attempt to use distutils if python is installed.
+
+2006-07-11 Hynek Hanke <hanke at chopin>
+
+ * speechd/client.py: Typos in docstrings.
+ (Speaker): Docstring clarification.
+ Method say() removed.
+
+2006-07-11 Tomas Cerha <cerha at brailcom.org>
+
+ * speechd/_test.py: `Client' -> `SSIPClient'.
+ (ClientTest): -> `SSIPClientTest'.
+ (SSIPClientTest.check_escapes)
+ (SSIPClientTest.check_voice_properties)
+ (SSIPClientTest.check_other_commands): `say' -> `speak'.
+ (SpeakerTest): New test class.
+
+ * speechd/client.py (Module): Docstring updated.
+ (Client): Class renamed to `SSIPClient'.
+ (SSIPClient): Docstring improved.
+ (SSIPClient.__init__): Don't initialize `self._current_priority'.
+ (SSIPClient.set_priority): Added docstring. Always send the
+ command. Don't check `self._current_priority'.
+ (SSIPClient.say): Method renamed to `speak'.
+ (SSIPClient.speak, SSIPClient.char, SSIPClient.key)
+ (SSIPClient.sound_icon): The arguent `priority' removed. Don't
+ set the priority here.
+ (Speaker): New class.
+ (Client): New class.
+
+2006-07-10 Tomas Cerha <cerha at brailcom.org>
+
+ * speechd/_test.py: Import `speechd' not `client'.
+ (Client): Renamed to `ClientTest'.
+ (ClientTest._client): New method.
+ (ClientTest.check_escapes, ClientTest.check_voice_properties)
+ (ClientTest.check_other_commands): New methods.
+ (ClientTest.check_it): Method split into the above new methods.
+
+ * speechd/client.py: Don't import `string'.
+ (SSIPCommandError, SSIPDataError): Docstrings improved.
+ (_SSIP_Connection.NEWLINE): Renamed to `_NEWLINE'.
+ (_SSIP_Connection.END_OF_DATA_SINGLE): Renamed to
+ `_END_OF_DATA_MARKER'.
+ (_SSIP_Connection.END_OF_DATA_ESCAPED_SINGLE): Renamed to
+ `_END_OF_DATA_MARKER_ESCAPED'.
+ (_SSIP_Connection.END_OF_DATA_BEGIN)
+ (_SSIP_Connection.END_OF_DATA_ESCAPED_BEGIN): Constants removed.
+ (_SSIP_Connection.END_OF_DATA): Renamed to `_END_OF_DATA'.
+ (_SSIP_Connection.END_OF_DATA_ESCAPED): Renamed to
+ `_END_OF_DATA_ESCAPED'.
+ (_SSIP_Connection._readline): Docstring improved. `self.NEWLINE'
+ -> `self._NEWLINE'.
+ (_SSIP_Connection.send_command): `self.NEWLINE' ->
+ `self._NEWLINE'.
+ (_SSIP_Connection.send_data): Use new constant names.
+ (Client.__init__): Initialize the `_priority' attribute to None.
+ (Client._set_priority): Only set the priority if it is different
+ than the last priority stored in the `_priority' attribute. Set
+ this attribute on change.
+ (Client.char): Replace the space by `space' as required by SSIP.
+ (Client.__init__): `self._priority' renamed to
+ `self._current_priority'.
+ (Client._set_priority): Renamed to `_set_priority'.
+ (Client.set_priority): `self._priority' ->
+ `self._current_priority'.
+ (Client.char, Client.key, Client.sound_icon, Client.say):
+ `self._set_priority' -> `self.set_priority'.
+ (Client.pause): Improved docstring formatting.
+
+ * speechd/util.py: File removed.
+
+2006-06-28 Tomas Cerha <cerha at brailcom.org>
+
+ * speechd/client.py (SSIPCommandError)
+ (_CommunicationError): Renamed to `SSIPError'.
+ (CommandError): Renamed to `SSIPCommandError'.
+ (SendDataError): Renamed to `SSIPDataError'.
+ (_SSIP_Connection.send_command): Added an assertion on the 'scope'
+ argument for certain commands.
+ (_SSIP_Connection.send_command): `CommandError' ->
+ `SSIPCommandError'.
+ (_SSIP_Connection.send_data): `SendDataError' -> `SSIPDataError'.
+ (Scope, Priority): New classes.
+ (Client): Derive from `object'. Some doc added.
+ (Client.__init__): The `port' default value set to `None'.
+ (Client.__set_priority): Renamed to `_set_priority()'.
+ (Client._set_priority): Use the `Priority' constants for
+ assertion.
+ (__check_scope): Method removed.
+ (Client.say, Client.char, Client.key, Client.sound_icon): Use
+ `Priority' constants for default values. `__set_priority()' ->
+ `_set_priority()'. Docstrings updated.
+ (Client.char): Don't replace space by "space".
+ (Client.cancel, Client.stop, Client.resume, Client.set_language)
+ (Client.set_pitch, Client.set_rate, Client.set_volume)
+ (Client.set_punctuation, Client.set_spelling)
+ (Client.set_cap_let_recogn, Client.set_voice)
+ (Client.set_pause_context): Use `Scope' constants for default
+ values. Don't call `__check_scope()'. Docstrings updated.
+ (Client.say): Only set the priority if it's not a 'MESSAGE'.
+ (Client.set_rate, _SSIP_Connection.send_command): Use the `Scope'
+ constants for assertions.
+ (_SSIP_Connection.close): Unset the `_socket' attribute.
+ (PunctuationMode): New class.
+ (Client.__init__, Client._set_priority): Use the `Scope'
+ constants.
+ (Client.say): Don't set the priority if it is 'MESSAGE'.
+ (Client.set_pitch, Client.set_rate, Client.set_volume)
+ (Client.set_voice, Client.set_pause_context): Use `isinstance'
+ instead of comparint types.
+ (Client.set_punctuation): Use the 'PunctuationMode' constants for
+ assertions.
+
+2006-06-27 Tomas Cerha <cerha at brailcom.org>
+
+ * speechd/client.py (_SSIP_Connection.__init__): Initialize
+ the `_buffer' attribute.
+ (_SSIP_Connection._readline): Read data from the socket using a
+ buffer, not character by character...
+ (Client.__init__): Require the `name' argument. Changed the
+ default value of `user' to `unknown' and `component' to `default'.
+ (Client.pause): Cosmetical change.
+
+2003-11-20 Tomas Cerha <cerha at freebsoft.org>
+
+ * speechd/client.py (Client): Changed the port number.
+
+ * speechd/_test.py (get_tests): Don't set the language.
+
+2003-07-22 Tomas Cerha <cerha at freebsoft.org>
+
+ * speechd/_test.py (Client.check_it): Added tests for `set_rate()'
+ and `set_pitch()' methods.
+ (Client.check_it): Added test for Czech language.
+
+ * speechd/client.py: `Speech Daemon' changed to `Speech
+ Dispatcher'.
+ (_SSIP_Connection): New Class.
+ (Client.RECV_BUFFER_SIZE, Client.SSIP_NEWLINE)
+ (Client._SSIP_END_OF_DATA, Client._SSIP_END_OF_DATA_ESCAPED):
+ Constants removed.
+ (_Socket): Class removed.
+ (Client.__init__): Use `_SSIP_Conection' instead of plain socket.
+ (Client._send_command, Client._send_data, Client._recv_response):
+ Methods removed.
+ (Client.close): Close `self._conn' instead of `self._socket'.
+ (Client.say, Client.stop): Use `self._conn.send_command()' instead
+ of `self._send_command()'.
+ (Client.set_language): New method.
+ (Client.set_pitch): New method.
+ (Client.set_pitch): New method.
+ (Client.get_client_list): Use `self._conn.send_command()' instead
+ of `self._send_command()'.
+
+2003-07-17 Tomas Cerha <cerha at freebsoft.org>
+
+ * speechd/_test.py (Client.check_it): Pass a new `user' argument
+ to `Client' constructor.
+
+ * speechd/client.py (Client.__init__._Socket): New class.
+ (Client.__init__): Use `_Socket' instead of `socket.socket'.
+ (Client._recv_response): Use `_Socket.readline()'.
+ (module): The commented out C library code removed.
+ (Client.say): Use new priority names.
+ (Client.__init__): Arguments `client_name' and `connection_name'
+ replaced by `user', `client' and `component'. Send `self' in the
+ 'SET self CLIENT_NAME' command.
+
+2003-03-30 Tomas Cerha <cerha at brailcom.org>
+
+ * speechd/client.py (Client.SPEECH_PORT): Updated docstring.
+ (Client.RECV_BUFFER_SIZE, Client.SSIP_NEWLINE)
+ (Client._SSIP_END_OF_DATA, Client._SSIP_END_OF_DATA_ESCAPED): New
+ constants.
+ (Client._send_command, Client._send_data, Client._recv_response):
+ Use them.
+ (Client._send_data, Client._recv_response): Rewritten, to handle
+ multiline responses. Return response data as third item of
+ returned tuple.
+ (Client.close): Don't send `BYE' command.
+ (Client.say): Don't return server response (isolate client from
+ those details).
+ (Client.get_client_list): New method.
+
+2003-03-29 Tomas Cerha <cerha at freebsoft.org>
+
+ * speechd/client.py (SPEECH_PORT, RECV_BUFFER_SIZE): Moved to
+ class `Client'.
+ (Client): Documentation added.
+ (Client.SPEECH_PORT, Client.RECV_BUFFER_SIZE): New constants.
+ (Client.__init__): `SPEECH_PORT' -> `self.SPEECH_PORT'
+ (Client._send_command): Cosmetic changes.
+ (Client._send_data): Cosmetic changes.
+ (Client._recv_response): Commant added. `RECV_BUFFER_SIZE' ->
+ `self.RECV_BUFFER_SIZE'.
+ (Client.close): Documentation added.
+ (Client.stop): New keyword argument `all'.
+ (Client.say): Documentation added. Return the value of respose
+ code and message.
+
+2003-03-27 Tomas Cerha <cerha at freebsoft.org>
+
+ * speechd/client.py: Import `string' module.
+ (SPEECH_PORT): Added docstring.
+ (RECV_BUFFER_SIZE): New constant.
+ (_CommunicationError): New class.
+ (CommandError): New class.
+ (SendDataError.data): New class.
+ (Client.__init__): Make `client_name' and `conn_name' keyword
+ args.
+ (Client._send_command): Convert `args' to strings. Handle server
+ response. Return resonse code and message.
+ (Client._send_data): New method.
+ (Client._recv_response): New method.
+ (Client.close): Before closing socket send a `BYE' command.
+ (Client.stop): New method.
+ (Client.say): New method.
+
+ * speechd/_test.py (Client.check_it): Test the `say()' method.
+
diff --git a/src/api/python/Makefile.am b/src/api/python/Makefile.am
new file mode 100644
index 0000000..d54489f
--- /dev/null
+++ b/src/api/python/Makefile.am
@@ -0,0 +1,2 @@
+## Process this file with automake to produce Makefile.in
+SUBDIRS = speechd speechd_config
diff --git a/src/api/python/README b/src/api/python/README
new file mode 100644
index 0000000..39d74a3
--- /dev/null
+++ b/src/api/python/README
@@ -0,0 +1,10 @@
+This is a Python interface to SSIP.
+
+Full range of SSIP commands is implemented including callback handling. See
+the section "Python API" in Speech Dispatcher documentation for more
+information.
+
+If you have any questions, suggestions, etc., feel free to contact us at the
+mailing list <speechd at lists.freebsoft.org>.
+
+-- Tomas Cerha <cerha at freebsoft.org>
diff --git a/src/api/python/speechd/Makefile.am
b/src/api/python/speechd/Makefile.am
new file mode 100644
index 0000000..59a7afe
--- /dev/null
+++ b/src/api/python/speechd/Makefile.am
@@ -0,0 +1,21 @@
+## Process this file with automake to produce Makefile.in
+
+speechd_pythondir = $(pyexecdir)/speechd
+speechd_python_PYTHON = __init__.py _test.py client.py
+
+nodist_speechd_python_PYTHON = paths.py
+
+edit = sed \
+ -e 's:@address@hidden:$(bindir):g'
+
+paths.py: Makefile
+ rm -f $@
+ srcdir=; \
+ test -f ./address@hidden || srcdir=$(srcdir)/; \
+ $(edit) address@hidden > $@
+
+paths.py: $(srcdir)/paths.py.in
+
+CLEANFILES = paths.py
+
+EXTRA_DIST = paths.py.in
diff --git a/src/api/python/speechd/__init__.py
b/src/api/python/speechd/__init__.py
new file mode 100644
index 0000000..2bb2923
--- /dev/null
+++ b/src/api/python/speechd/__init__.py
@@ -0,0 +1,18 @@
+# Copyright (C) 2001, 2002 Brailcom, o.p.s.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from client import *
+
diff --git a/src/api/python/speechd/_test.py b/src/api/python/speechd/_test.py
new file mode 100644
index 0000000..d503e81
--- /dev/null
+++ b/src/api/python/speechd/_test.py
@@ -0,0 +1,136 @@
+#!/usr/bin/env python
+
+# Copyright (C) 2003, 2006, 2007 Brailcom, o.p.s.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public Licensex1 as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import unittest
+import time
+
+from client import PunctuationMode, CallbackType, SSIPClient, Scope, Speaker
+
+
+class _SSIPClientTest(unittest.TestCase):
+
+ def setUp(self):
+ self._client = SSIPClient('test')
+ self._client.set_language('en')
+ self._client.set_rate(30)
+
+ def tearDown(self):
+ self._client.close()
+
+class AutomaticTest(_SSIPClientTest):
+ """A set of tests which may be evaluated automatically.
+
+ Please put all tests which require a user to listen to their output to the
+ VoiceTest below.
+
+ """
+ def test_callbacks(self):
+ # TODO: This needs to be fixed. There is no guarantee that
+ # the message will start in one second nor is there any
+ # guarantee that it will start at all. It can be interrupted
+ # by other applications etc. Also there is no guarantee that
+ # the cancel will arrive on time and the end callback will be
+ # received on time. Also the combination cancel/end does not have
+ # to work as expected and SD and the interface can still be ok.
+ # -- Hynek Hanke
+ self._client.set_output_module('flite')
+ called = {CallbackType.BEGIN: [],
+ CallbackType.CANCEL: [],
+ CallbackType.END: []}
+ self._client.speak("This message should get interrupted. It is "
+ "hopefully long enough to last more than 1 second.",
+ callback=lambda type: called[type].append('msg1'))
+ self._client.speak("This second message should not be spoken at all.",
+ callback=lambda type: called[type].append('msg2'))
+ time.sleep(1)
+ self._client.cancel()
+ self._client.speak("Hi.",
+ callback=lambda type: called[type].append('msg3'))
+ # Wait for pending events...
+ time.sleep(3)
+ started, canceled, ended = [called[t] for t in (CallbackType.BEGIN,
+ CallbackType.CANCEL,
+ CallbackType.END)]
+ assert started == ['msg1', 'msg3'] and ended == ['msg3'] and \
+ 'msg1' in canceled and 'msg2' in canceled and \
+ 'msg3' not in canceled, \
+ (called,
+ "This failure only indicates a possible error. The test "
+ "depends on proper timing and results may warry depending "
+ "on the used output module and other conditions. See the "
+ "code of this test method if you want to investigate "
+ "further.")
+
+
+
+class VoiceTest(_SSIPClientTest):
+ """This set of tests requires a user to listen to it.
+
+ The success or failure of the tests defined here can not be detected
+ automatically.
+
+ """
+
+ def test_escapes(self):
+ c = self._client
+ c.speak("Testing data escapes:")
+ c.set_punctuation(PunctuationMode.ALL)
+ c.speak(".")
+ c.speak("Marker at the end.\r\n.\r\n")
+ c.speak(".\r\nMarker at the beginning.")
+
+ def test_voice_properties(self):
+ c = self._client
+ c.speak("Testing voice properties:")
+ c.set_pitch(-100)
+ c.speak("I am fat Billy")
+ c.set_pitch(100)
+ c.speak("I am slim Willy")
+ c.set_pitch(0)
+ c.set_rate(100)
+ c.speak("I am quick Dick.")
+ c.set_rate(-80)
+ c.speak("I am slow Joe.")
+ c.set_rate(0)
+ c.set_pitch(100)
+ c.set_volume(-50)
+ c.speak("I am quiet Mariette.")
+ c.set_volume(100)
+ c.speak("I am noisy Daisy.")
+
+ def test_other_commands(self):
+ c = self._client
+ c.speak("Testing other commands:")
+ c.char("a")
+ c.key("shift_b")
+ c.sound_icon("empty")
+
+ def test_lists(self):
+ c = self._client
+ for module in c.list_output_modules():
+ c.set_output_module(module)
+ print "**", module
+ c.speak(module +"using default voice")
+ for name, lang, dialect in c.list_synthesis_voices():
+ print " -", module, name, lang, dialect
+ c.set_synthesis_voice(name)
+ c.speak(module +" using voice "+ name)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/src/api/python/speechd/client.py b/src/api/python/speechd/client.py
new file mode 100644
index 0000000..13a9c7e
--- /dev/null
+++ b/src/api/python/speechd/client.py
@@ -0,0 +1,1125 @@
+# Copyright (C) 2003-2008 Brailcom, o.p.s.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+"""Python API to Speech Dispatcher
+
+Basic Python client API to Speech Dispatcher is provided by the 'SSIPClient'
+class. This interface maps directly to available SSIP commands and logic.
+
+A more convenient interface is provided by the 'Speaker' class.
+
+"""
+
+#TODO: Blocking variants for speak, char, key, sound_icon.
+
+import socket, sys, os, subprocess, time, tempfile
+
+try:
+ import threading
+except:
+ import dummy_threading as threading
+
+import paths
+
+class CallbackType(object):
+ """Constants describing the available types of callbacks"""
+ INDEX_MARK = 'index_marks'
+ """Index mark events are reported when the place they were
+ included into the text by the client application is reached
+ when speaking them"""
+ BEGIN = 'begin'
+ """The begin event is reported when Speech Dispatcher starts
+ actually speaking the message."""
+ END = 'end'
+ """The end event is reported after the message has terminated and
+ there is no longer any sound from it being produced"""
+ CANCEL = 'cancel'
+ """The cancel event is reported when a message is canceled either
+ on request of the user, because of prioritization of messages or
+ due to an error"""
+ PAUSE = 'pause'
+ """The pause event is reported after speaking of a message
+ was paused. It no longer produces any audio."""
+ RESUME = 'resume'
+ """The resume event is reported right after speaking of a message
+ was resumed after previous pause."""
+
+class SSIPError(Exception):
+ """Common base class for exceptions during SSIP communication."""
+
+class SSIPCommunicationError(SSIPError):
+ """Exception raised when trying to operate on a closed connection."""
+
+ _additional_exception = None
+
+ def __init__(self, description=None, original_exception=None, **kwargs):
+ self._original_exception = original_exception
+ self._description = description
+ super(SSIPError, self).__init__(**kwargs)
+
+ def original_exception(self):
+ """Return the original exception if any
+
+ If this exception is secondary, being caused by a lower
+ level exception, return this original exception, otherwise
+ None"""
+ return self._original_exception
+
+ def set_additional_exception(self, exception):
+ """Set an additional exception
+
+ See method additional_exception().
+ """
+ self._additional_exception = exception
+
+ def additional_exception(self):
+ """Return an additional exception
+
+ Additional exceptions araise from failed attempts to resolve
+ the former problem"""
+ return self._additional_exception
+
+ def description(self):
+ """Return error description"""
+ return self._description
+
+ def __str__(self):
+ msgs = []
+ if self.description():
+ msgs.append(self.description())
+ if self.original_exception:
+ msgs.append("Original error: " + str(self.original_exception()))
+ if self.additional_exception:
+ msgs.append("Additional error: " +
str(self.additional_exception()))
+ return "\n".join(msgs)
+
+class SSIPResponseError(Exception):
+ def __init__(self, code, msg, data):
+ Exception.__init__(self, "%s: %s" % (code, msg))
+ self._code = code
+ self._msg = msg
+ self._data = data
+
+ def code(self):
+ """Return the server response error code as integer number."""
+ return self._code
+
+ def msg(self):
+ """Return server response error message as string."""
+ return self._msg
+
+
+class SSIPCommandError(SSIPResponseError):
+ """Exception raised on error response after sending command."""
+
+ def command(self):
+ """Return the command string which resulted in this error."""
+ return self._data
+
+
+class SSIPDataError(SSIPResponseError):
+ """Exception raised on error response after sending data."""
+
+ def data(self):
+ """Return the data which resulted in this error."""
+ return self._data
+
+
+class SpawnError(Exception):
+ """Indicates failure in server autospawn."""
+
+class CommunicationMethod(object):
+ """Constants describing the possible methods of connection to server."""
+ UNIX_SOCKET = 'unix_socket'
+ """Unix socket communication using a filesystem path"""
+ INET_SOCKET = 'inet_socket'
+ """Inet socket communication using a host and port"""
+
+class _SSIP_Connection(object):
+ """Implemantation of low level SSIP communication."""
+
+ _NEWLINE = "\r\n"
+ _END_OF_DATA_MARKER = '.'
+ _END_OF_DATA_MARKER_ESCAPED = '..'
+ _END_OF_DATA = _NEWLINE + _END_OF_DATA_MARKER + _NEWLINE
+ _END_OF_DATA_ESCAPED = _NEWLINE + _END_OF_DATA_MARKER_ESCAPED + _NEWLINE
+ # Constants representing \r\n. and \r\n..
+ _RAW_DOTLINE = _NEWLINE + _END_OF_DATA_MARKER
+ _ESCAPED_DOTLINE = _NEWLINE + _END_OF_DATA_MARKER_ESCAPED
+
+ _CALLBACK_TYPE_MAP = {700: CallbackType.INDEX_MARK,
+ 701: CallbackType.BEGIN,
+ 702: CallbackType.END,
+ 703: CallbackType.CANCEL,
+ 704: CallbackType.PAUSE,
+ 705: CallbackType.RESUME,
+ }
+
+ def __init__(self, communication_method, socket_path, host, port):
+ """Init connection: open the socket to server,
+ initialize buffers, launch a communication handling
+ thread.
+ """
+
+ if communication_method == CommunicationMethod.UNIX_SOCKET:
+ socket_family = socket.AF_UNIX
+ socket_connect_args = socket_path
+ elif communication_method == CommunicationMethod.INET_SOCKET:
+ assert host and port
+ socket_family = socket.AF_INET
+ socket_connect_args = (socket.gethostbyname(host), port)
+ else:
+ raise ValueError("Unsupported communication method")
+
+ try:
+ self._socket = socket.socket(socket_family, socket.SOCK_STREAM)
+ self._socket.connect(socket_connect_args)
+ except socket.error, ex:
+ raise SSIPCommunicationError("Can't open socket using method "
+ + communication_method,
+ original_exception = ex)
+
+ self._buffer = ""
+ self._com_buffer = []
+ self._callback = None
+ self._ssip_reply_semaphore = threading.Semaphore(0)
+ self._communication_thread = \
+ threading.Thread(target=self._communication, kwargs={},
+ name="SSIP client communication thread")
+ self._communication_thread.start()
+
+ def close(self):
+ """Close the server connection, destroy the communication thread."""
+ # Read-write shutdown here is necessary, otherwise the socket.recv()
+ # function in the other thread won't return at last on some platforms.
+ try:
+ self._socket.shutdown(socket.SHUT_RDWR)
+ except socket.error:
+ pass
+ self._socket.close()
+ # Wait for the other thread to terminate
+ self._communication_thread.join()
+
+ def _communication(self):
+ """Handle incomming socket communication.
+
+ Listens for all incomming communication on the socket, dispatches
+ events and puts all other replies into self._com_buffer list in the
+ already parsed form as (code, msg, data). Each time a new item is
+ appended to the _com_buffer list, the corresponding semaphore
+ 'self._ssip_reply_semaphore' is incremented.
+
+ This method is designed to run in a separate thread. The thread can be
+ interrupted by closing the socket on which it is listening for
+ reading."""
+
+ while True:
+ try:
+ code, msg, data = self._recv_message()
+ except IOError:
+ # If the socket has been closed, exit the thread
+ sys.exit()
+ if code/100 != 7:
+ # This is not an index mark nor an event
+ self._com_buffer.append((code, msg, data))
+ self._ssip_reply_semaphore.release()
+ continue
+ # Ignore the event if no callback function has been registered.
+ if self._callback is not None:
+ type = self._CALLBACK_TYPE_MAP[code]
+ if type == CallbackType.INDEX_MARK:
+ kwargs = {'index_mark': data[2]}
+ else:
+ kwargs = {}
+ # Get message and client ID of the event
+ msg_id, client_id = map(int, data[:2])
+ self._callback(msg_id, client_id, type, **kwargs)
+
+
+ def _readline(self):
+ """Read one whole line from the socket.
+
+ Blocks until the line delimiter ('_NEWLINE') is read.
+
+ """
+ pointer = self._buffer.find(self._NEWLINE)
+ while pointer == -1:
+ try:
+ d = self._socket.recv(1024)
+ except:
+ raise IOError
+ if len(d) == 0:
+ raise IOError
+ self._buffer += d
+ pointer = self._buffer.find(self._NEWLINE)
+ line = self._buffer[:pointer]
+ self._buffer = self._buffer[pointer+len(self._NEWLINE):]
+ return line
+
+ def _recv_message(self):
+ """Read server response or a callback
+ and return the triplet (code, msg, data)."""
+ data = []
+ c = None
+ while True:
+ line = self._readline()
+ assert len(line) >= 4, "Malformed data received from server!"
+ code, sep, text = line[:3], line[3], line[4:]
+ assert code.isalnum() and (c is None or code == c) and \
+ sep in ('-', ' '), "Malformed data received from server!"
+ if sep == ' ':
+ msg = text
+ return int(code), msg, tuple(data)
+ data.append(text)
+
+ def _recv_response(self):
+ """Read server response from the communication thread
+ and return the triplet (code, msg, data)."""
+ # TODO: This check is dumb but seems to work. The main thread
+ # hangs without it, when the Speech Dispatcher connection is lost.
+ if not self._communication_thread.isAlive():
+ raise SSIPCommunicationError
+ self._ssip_reply_semaphore.acquire()
+ # The list is sorted, read the first item
+ response = self._com_buffer[0]
+ del self._com_buffer[0]
+ return response
+
+ def send_command(self, command, *args):
+ """Send SSIP command with given arguments and read server response.
+
+ Arguments can be of any data type -- they are all stringified before
+ being sent to the server.
+
+ Returns a triplet (code, msg, data), where 'code' is a numeric SSIP
+ response code as an integer, 'msg' is an SSIP rsponse message as string
+ and 'data' is a tuple of strings (all lines of response data) when a
+ response contains some data.
+
+ 'SSIPCommandError' is raised in case of non 2xx return code. See SSIP
+ documentation for more information about server responses and codes.
+
+ 'IOError' is raised when the socket was closed by the remote side.
+
+ """
+ if __debug__:
+ if command in ('SET', 'CANCEL', 'STOP',):
+ assert args[0] in (Scope.SELF, Scope.ALL) \
+ or isinstance(args[0], int)
+ cmd = ' '.join((command,) + tuple(map(str, args)))
+ try:
+ self._socket.send(cmd + self._NEWLINE)
+ except socket.error:
+ raise SSIPCommunicationError("Speech Dispatcher connection lost.")
+ code, msg, data = self._recv_response()
+ if code/100 != 2:
+ raise SSIPCommandError(code, msg, cmd)
+ return code, msg, data
+
+ def send_data(self, data):
+ """Send multiline data and read server response.
+
+ Returned value is the same as for 'send_command()' method.
+
+ 'SSIPDataError' is raised in case of non 2xx return code. See SSIP
+ documentation for more information about server responses and codes.
+
+ 'IOError' is raised when the socket was closed by the remote side.
+
+ """
+ # Escape the end-of-data marker even if present at the beginning
+ # The start of the string is also the start of a line.
+ if data.startswith(self._END_OF_DATA_MARKER):
+ l = len(self._END_OF_DATA_MARKER)
+ data = self._END_OF_DATA_MARKER_ESCAPED + data[l:]
+
+ # Escape the end of data marker at the start of each subsequent
+ # line. We can do that by simply replacing \r\n. with \r\n..,
+ # since the start of a line is immediately preceded by \r\n,
+ # when the line is not the beginning of the string.
+ data = data.replace(self._RAW_DOTLINE, self._ESCAPED_DOTLINE)
+
+ try:
+ self._socket.send(data + self._END_OF_DATA)
+ except socket.error:
+ raise SSIPCommunicationError("Speech Dispatcher connection lost.")
+ code, msg, response_data = self._recv_response()
+ if code/100 != 2:
+ raise SSIPDataError(code, msg, data)
+ return code, msg, response_data
+
+ def set_callback(self, callback):
+ """Register a callback function for handling asynchronous events.
+
+ Arguments:
+ callback -- a callable object (function) which will be called to
+ handle asynchronous events (arguments described below). Passing
+ `None' results in removing the callback function and ignoring
+ events. Just one callback may be registered. Attempts to register
+ a second callback will result in the former callback being
+ replaced.
+
+ The callback function must accept three positional arguments
+ ('message_id', 'client_id', 'event_type') and an optional keyword
+ argument 'index_mark' (when INDEX_MARK events are turned on).
+
+ Note, that setting the callback function doesn't turn the events on.
+ The user is responsible to turn them on by sending the appropriate `SET
+ NOTIFICATION' command.
+
+ """
+ self._callback = callback
+
+class _CallbackHandler(object):
+ """Internal object which handles callbacks."""
+
+ def __init__(self, client_id):
+ self._client_id = client_id
+ self._callbacks = {}
+ self._lock = threading.Lock()
+
+ def __call__(self, msg_id, client_id, type, **kwargs):
+ if client_id != self._client_id:
+ # TODO: does that ever happen?
+ return
+ self._lock.acquire()
+ try:
+ try:
+ callback, event_types = self._callbacks[msg_id]
+ except KeyError:
+ pass
+ else:
+ if event_types is None or type in event_types:
+ callback(type, **kwargs)
+ if type in (CallbackType.END, CallbackType.CANCEL):
+ del self._callbacks[msg_id]
+ finally:
+ self._lock.release()
+
+ def add_callback(self, msg_id, callback, event_types):
+ self._lock.acquire()
+ try:
+ self._callbacks[msg_id] = (callback, event_types)
+ finally:
+ self._lock.release()
+
+class Scope(object):
+ """An enumeration of valid SSIP command scopes.
+
+ The constants of this class should be used to specify the 'scope' argument
+ for the 'Client' methods.
+
+ """
+ SELF = 'self'
+ """The command (mostly a setting) applies to current connection only."""
+ ALL = 'all'
+ """The command applies to all current Speech Dispatcher connections."""
+
+
+class Priority(object):
+ """An enumeration of valid SSIP message priorities.
+
+ The constants of this class should be used to specify the 'priority'
+ argument for the 'Client' methods. For more information about message
+ priorities and their interaction, see the SSIP documentation.
+
+ """
+ IMPORTANT = 'important'
+ TEXT = 'text'
+ MESSAGE = 'message'
+ NOTIFICATION = 'notification'
+ PROGRESS = 'progress'
+
+
+class PunctuationMode(object):
+ """Constants for selecting a punctuation mode.
+
+ The mode determines which characters should be read.
+
+ """
+ ALL = 'all'
+ """Read all punctuation characters."""
+ NONE = 'none'
+ """Don't read any punctuation character at all."""
+ SOME = 'some'
+ """Only the user-defined punctuation characters are read.
+
+ The set of characters is specified in Speech Dispatcher configuration.
+
+ """
+
+class DataMode(object):
+ """Constants specifying the type of data contained within messages
+ to be spoken.
+
+ """
+ TEXT = 'text'
+ """Data is plain text."""
+ SSML = 'ssml'
+ """Data is SSML (Speech Synthesis Markup Language)."""
+
+
+class SSIPClient(object):
+ """Basic Speech Dispatcher client interface.
+
+ This class provides a Python interface to Speech Dispatcher functionality
+ over an SSIP connection. The API maps directly to available SSIP commands.
+ Each connection to Speech Dispatcher is represented by one instance of this
+ class.
+
+ Many commands take the 'scope' argument, thus it is shortly documented
+ here. It is either one of 'Scope' constants or a number of connection. By
+ specifying the connection number, you are applying the command to a
+ particular connection. This feature is only meant to be used by Speech
+ Dispatcher control application, however. More datails can be found in
+ Speech Dispatcher documentation.
+
+ """
+
+ DEFAULT_HOST = '127.0.0.1'
+ """Default host for server connections."""
+ DEFAULT_PORT = 6560
+ """Default port number for server connections."""
+ DEFAULT_SOCKET_PATH = "~/.speech-dispatcher/speechd.sock"
+ """Default name of the communication unix socket"""
+
+ def __init__(self, name, component='default', user='unknown', address=None,
+ autospawn=None,
+ # Deprecated ->
+ host=None, port=None, method=None, socket_path=None):
+ """Initialize the instance and connect to the server.
+
+ Arguments:
+ name -- client identification string
+ component -- connection identification string. When one client opens
+ multiple connections, this can be used to identify each of them.
+ user -- user identification string (user name). When multi-user
+ acces is expected, this can be used to identify their connections.
+ address -- server address as specified in Speech Dispatcher
+ documentation (e.g.
"unix:/home/joe/.speech-dispatcher/speechd.sock"
+ or "inet:192.168.0.85:6561")
+ autospawn -- a flag to specify whether the library should
+ try to start the server if it determines its not already
+ running or not
+
+ Deprecated arguments:
+ method -- communication method to use, one of the constants defined
in class
+ CommunicationMethod
+ socket_path -- for CommunicationMethod.UNIX_SOCKET, socket
+ path in filesystem. By default, this is
~/.speech-dispatcher/speechd.sock
+ where `~' is the users home directory as determined from the system
+ defaults and from the $HOMEDIR environment variable.
+ host -- for CommunicationMethod.INET_SOCKET, server hostname
+ or IP address as a string. If None, the default value is
+ taken from SPEECHD_HOST environment variable (if it
+ exists) or from the DEFAULT_HOST attribute of this class.
+ port -- for CommunicationMethod.INET_SOCKET method, server
+ port as number or None. If None, the default value is
+ taken from SPEECHD_PORT environment variable (if it
+ exists) or from the DEFAULT_PORT attribute of this class.
+
+ For more information on client identification strings see Speech
+ Dispatcher documentation.
+ """
+
+ # Resolve connection parameters:
+ connection_args = {'communication_method':
CommunicationMethod.UNIX_SOCKET,
+ 'socket_path':
os.path.expanduser(self.DEFAULT_SOCKET_PATH),
+ 'host': self.DEFAULT_HOST,
+ 'port': self.DEFAULT_PORT,
+ }
+ # Respect address method argument and SPEECHD_ADDRESS environemt
variable
+ _address = address or os.environ.get("SPEECHD_ADDRESS")
+
+ if _address:
+
connection_args.update(self._connection_arguments_from_address(_address))
+ # Respect the old (deprecated) key arguments and environment variables
+ # TODO: Remove this section in 0.8 release
+ else:
+ # Read the environment variables
+ env_speechd_host = os.environ.get("SPEECHD_HOST")
+ try:
+ env_speechd_port = int(os.environ.get("SPEECHD_PORT"))
+ except:
+ env_speechd_port = None
+ env_speechd_socket_path = os.environ.get("SPEECHD_SOCKET")
+ # Prefer old (deprecated) function arguments, but if
+ # not specified and old (deprecated) environment variable
+ # is set, use the value of the environment variable
+ if method:
+ connection_args['method'] = method
+ if port:
+ connection_args['port'] = port
+ elif env_speechd_port:
+ connection_args['port'] = env_speechd_port
+ if socket_path:
+ connection_args['socket_path'] = socket_path
+ elif env_speechd_socket_path:
+ connection_args['socket_path'] = env_speechd_socket_path
+ self._connect_with_autospawn(connection_args, autospawn)
+ self._initialize_connection(user, name, component)
+
+ def _connect_with_autospawn(self, connection_args, autospawn):
+ """Establish new connection (and/or autospawn server)"""
+ try:
+ self._conn = _SSIP_Connection(**connection_args)
+ except SSIPCommunicationError, ce:
+ # Suppose server might not be running, try the autospawn mechanism
+ if autospawn != False:
+ # Autospawn is however not guaranteed to start the server. The
server
+ # will decide, based on it's configuration, whether to honor
the request.
+ try:
+ self._server_spawn(connection_args)
+ except SpawnError, se:
+ ce.set_additional_exception(se)
+ raise ce
+ self._conn = _SSIP_Connection(**connection_args)
+ else:
+ raise
+
+ def _initialize_connection(self, user, name, component):
+ """Initialize connection -- Set client name, get id, register
callbacks etc."""
+ full_name = '%s:%s:%s' % (user, name, component)
+ self._conn.send_command('SET', Scope.SELF, 'CLIENT_NAME', full_name)
+ code, msg, data = self._conn.send_command('HISTORY', 'GET',
'CLIENT_ID')
+ self._client_id = int(data[0])
+ self._callback_handler = _CallbackHandler(self._client_id)
+ self._conn.set_callback(self._callback_handler)
+ for event in (CallbackType.INDEX_MARK,
+ CallbackType.BEGIN,
+ CallbackType.END,
+ CallbackType.CANCEL,
+ CallbackType.PAUSE,
+ CallbackType.RESUME):
+ self._conn.send_command('SET', 'self', 'NOTIFICATION', event, 'on')
+
+ def _connection_arguments_from_address(self, address):
+ """Parse a Speech Dispatcher address line and return a dictionary
+ of connection arguments"""
+ connection_args = {}
+ address_params = address.split(":")
+ try:
+ _method = address_params[0]
+ except:
+ raise SSIPCommunicationErrror("Wrong format of server address")
+ connection_args['communication_method'] = _method
+ if _method == CommunicationMethod.UNIX_SOCKET:
+ try:
+ connection_args['socket_path'] = address_params[1]
+ except IndexError:
+ pass # The additional parameters was not set, let's stay with
defaults
+ elif _method == CommunicationMethod.INET_SOCKET:
+ try:
+ connection_args['host'] = address_params[1]
+ connection_args['port'] = int(address_params[2])
+ except ValueError: # Failed conversion to int
+ raise SSIPCommunicationError("Third parameter of inet_socket
address must be a port number")
+ except IndexError:
+ pass # The additional parameters was not set, let's stay with
defaults
+ else:
+ raise SSIPCommunicationError("Unknown communication method in
address.");
+ return connection_args
+
+ def __del__(self):
+ """Close the connection"""
+ self.close()
+
+ def _server_spawn(self, connection_args):
+ """Attempts to spawn the speech-dispatcher server."""
+ # Check whether we are not connecting to a remote host
+ # TODO: This is a hack. inet sockets specific code should
+ # belong to _SSIPConnection. We do not however have an _SSIPConnection
+ # yet.
+ if connection_args['communication_method'] == 'inet_socket':
+ addrinfos = socket.getaddrinfo(connection_args['host'],
+ connection_args['port'])
+ # Check resolved addrinfos for presence of localhost
+ ip_addresses = [addrinfo[4][0] for addrinfo in addrinfos]
+ localhost=False
+ for ip in ip_addresses:
+ if ip.startswith("127.") or ip == "::1":
+ connection_args['host'] = ip
+ localhost=True
+ if not localhost:
+ # The hostname didn't resolve on localhost in neither case,
+ # do not spawn server on localhost...
+ raise SpawnError(
+ "Can't start server automatically (autospawn), requested
address %s "
+ "resolves on %s which seems to be a remote host. You must
start the "
+ "server manually or choose another connection address." %
(connection_args['host'],
+
str(ip_addresses),))
+ if os.path.exists(paths.SPD_SPAWN_CMD):
+ connection_params = []
+ for param, value in connection_args.items():
+ if param not in ["host",]:
+ connection_params += ["--"+param.replace("_","-"),
str(value)]
+
+ server = subprocess.Popen([paths.SPD_SPAWN_CMD,
"--spawn"]+connection_params,
+ stdin=None, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
+ stdout_reply, stderr_reply = server.communicate()
+ retcode = server.wait()
+ if retcode != 0:
+ raise SpawnError("Server refused to autospawn, stating this
reason: %s" % (stderr_reply,))
+ return server.pid
+ else:
+ raise SpawnError("Can't find Speech Dispatcher spawn command %s"
+ % (paths.SPD_SPAWN_CMD,))
+
+ def set_priority(self, priority):
+ """Set the priority category for the following messages.
+
+ Arguments:
+ priority -- one of the 'Priority' constants.
+
+ """
+ assert priority in (Priority.IMPORTANT, Priority.MESSAGE,
+ Priority.TEXT, Priority.NOTIFICATION,
+ Priority.PROGRESS), priority
+ self._conn.send_command('SET', Scope.SELF, 'PRIORITY', priority)
+
+ def set_data_mode(self, value):
+ """Set the data mode for further speech commands.
+
+ Arguments:
+ value - one of the constants defined by the DataMode class.
+
+ """
+ if value == DataMode.SSML:
+ ssip_val = 'on'
+ elif value == DataMode.TEXT:
+ ssip_val = 'off'
+ else:
+ raise ValueError(
+ 'Value "%s" is not one of the constants from the DataMode
class.' % \
+ value)
+ self._conn.send_command('SET', Scope.SELF, 'SSML_MODE', ssip_val)
+
+ def speak(self, text, callback=None, event_types=None):
+ """Say given message.
+
+ Arguments:
+ text -- message text to be spoken. This may be either a UTF-8
+ encoded byte string or a Python unicode string.
+ callback -- a callback handler for asynchronous event notifications.
+ A callable object (function) which accepts one positional argument
+ `type' and one keyword argument `index_mark'. See below for more
+ details.
+ event_types -- a tuple of event types for which the callback should
+ be called. Each item must be one of `CallbackType' constants.
+ None (the default value) means to handle all event types. This
+ argument is irrelevant when `callback' is not used.
+
+ The callback function will be called whenever one of the events occurs.
+ The event type will be passed as argument. Its value is one of the
+ `CallbackType' constants. In case of an index mark event, additional
+ keyword argument `index_mark' will be passed and will contain the index
+ mark identifier as specified within the text.
+
+ The callback function should not perform anything complicated and is
+ not allowed to issue any further SSIP client commands. An attempt to
+ do so would lead to a deadlock in SSIP communication.
+
+ This method is non-blocking; it just sends the command, given
+ message is queued on the server and the method returns immediately.
+
+ """
+ self._conn.send_command('SPEAK')
+ if isinstance(text, unicode):
+ text = text.encode('utf-8')
+ result = self._conn.send_data(text)
+ if callback:
+ msg_id = int(result[2][0])
+ # TODO: Here we risk, that the callback arrives earlier, than we
+ # add the item to `self._callback_handler'. Such a situation will
+ # lead to the callback being ignored.
+ self._callback_handler.add_callback(msg_id, callback, event_types)
+ return result
+
+ def char(self, char):
+ """Say given character.
+
+ Arguments:
+ char -- a character to be spoken. Either a Python unicode string or
+ a UTF-8 encoded byte string.
+
+ This method is non-blocking; it just sends the command, given
+ message is queued on the server and the method returns immediately.
+
+ """
+ if isinstance(char, unicode):
+ char = char.encode('utf-8')
+ self._conn.send_command('CHAR', char.replace(' ', 'space'))
+
+ def key(self, key):
+ """Say given key name.
+
+ Arguments:
+ key -- the key name (as defined in SSIP); string.
+
+ This method is non-blocking; it just sends the command, given
+ message is queued on the server and the method returns immediately.
+
+ """
+ self._conn.send_command('KEY', key)
+
+ def sound_icon(self, sound_icon):
+ """Output given sound_icon.
+
+ Arguments:
+ sound_icon -- the name of the sound icon as defined by SSIP; string.
+
+ This method is non-blocking; it just sends the command, given message
+ is queued on the server and the method returns immediately.
+
+ """
+ self._conn.send_command('SOUND_ICON', sound_icon)
+
+ def cancel(self, scope=Scope.SELF):
+ """Immediately stop speaking and discard messages in queues.
+
+ Arguments:
+ scope -- see the documentation of this class.
+
+ """
+ self._conn.send_command('CANCEL', scope)
+
+
+ def stop(self, scope=Scope.SELF):
+ """Immediately stop speaking the currently spoken message.
+
+ Arguments:
+ scope -- see the documentation of this class.
+
+ """
+ self._conn.send_command('STOP', scope)
+
+ def pause(self, scope=Scope.SELF):
+ """Pause speaking and postpone other messages until resume.
+
+ This method is non-blocking. However, speaking can continue for a
+ short while even after it's called (typically to the end of the
+ sentence).
+
+ Arguments:
+ scope -- see the documentation of this class.
+
+ """
+ self._conn.send_command('PAUSE', scope)
+
+ def resume(self, scope=Scope.SELF):
+ """Resume speaking of the currently paused messages.
+
+ This method is non-blocking. However, speaking can continue for a
+ short while even after it's called (typically to the end of the
+ sentence).
+
+ Arguments:
+ scope -- see the documentation of this class.
+
+ """
+ self._conn.send_command('RESUME', scope)
+
+ def list_output_modules(self):
+ """Return names of all active output modules as a tuple of strings."""
+ code, msg, data = self._conn.send_command('LIST', 'OUTPUT_MODULES')
+ return data
+
+ def list_synthesis_voices(self):
+ """Return names of all available voices for the current output module.
+
+ Returns a tuple of tripplets (name, language, dialect).
+
+ 'name' is a string, 'language' is an ISO 639-1 Alpha-2 language code
+ and 'dialect' is a string. Language and dialect may be None.
+
+ """
+ try:
+ code, msg, data = self._conn.send_command('LIST',
'SYNTHESIS_VOICES')
+ except SSIPCommandError:
+ return ()
+ def split(item):
+ name, lang, dialect = tuple(item.rsplit(' ', 3))
+ return (name, lang or None, dialect or None)
+ return tuple([split(item) for item in data])
+
+ def set_language(self, language, scope=Scope.SELF):
+ """Switch to a particular language for further speech commands.
+
+ Arguments:
+ language -- two letter language code according to RFC 1776 as string.
+ scope -- see the documentation of this class.
+
+ """
+ assert isinstance(language, str) and len(language) == 2
+ self._conn.send_command('SET', scope, 'LANGUAGE', language)
+
+ def set_output_module(self, name, scope=Scope.SELF):
+ """Switch to a particular output module.
+
+ Arguments:
+ name -- module (string) as returned by 'list_output_modules()'.
+ scope -- see the documentation of this class.
+
+ """
+ self._conn.send_command('SET', scope, 'OUTPUT_MODULE', name)
+
+ def set_pitch(self, value, scope=Scope.SELF):
+ """Set the pitch for further speech commands.
+
+ Arguments:
+ value -- integer value within the range from -100 to 100, with 0
+ corresponding to the default pitch of the current speech synthesis
+ output module, lower values meaning lower pitch and higher values
+ meaning higher pitch.
+ scope -- see the documentation of this class.
+
+ """
+ assert isinstance(value, int) and -100 <= value <= 100, value
+ self._conn.send_command('SET', scope, 'PITCH', value)
+
+ def set_rate(self, value, scope=Scope.SELF):
+ """Set the speech rate (speed) for further speech commands.
+
+ Arguments:
+ value -- integer value within the range from -100 to 100, with 0
+ corresponding to the default speech rate of the current speech
+ synthesis output module, lower values meaning slower speech and
+ higher values meaning faster speech.
+ scope -- see the documentation of this class.
+
+ """
+ assert isinstance(value, int) and -100 <= value <= 100
+ self._conn.send_command('SET', scope, 'RATE', value)
+
+ def set_volume(self, value, scope=Scope.SELF):
+ """Set the speech volume for further speech commands.
+
+ Arguments:
+ value -- integer value within the range from -100 to 100, with 100
+ corresponding to the default speech volume of the current speech
+ synthesis output module, lower values meaning softer speech.
+ scope -- see the documentation of this class.
+
+ """
+ assert isinstance(value, int) and -100 <= value <= 100
+ self._conn.send_command('SET', scope, 'VOLUME', value)
+
+ def set_punctuation(self, value, scope=Scope.SELF):
+ """Set the punctuation pronounciation level.
+
+ Arguments:
+ value -- one of the 'PunctuationMode' constants.
+ scope -- see the documentation of this class.
+
+ """
+ assert value in (PunctuationMode.ALL, PunctuationMode.SOME,
+ PunctuationMode.NONE), value
+ self._conn.send_command('SET', scope, 'PUNCTUATION', value)
+
+ def set_spelling(self, value, scope=Scope.SELF):
+ """Toogle the spelling mode or on off.
+
+ Arguments:
+ value -- if 'True', all incomming messages will be spelled
+ instead of being read as normal words. 'False' switches
+ this behavior off.
+ scope -- see the documentation of this class.
+
+ """
+ assert value in [True, False]
+ if value == True:
+ self._conn.send_command('SET', scope, 'SPELLING', "on")
+ else:
+ self._conn.send_command('SET', scope, 'SPELLING', "off")
+
+ def set_cap_let_recogn(self, value, scope=Scope.SELF):
+ """Set capital letter recognition mode.
+
+ Arguments:
+ value -- one of 'none', 'spell', 'icon'. None means no signalization
+ of capital letters, 'spell' means capital letters will be spelled
+ with a syntetic voice and 'icon' means that the capital-letter icon
+ will be prepended before each capital letter.
+ scope -- see the documentation of this class.
+
+ """
+ assert value in ("none", "spell", "icon")
+ self._conn.send_command('SET', scope, 'CAP_LET_RECOGN', value)
+
+ def set_voice(self, value, scope=Scope.SELF):
+ """Set voice by a symbolic name.
+
+ Arguments:
+ value -- one of the SSIP symbolic voice names: 'MALE1' .. 'MALE3',
+ 'FEMALE1' ... 'FEMALE3', 'CHILD_MALE', 'CHILD_FEMALE'
+ scope -- see the documentation of this class.
+
+ Symbolic voice names are mapped to real synthesizer voices in the
+ configuration of the output module. Use the method
+ 'set_synthesis_voice()' if you want to work with real voices.
+
+ """
+ assert isinstance(value, str) and \
+ value.lower() in ("male1", "male2", "male3", "female1",
+ "female2", "female3", "child_male",
+ "child_female")
+ self._conn.send_command('SET', scope, 'VOICE', value)
+
+ def set_synthesis_voice(self, value, scope=Scope.SELF):
+ """Set voice by its real name.
+
+ Arguments:
+ value -- voice name as returned by 'list_synthesis_voices()'
+ scope -- see the documentation of this class.
+
+ """
+ self._conn.send_command('SET', scope, 'SYNTHESIS_VOICE', value)
+
+ def set_pause_context(self, value, scope=Scope.SELF):
+ """Set the amount of context when resuming a paused message.
+
+ Arguments:
+ value -- a positive or negative value meaning how many chunks of data
+ after or before the pause should be read when resume() is executed.
+ scope -- see the documentation of this class.
+
+ """
+ assert isinstance(value, int)
+ self._conn.send_command('SET', scope, 'PAUSE_CONTEXT', value)
+
+ def set_debug(self, val):
+ """Switch debugging on and off. When switched on,
+ debugging files will be created in the chosen destination
+ (see set_debug_destination()) for Speech Dispatcher and all
+ its running modules. All logging information will then be
+ written into these files with maximal verbosity until switched
+ off. You should always first call set_debug_destination.
+
+ The intended use of this functionality is to switch debuging
+ on for a period of time while the user will repeat the behavior
+ and then send the logs to the appropriate bug-reporting place.
+
+ Arguments:
+ val -- a boolean value determining whether debugging
+ is switched on or off
+ scope -- see the documentation of this class.
+
+ """
+ assert isinstance(val, bool)
+ if val == True:
+ ssip_val = "ON"
+ else:
+ ssip_val = "OFF"
+
+ self._conn.send_command('SET', scope.ALL, 'DEBUG', ssip_val)
+
+
+ def set_debug_destination(self, path):
+ """Set debug destination.
+
+ Arguments:
+ path -- path (string) to the directory where debuging
+ files will be created
+ scope -- see the documentation of this class.
+
+ """
+ assert isinstance(val, string)
+
+ self._conn.send_command('SET', scope.ALL, 'DEBUG_DESTINATION', val)
+
+ def block_begin(self):
+ """Begin an SSIP block.
+
+ See SSIP documentation for more details about blocks.
+
+ """
+ self._conn.send_command('BLOCK', 'BEGIN')
+
+ def block_end(self):
+ """Close an SSIP block.
+
+ See SSIP documentation for more details about blocks.
+
+ """
+ self._conn.send_command('BLOCK', 'END')
+
+ def close(self):
+ """Close the connection to Speech Dispatcher."""
+ if hasattr(self, '_conn'):
+ self._conn.close()
+ del self._conn
+
+
+class Client(SSIPClient):
+ """A DEPRECATED backwards-compatible API.
+
+ This Class is provided only for backwards compatibility with the prevoius
+ unofficial API. It will be removed in future versions. Please use either
+ 'SSIPClient' or 'Speaker' interface instead. As deprecated, the API is no
+ longer documented.
+
+ """
+ def __init__(self, name=None, client=None, **kwargs):
+ name = name or client or 'python'
+ super(Client, self).__init__(name, **kwargs)
+
+ def say(self, text, priority=Priority.MESSAGE):
+ self.set_priority(priority)
+ self.speak(text)
+
+ def char(self, char, priority=Priority.TEXT):
+ self.set_priority(priority)
+ super(Client, self).char(char)
+
+ def key(self, key, priority=Priority.TEXT):
+ self.set_priority(priority)
+ super(Client, self).key(key)
+
+ def sound_icon(self, sound_icon, priority=Priority.TEXT):
+ self.set_priority(priority)
+ super(Client, self).sound_icon(sound_icon)
+
+
+class Speaker(SSIPClient):
+ """Extended Speech Dispatcher Interface.
+
+ This class provides an extended intercace to Speech Dispatcher
+ functionality and tries to hide most of the lower level details of SSIP
+ (such as a more sophisticated handling of blocks and priorities and
+ advanced event notifications) under a more convenient API.
+
+ Please note that the API is not yet stabilized and thus is subject to
+ change! Please contact the authors if you plan using it and/or if you have
+ any suggestions.
+
+ Well, in fact this class is currently not implemented at all. It is just a
+ draft. The intention is to hide the SSIP details and provide a generic
+ interface practical for screen readers.
+
+ """
+
+
+# Deprecated but retained for backwards compatibility
+
+# This class was introduced in 0.7 but later renamed to CommunicationMethod
+class ConnectionMethod(object):
+ """Constants describing the possible methods of connection to server.
+
+ Retained for backwards compatibility but DEPRECATED. See
CommunicationMethod."""
+ UNIX_SOCKET = 'unix_socket'
+ """Unix socket communication using a filesystem path"""
+ INET_SOCKET = 'inet_socket'
+ """Inet socket communication using a host and port"""
diff --git a/src/api/python/speechd/paths.py.in
b/src/api/python/speechd/paths.py.in
new file mode 100644
index 0000000..a2a9696
--- /dev/null
+++ b/src/api/python/speechd/paths.py.in
@@ -0,0 +1 @@
+SPD_SPAWN_CMD = "@bindir@/speech-dispatcher"
diff --git a/src/api/python/speechd_config/Makefile.am
b/src/api/python/speechd_config/Makefile.am
new file mode 100644
index 0000000..8b57ad8
--- /dev/null
+++ b/src/api/python/speechd_config/Makefile.am
@@ -0,0 +1,27 @@
+## Process this file with automake to produce Makefile.in
+
+dist_snddata_DATA = test.wav
+
+dist_bin_SCRIPTS = spd-conf
+
+speechd_pythondir = $(pyexecdir)/speechd_config
+speechd_python_PYTHON = __init__.py config.py
+nodist_speechd_python_PYTHON = paths.py
+
+paths_edit = sed \
+ -e "s:address@hidden@]:$(spdconforigdir):" \
+ -e "s:address@hidden@]:$(spdconfdir):" \
+ -e "s:address@hidden@]:$(snddatadir):" \
+ -e "s:address@hidden@]:$(spddesktopconforigdir):"
+
+paths.py: Makefile
+ rm -f $@
+ srcdir=; \
+ test -f ./address@hidden || srcdir=$(srcdir)/; \
+ $(paths_edit) address@hidden > $@
+
+paths.py: $(srcdir)/paths.py.in
+
+CLEANFILES = paths.py
+
+EXTRA_DIST = paths.py.in
diff --git a/src/api/python/speechd_config/__init__.py
b/src/api/python/speechd_config/__init__.py
new file mode 100644
index 0000000..6f7eef9
--- /dev/null
+++ b/src/api/python/speechd_config/__init__.py
@@ -0,0 +1,18 @@
+# Copyright (C) 2008 Brailcom, o.p.s.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from config import *
+
diff --git a/src/api/python/speechd_config/config.py
b/src/api/python/speechd_config/config.py
new file mode 100644
index 0000000..26bdccd
--- /dev/null
+++ b/src/api/python/speechd_config/config.py
@@ -0,0 +1,924 @@
+# config.py - A simple dialog based tool for basic configuration of
+# Speech Dispatcher and problem diagnostics.
+#
+# Copyright (C) 2008, 2010 Brailcom, o.p.s.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import sys
+import os
+import shutil
+import fileinput
+import socket
+import time
+import datetime
+
+from optparse import OptionParser
+
+# Configuration and sound data paths
+import paths
+
+def report(msg):
+ """Output information messages for the user on stdout
+ and if desired, by espeak synthesis"""
+ print msg
+ if options.use_espeak_synthesis:
+ os.system("espeak \"" + msg + "\"")
+
+def input_audio_icon():
+ """Produce a sound for the event 'input requested' used in question()"""
+ if options.use_espeak_synthesis:
+ os.system("espeak \"Type in\"")
+
+def question(text, default):
+ """Ask a simple question and suggest the default value"""
+
+ while 1:
+ if isinstance(default, bool):
+ if default == True:
+ default_str = "yes"
+ else:
+ default_str = "no"
+ else:
+ default_str = str(default)
+ report(text + " ["+default_str+"] :")
+ input_audio_icon()
+
+ if not options.dont_ask:
+ str_inp = raw_input(">")
+
+ # On plain enter, return default
+ if options.dont_ask or (len(str_inp) == 0):
+ return default
+ # If a value was typed, check it and convert it
+ elif isinstance(default, bool):
+ if str_inp in ["yes","y", "Y", "true","t", "1"]:
+ return True
+ elif str_inp in ["no", "n", "N", "false", "f", "0"]:
+ return False
+ else:
+ report ("Unknown answer (type 'yes' or 'no')")
+ continue
+ elif isinstance(default, int):
+ return int(str_inp)
+ elif isinstance(default, str):
+ return str_inp
+ else:
+ raise TypeError("Invalid type for the default value")
+
+def question_with_suggested_answers(text, default, suggest):
+ """Ask a question with suggested answers. If the answer typed is not
+ in 'suggest', the user is notified and given an opportunity to correct
+ his choice"""
+
+ reply = question(text, default)
+ while reply not in suggest:
+ report("""The value you have chosen is not among the suggested values.
+You have chosen '%s'.""" % reply)
+ report("The suggested values are " + str(suggest))
+ correct = question("Do you want to correct your answer?", True)
+ if correct == True:
+ reply = question(text, default)
+ else:
+ return reply
+ return reply
+
+def question_with_required_answers(text, default, required):
+ """Ask a question and repeat it until the answer typed in is in
'required'"""
+
+ reply = question(text, default)
+ while reply not in required:
+ report("You have chosen '%s'. Please choose one of %s" % (reply,
str(required)))
+ reply = question(text, default)
+ return reply
+
+class Options(object):
+ """Configuration for spdconf"""
+
+ _conf_options = \
+ {
+ 'create_user_configuration':
+ {
+ 'descr' : "Create Speech Dispatcher configuration for the given
user",
+ 'doc' : None,
+ 'type' : bool,
+ 'default' : False,
+ 'command_line' : ('-u', '--create-user-conf'),
+ },
+ 'config_basic_settings_user':
+ {
+ 'descr' : "Configure basic settings in user configuration",
+ 'doc' : None,
+ 'type' : bool,
+ 'default' : False,
+ 'command_line' : ('-c', '--config-basic-settings-user'),
+ },
+ 'config_basic_settings_system':
+ {
+ 'descr' : "Configure basic settings in system-wide configuration",
+ 'doc' : None,
+ 'type' : bool,
+ 'default' : False,
+ 'command_line' : ('-C', '--config-basic-settings-system'),
+ },
+ 'diagnostics':
+ {
+ 'descr' : "Diagnose problems with the current setup",
+ 'doc' : None,
+ 'type' : bool,
+ 'default' : False,
+ 'command_line' : ('-d', '--diagnostics'),
+ },
+ 'test_spd_say':
+ {
+ 'descr' : "Test connection to Speech Dispatcher using spd-say",
+ 'doc' : None,
+ 'type' : bool,
+ 'default' : False,
+ 'command_line' : ('-s', '--test-spd-say'),
+ },
+ 'test_festival':
+ {
+ 'descr' : "Test whether Festival works as a server",
+ 'doc' : None,
+ 'type' : bool,
+ 'default' : False,
+ 'command_line' : ('', '--test-festival'),
+ },
+ 'test_espeak':
+ {
+ 'descr' : "Test whether Espeak works as a standalone binary",
+ 'doc' : None,
+ 'type' : bool,
+ 'default' : False,
+ 'command_line' : ('', '--test-espeak'),
+ },
+ 'test_alsa':
+ {
+ 'descr' : "Test ALSA audio",
+ 'doc' : None,
+ 'type' : bool,
+ 'default' : False,
+ 'command_line' : ('', '--test-alsa'),
+ },
+
+ 'test_pulse':
+ {
+ 'descr' : "Test Pulse Audio",
+ 'doc' : None,
+ 'type' : bool,
+ 'default' : False,
+ 'command_line' : ('', '--test-pulse'),
+ },
+
+ 'use_espeak_synthesis':
+ {
+ 'descr' : "Use espeak to synthesize messages",
+ 'doc' : None,
+ 'type' : bool,
+ 'default' : False,
+ 'command_line' : ('-e', '--espeak'),
+ },
+ 'dont_ask':
+ {
+ 'descr' : "Do not ask any questions, allways use default values",
+ 'doc' : None,
+ 'type' : bool,
+ 'default' : False,
+ 'command_line' : ('-n', '--dont-ask'),
+ },
+ 'debug':
+ {
+ 'descr' : "Debug a problem and generate a report",
+ 'doc' : None,
+ 'type' : bool,
+ 'default' : False,
+ 'command_line' : ('-D', '--debug'),
+ },
+ }
+
+ def __init__(self):
+ usage = """%prog [options]
+A simple dialog based tool for basic configuration of Speech Dispatcher
+and problem diagnostics."""
+ self.cmdline_parser = OptionParser(usage)
+
+ for option, definition in self._conf_options.iteritems():
+ # Set object attributes to default values
+ def_val = definition.get('default', None)
+ setattr(self, option, def_val)
+
+ # Fill in the cmdline_parser object
+ if definition.has_key('command_line'):
+ descr = definition.get('descr', None)
+ type = definition.get('type', None)
+
+ if definition.has_key('arg_map'):
+ type, map = definition['arg_map']
+ if type == str:
+ type_str = 'string'
+ elif type == int:
+ type_str = 'int'
+ elif type == float:
+ type_str = 'float'
+ elif type == bool:
+ type_str = None
+ else:
+ raise TypeError("Unknown type")
+
+ if type != bool:
+ self.cmdline_parser.add_option(type=type_str, dest=option,
+ help=descr,
+ *definition['command_line'])
+ else: # type == bool
+ self.cmdline_parser.add_option(action="store_true",
dest=option,
+ help=descr,
+ *definition['command_line'])
+
+ # Set options according to command line flags
+ (cmdline_options, args) = self.cmdline_parser.parse_args()
+
+ for option, definition in self._conf_options.iteritems():
+ val = getattr(cmdline_options, option, None)
+ if val != None:
+ if definition.has_key('arg_map'):
+ former_type, map = definition['arg_map']
+ try:
+ val = map[val]
+ except KeyError:
+ raise ValueError("Invalid option value: " +
str(val))
+
+ setattr(self, option, val)
+
+ #if len(args) != 0:
+ # raise ValueError("This command takes no positional arguments
(without - or -- prefix)")
+
+class Tests:
+ """Tests of functionality of Speech Dispatcher and its dependencies
+ and methods for determination of proper paths"""
+
+ def __init__(self):
+ self.festival_socket = None
+
+ def user_speechd_dir(self):
+ """Return user Speech Dispatcher configuration and logging directory"""
+ return os.path.expanduser(os.path.join('~', '.speech-dispatcher'))
+
+ def user_speechd_dir_exists(self):
+ """Determine whether user speechd directory exists"""
+ return os.path.exists(self.user_speechd_dir())
+
+ def user_conf_dir(self):
+ """Return user configuration directory"""
+ return os.path.join(self.user_speechd_dir(), "conf")
+
+ def system_conf_dir(self):
+ """Determine system configuration directory"""
+ return paths.SPD_CONF_PATH
+
+ def user_conf_dir_exists(self):
+ """Determine whether user configuration directory exists"""
+ return os.path.exists(self.user_conf_dir())
+
+ def festival_connect(self, host="localhost", port=1314):
+ """
+ Try to connect to festival and determine whether it is possible.
+ On success self.festival_socket is initialized with the openned socket.
+ """
+ self.festival_socket = socket.socket(socket.AF_INET,
socket.SOCK_STREAM)
+ try:
+ self.festival_socket.connect((socket.gethostbyname(host), port))
+ except socket.error, (num, reson):
+ report("""ERROR: It was not possible to connect to Festival on the
+given host and port. Connection failed with error %d : %s .""" % (num, reson))
+ report("""Hint: Most likely, your Festival server is not running
now
+or not at the default port %d.
+
+Try /etc/init.d/festival start or run 'festival --server' from the command
line.""" % port)
+ return False
+ return True
+
+ def festival_with_freebsoft_utils(self):
+ """Test whether festival works and contains working
festival-freebsoft-utils.
+ """
+ if not self.festival_socket:
+ if not self.festival_connect():
+ return False
+ self.festival_socket.send("(require 'speech-dispatcher)\n")
+ reply = self.festival_socket.recv(1024)
+ if "LP" in reply:
+ report("Festival contains freebsoft-utils.")
+ return True
+ else:
+ report("""ERROR: Your Festival server is working but it doesn't
seem
+to load festival-freebsoft-utils. You need to install festival-freebsoft-utils
+to be able to use Festival with Speech Dispatcher.""")
+ return False
+
+ def python_speechd_in_path(self):
+ """Try whether python speechd library is importable"""
+ try:
+ import speechd
+ except:
+ report("""Python can't find the Speech Dispatcher library.
+Is it installed? This won't prevent Speech Dispatcher to work, but no
+Python applications like Orca will be able to use it.
+Search for package like python-speechd, download and install it""")
+ return False
+ return True
+
+ def audio_try_play(self, type):
+ """Try to play a sound through the standard playback utility for the
+ given audio method."""
+ wavfile = os.path.join(paths.SPD_SOUND_DATA_PATH,"test.wav")
+
+ if type == 'alsa':
+ cmd = "aplay" + " " + wavfile
+ elif type == 'pulse':
+ cmd = "paplay" + " " + wavfile
+ else:
+ raise NotImplementedError("Test for this audio system is not
implemented")
+
+ try:
+ ret = os.system(cmd)
+ except:
+ report("""Can't execute the %s command, this audio output might
not be available
+on your system, but it might also be a false warning. Please make
+sure your audio is working.""")
+ reply = question("Are you sure that %s audio is working?" % type,
False)
+ return reply
+
+ if ret:
+ report("Can't play audio via\n %s" % cmd)
+ report("""Your audio doesn't seem to work, please fix audio first
or choose
+a different method.""")
+ return False
+
+
+ reply = question("Did you hear the sound?", True)
+
+ if not reply:
+ report("""Please examine the above output from the sound playback
+utility. If everything seems right, are you sure your audio is loud enough and
+not muted in the mixer? Please fix your audio system first or choose a
different
+audio output method in configuration.""")
+ return False
+ else:
+ report("Audio output '%s' works" % type)
+ return True
+
+ def test_spd_say(self):
+ """Test Speech Dispatcher using spd_say"""
+
+ report("Testing Speech Dispatcher using spd_say")
+
+ while True:
+ try:
+ ret = os.system("spd-say -P important \"Speech Dispatcher
works\"")
+ except:
+ report("""Can't execute the spd-say binary,
+it is very likely that Speech Dispatcher is not installed.""")
+ return False
+ hearing_test = question("Did you hear the message about Speech
Dispatcher working?", True)
+ if hearing_test:
+ report("Speech Dispatcher is installed and working!")
+ return True
+ else:
+ report("Speech Dispatcher is installed but there is some
problem")
+ return False
+
+ def test_festival(self):
+ """Test whether Festival works as a server"""
+ report("Testing whether Festival works as a server")
+
+ ret = self.festival_with_freebsoft_utils()
+ if not ret:
+ report("Festival server is not working.")
+ return False
+ else:
+ report("Festival server seems to work correctly")
+ return True
+
+ def test_espeak(self):
+ """Test the espeak utility"""
+
+ report("Testing whether Espeak works")
+
+ while True:
+ try:
+ os.system("espeak \"Espeak seems to work\"")
+ except:
+ report("""Can't execute the espeak binary, it is likely that
espeak
+is not installed.""")
+ return False
+
+ report("Espeak is installed")
+ return True
+
+ def test_alsa(self):
+ """Test ALSA sound output"""
+ report("Testing ALSA sound output")
+ return self.audio_try_play(type='alsa')
+
+ def test_pulse(self):
+ """Test Pulse Audio sound output"""
+ report("Testing PULSE sound output")
+ return self.audio_try_play(type='pulse')
+
+ def diagnostics(self, speechd_running = True, output_modules=[],
audio_output=[]):
+
+ """Perform a complete diagnostics"""
+ working_modules = []
+ working_audio = []
+
+ if speechd_running:
+ # Test whether Speech Dispatcher works
+ if self.test_spd_say():
+ spd_say_working = True
+ skip = question("Speech Dispatcher works. Do you want to skip
other tests?",
+ True)
+ if skip:
+ return {'spd_say_working': True}
+ else:
+ spd_say_working = False
+ else:
+ spd_say_working = False
+
+ if not spd_say_working:
+ if not question("""
+Speech Dispatcher isn't running or we can't connect to it (see above),
+do you want to proceed with other tests? (They can help to determine
+what is wrong)""", True):
+ return {'spd_say_working': False}
+
+ def decide_to_test(identifier, name, listing):
+ """"Ask the user whether to test a specific capability"""
+ if ((identifier in listing)
+ or (not len(listing)
+ and question("Do you want to test the %s now?" % name,
True))):
+ return True
+ else:
+ return False
+
+ if decide_to_test('festival', "Festival synthesizer", output_modules):
+ if self.test_festival():
+ working_modules += ["festival"]
+
+ if decide_to_test('espeak', "Espeak synthesizer", output_modules):
+ if self.test_espeak():
+ working_modules += ["espeak"]
+
+ if decide_to_test('alsa', "ALSA sound system", audio_output):
+ if self.test_alsa():
+ working_audio += ["alsa"]
+
+ if decide_to_test('pulse', "Pulse Audio sound system", audio_output):
+ if self.test_pulse():
+ working_audio += ["pulse"]
+
+ report("Testing whether Python Speech Dispatcher library is in path
and importable")
+ python_speechd_working = test.python_speechd_in_path()
+
+ return {'spd_say_working': spd_say_working,
+ 'audio': working_audio,
+ 'synthesizers': working_modules,
+ 'python_speechd' : python_speechd_working}
+
+ def write_diagnostics_results(self, results):
+ """Write out diagnostics results using report()"""
+
+ report("""
+
+Diagnostics results:""")
+ if results.has_key('spd_say_working'):
+ if results['spd_say_working']:
+ report("Speech Dispatcher is working")
+ else:
+ report("Speech Dispatcher not working through spd-say")
+ if results.has_key('synthesizers'):
+ report("Synthesizers that were tested and seem to work: %s" %
+ str(results['synthesizers']))
+ if results.has_key('audio'):
+ report("Audio systems that were tested and seem to work: %s" %
+ str(results['audio']))
+ if results.has_key('python_speechd'):
+ if(results['python_speechd']):
+ report("Python Speech Dispatcher module is importable")
+ else:
+ report("""Python Speech Dispatcher module not importable.
+Either not installed or not in path.""")
+ report("End of diagnostics results")
+
+ def user_configuration_seems_complete(self):
+ """Decide if the user configuration seems reasonably complete"""
+ if not os.path.exists(os.path.join(self.user_conf_dir(),
"speechd.conf")):
+ return False
+
+ if not len(os.listdir(self.user_conf_dir())) > 2:
+ return False
+
+ if not os.path.exists(os.path.join(self.user_conf_dir(), "modules")):
+ return False
+
+ if not os.path.exists(os.path.join(self.user_conf_dir(), "clients")):
+ return False
+
+ return True
+
+ def debug_and_report(self, type = None):
+ """Start Speech Dispatcher in debugging mode, collect the debugging
output
+ and offer to send it to the developers"""
+
+ report("Starting collecting debugging output, configuration and
logfiles")
+
+ if not type:
+ type = question_with_required_answers("""
+Do you want to debug 'system' or 'user' Speech Dispatcher?""",
+ 'user', ['user', 'system'])
+
+ # Try to kill running Speech Dispatcher
+ reply = question("""It is necessary to kill the currently running
Speech Dispatcher
+processes. Do you want to do it now?""", True)
+ if reply:
+ os.system("killall speech-dispatcher")
+ else:
+ report("""
+You decided not to kill running Speech Dispatcher processes.
+Please make sure your Speech Dispatcher is not running now.""")
+ reply = question("Is your Speech Dispatcher not running now?",
True)
+ if not reply:
+ report("Can't continue, please stop your Speech Dispatcher and
try again")
+
+ time.sleep(2)
+
+ # All debugging files are written to TMPDIR/speech-dispatcher/
+ if os.environ.has_key('TMPDIR'):
+ tmpdir = os.environ['TMPDIR']
+ else:
+ tmpdir = "/tmp/"
+ debugdir_path = os.path.join(tmpdir, "speechd-debug")
+ date = datetime.date.today()
+ debugarchive_path = os.path.join(tmpdir,
"speechd-debug-%d-%d-%d.tar.gz" %
+ (date.day, date.month, date.year))
+
+ # Start Speech Dispatcher with debugging enabled
+ if type == 'user':
+ report("Speech Dispatcher will be started now in debugging mode")
+ speechd_started = not os.system("speech-dispatcher -D")
+ configure_directory = test.user_conf_dir()
+ else:
+ report("Warning: You must be root or under sudo to do this.")
+ report("""
+Please start your system Speech Dispatcher now with parameter '-D'""")
+ reply = question("Is your Speech Dispatcher running now?", True)
+ if reply:
+ speechd_started = True
+ else:
+ report("Can't continue")
+ configure_directory = test.system_conf_dir()
+ time.sleep(2)
+
+ if not speechd_started:
+ reply = question("Speech Dispatcher failed to start, continuing
anyway")
+
+ report("Trying to speak some messages")
+ ret = os.system("spd-say \"Speech Dispatcher debugging 1\"")
+ if not ret:
+ os.system("spd-say \"Speech Dispatcher debugging 2\"")
+ os.system("spd-say \"Speech Dispatcher debugging 3\"")
+ else:
+ report("Can't test Speech Dispatcher connection, can't connect")
+
+ report("Please wait (about 5 seconds)")
+ time.sleep(5)
+
+ report("Collecting debugging output and your configuration
information")
+
+ os.system("umask 077")
+ os.system("tar -cz %s %s > %s" %
+ (debugdir_path, configure_directory, debugarchive_path))
+ os.system("killall speech-dispatcher")
+ os.system("rm -rf %s" % debugdir_path)
+
+ report("""
+Please send %s to speechd at bugs.freebsoft.org with
+a short description of what you did. We will get in touch with you soon
+and suggest a solution.""" % debugarchive_path)
+
+test = Tests()
+
+class Configure:
+
+ """Setup user configuration and/or set basic options in user/system
configuration"""
+
+ default_output_module = None
+ default_language = None
+ default_audio_method = None
+
+ def remove_user_configuration(self):
+ """Remove user configuration tree"""
+ shutil.rmtree(test.user_conf_dir())
+
+ def options_substitute(self, configfile, options):
+ """Substitute the given options with given values.
+
+ Arguments:
+ configfile -- the file path of the configuration file as a string
+ options -- a list of tuples (option_name, value)"""
+
+ # Parse config file in-place and replace the desired options+values
+ for line in fileinput.input(configfile, inplace=True, backup=".bak"):
+ # Check if the current line contains any of the desired options
+ for opt, value in options.iteritems():
+ if opt in line:
+ # Now count unknown words and try to judge if this is
+ # real configuration or just a comment
+ unknown = 0
+ for word in line.split():
+ if word =='#' or word == '\t':
+ continue
+ elif word == opt:
+ # If a foreign word went before our option
identifier,
+ # we are not in code but in comments
+ if unknown != 0:
+ unknown = 2
+ break
+ else:
+ unknown += 1
+
+ # Only consider the line as the actual code when the
keyword
+ # is followed by exactly one word value. Otherwise
consider this
+ # line as plain comment and leave intact
+ if unknown == 1:
+ # Convert value into string representation in spd_val
+ if isinstance(value, bool):
+ if value == True:
+ spd_val = "1"
+ elif value == False:
+ spd_val = "2"
+ elif isinstance(value, int):
+ spd_val = str(value)
+ else:
+ spd_val = str(value)
+
+ print opt + " " + spd_val
+ break
+
+ else:
+ print line,
+
+ def create_user_configuration(self):
+ """Create user configuration in the standard location"""
+
+ # Ask before touching things that we do not have to!
+ if test.user_speechd_dir_exists():
+ if test.user_conf_dir_exists():
+ if test.user_configuration_seems_complete():
+ reply = question(
+ """User configuration already exists.
+Do you want to rewrite it with a new one?""", False)
+ if reply == False:
+ report("Keeping configuration intact and continuing
with settings.")
+ return
+ else:
+ self.remove_user_configuration()
+ else:
+ reply = question(
+ """User configuration already exists, but it seems to
be incomplete.
+Do you want to keep it?""", False)
+ if reply == False:
+ self.remove_user_configuration()
+ else:
+ report("Keeping configuration intact and aborting.")
+ return
+
+ # TODO: Check for permissions on logfiles and pid
+ else:
+ report("Creating " + test.user_speechd_dir())
+ os.mkdir(test.user_speechd_dir())
+
+ # Copy the original intact configuration files
+ # creating a conf/ subdirectory
+ shutil.copytree(paths.SPD_CONF_ORIG_PATH, test.user_conf_dir())
+
+ report("User configuration created in %s" % test.user_conf_dir())
+
+ def configure_basic_settings(self, type='user'):
+ """Ask for basic settings and rewrite them in the configuration file"""
+
+ if type == 'user':
+ report("Configuring user settings for Speech Dispatcher")
+ elif type == 'system':
+ report("Warning: You must be root or under sudo to do this.")
+ report("Configuring system settings for Speech Dispatcher")
+ else:
+ raise ValueError("Invalid configuration type")
+
+ # Now determine the most important config option
+ self.default_output_module = question_with_suggested_answers(
+ "Default output module",
+ "espeak",
+ ["espeak", "flite", "festival", "cicero", "ibmtts"])
+
+ self.default_language = question(
+ "Default language (two-letter iso language code like \"en\" or
\"cs\")",
+ "en")
+
+ self.default_audio_method = question_with_suggested_answers(
+ "Default audio output method",
+ "pulse",
+ ["pulse", "alsa", "oss", "pulse,alsa"])
+
+ self.default_speech_rate = question(
+ "Default speech rate (on the scale of -100..100, 0 is default, 50
is faster, -50 is slower)",
+ "0")
+
+ self.default_speech_pitch = question(
+ "Default speech pitch (on the scale of -100..100, 0 is default, 50
is higher, -50 is lower)",
+ "0")
+
+ # Substitute given configuration options
+ if type == 'user':
+ configfile = os.path.join(test.user_conf_dir(), "speechd.conf")
+ elif type == 'system':
+ configfile = os.path.join(test.system_conf_dir(), "speechd.conf")
+
+ self.options_substitute(configfile,
+ {"DefaultModule": self.default_output_module,
+ "DefaultLanguage": self.default_language,
+ "AudioOutputMethod":
self.default_audio_method,
+ "DefaultRate": self.default_speech_rate,
+ "DefaultPitch": self.default_speech_pitch,
+ "DefaultLanguage": self.default_language,
+ })
+ if type == 'user':
+ self.setup_autostart = question(
+ """Do you want to have Speech Dispatcher automatically started
from ~/.config/autostart ?
+This is usually not necessary, most applications will start Speech Dispatcher
automatically.""",
+ False)
+ if self.setup_autostart:
+ os.system("""cp %s ~/.config/autostart/""" %
os.path.join(paths.SPD_DESKTOP_CONF_PATH,
+
"speechd.desktop"))
+
+ report("""
+Configuration written to %s
+Basic configuration now complete. You might still need to fine tune it by
+manually editing the configuration file above. Especially if you need to
+use special audio settings, non-standard synthesizer ports etc.""" %
configfile)
+
+ def speechd_start_user(self):
+ """Start Speech Dispatcher in user-mode"""
+
+ report("Starting Speech Dispatcher in user-mode")
+
+ err = os.system("speech-dispatcher")
+ if err:
+ report("Can't start Speech Dispatcher. Exited with status %d" %
err)
+ reply = question("""Perhaps this is because your Speech Dispatcher
is already running.
+Do you want to kill all running Speech Dispatchers and try again?""", True)
+ if reply:
+ os.system("killall speech-dispatcher")
+ err = os.system("speech-dispatcher")
+ if err:
+ report("Can't start Speech Dispatcher")
+ return False
+ else:
+ return False
+ return True
+
+ def speechd_start_system(self):
+ """Start Speech Dispatcher in system-mode"""
+
+ report("Warning: You must be root or under sudo to do this.")
+ report("Starting Speech Dispatcher in system-mode")
+
+ reply = question("Is your system using an
/etc/init.d/speech-dispatcher script?",
+ True)
+ if reply:
+ report("Stopping Speech Dispatcher in case any is running already")
+ os.system("/etc/init.d/speech-dispatcher stop")
+ report("Starting Speech Dispatcher via
/etc/init.d/speech-dispatcher")
+ ret = os.system("/etc/init.d/speech-dispatcher start")
+ if ret:
+ report("Can't start Speech Dispatcher. Exited with status %d"
% ret)
+ return False
+ else:
+ report("""Do not know how to start system Speech Dispatcher,
+you have to start it manually to continue.""")
+ reply = question("Have you started Speech Dispatcher now?", True)
+ if not reply:
+ report("Can't continue")
+ return False
+ return True
+
+ def complete_config(self):
+ """Create a complete configuration, run diagnosis and if necessary,
debugging"""
+
+ speechd_type = question_with_required_answers(
+ "Do you want to create/setup a 'user' or 'system' configuration",
+ 'user', ['user', 'system'])
+
+ if speechd_type == 'user':
+ self.create_user_configuration()
+ self.configure_basic_settings(type='user')
+ elif speechd_type == 'system':
+ self.configure_basic_settings(type='system')
+ else:
+ raise ValueError("Invalid configuration type")
+
+ reply = question("Do you want to start/restart Speech Dispatcher now
and run some tests?", True)
+ if not reply:
+ report("Your configuration is now done but not tested")
+ return
+ else:
+ if speechd_type == 'user':
+ started = self.speechd_start_user()
+ elif speechd_type == 'system':
+ started = self.speechd_start_system()
+
+ if not started:
+ report("Your Speech Dispatcher is not running")
+
+ result = test.diagnostics(speechd_running = started,
+ audio_output=[self.default_audio_method],
+ output_modules=[self.default_output_module])
+ test.write_diagnostics_results(result)
+
+ if not started:
+ reply = question("Do you want to run debugging now and send a
request for help to the developers?",
+ False)
+ if reply:
+ test.debug_and_report(type=speechd_type)
+
+
+# Basic objects
+options = Options()
+configure = Configure()
+test = Tests()
+
+
+def main():
+
+ report("\nSpeech Dispatcher configuration tool\n")
+
+ if options.create_user_configuration:
+ # Check for and/or create basic user configuration
+ configure.create_user_configuration()
+ reply = question("Do you want to continue with basic settings?", True)
+ if reply:
+ configure.configure_basic_settings(type='user')
+ elif options.config_basic_settings_user:
+ configure.configure_basic_settings(type='user')
+
+ elif options.config_basic_settings_system:
+ configure.configure_basic_settings(type='system')
+
+ elif options.test_festival:
+ test.test_festival()
+
+ elif options.test_spd_say:
+ test.test_spd_say()
+
+ elif options.test_espeak:
+ test.test_espeak()
+
+ elif options.test_alsa:
+ test.audio_try_play(type='alsa')
+
+ elif options.test_pulse:
+ test.audio_try_play(type='pulse')
+
+ elif options.diagnostics:
+ ret = test.diagnostics()
+ test.write_diagnostics_results(ret)
+
+ elif options.debug:
+ test.debug_and_report()
+
+ else:
+ reply = question("Do you want to setup a completely new
configuration?", True)
+ if reply:
+ configure.complete_config()
+ else:
+ reply = question("Do you want to run diagnosis of problems?", True)
+ if reply:
+ ret=test.diagnostics()
+ test.write_diagnostics_results(ret)
+ else:
+ report("""Please run this command again and select what you
want to do
+or read the quick help available through '-h' or '--help'.""")
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/src/api/python/speechd_config/paths.py.in
b/src/api/python/speechd_config/paths.py.in
new file mode 100644
index 0000000..326e954
--- /dev/null
+++ b/src/api/python/speechd_config/paths.py.in
@@ -0,0 +1,4 @@
+SPD_CONF_ORIG_PATH="@spdconforigdir@"
+SPD_CONF_PATH="@spdconfdir@"
+SPD_SOUND_DATA_PATH="@snddatadir@"
+SPD_DESKTOP_CONF_PATH="@spddesktopconforigdir@"
diff --git a/src/api/python/speechd_config/spd-conf
b/src/api/python/speechd_config/spd-conf
new file mode 100644
index 0000000..3cc90b5
--- /dev/null
+++ b/src/api/python/speechd_config/spd-conf
@@ -0,0 +1,10 @@
+#!/usr/bin/python
+
+# Helper script to be put in /usr/bin/ or a similar location
+# calling the appropriate python tool
+
+import speechd_config
+
+if __name__=='__main__':
+ import sys
+ sys.exit(speechd_config.main())
diff --git a/src/api/python/speechd_config/speechd.desktop
b/src/api/python/speechd_config/speechd.desktop
new file mode 100644
index 0000000..4a9e8d5
--- /dev/null
+++ b/src/api/python/speechd_config/speechd.desktop
@@ -0,0 +1,11 @@
+
+[Desktop Entry]
+Type=Application
+Encoding=UTF-8
+Version=1.0
+Name=Speech Dispatcher
+Comment=Interface to Text to Speech services
+TryExec=speech-dispatcher
+Exec=speech-dispatcher
+X-GNOME-Autostart-enabled=true
+X-GNOME-Autostart-Phase=Initialization
diff --git a/src/api/python/speechd_config/test.wav
b/src/api/python/speechd_config/test.wav
new file mode 100644
index
0000000000000000000000000000000000000000..1c49b4abdd29f5dab872f96691d2c67d2ecb545a
GIT binary patch
literal 17410
zcmW+;1$0wO*PXc%Ph*u-ptQKVJLTa#+~MKw?(XjHZVz{VxVu|{QmS!J?wy(ceE-T?
z2}yJB%pBQ!pEDEMH*eA;l7>*1hMoT!G;address@hidden;d
zM@>--UBvZf>BPkqr;TwF+ at Ji#9U$Y`DSS;{jr*b%Xa`Qi%XL?kry|P8NcW=rx1k{U
zN(SiSqUvyQxRk=k*`&VcJHLo+l1t(5^2x9$)X_aOIyEDd=lOX7s~dSr1QdZ*!CrJd
zJuU4BRUpsR)address@hidden<uth<H6`gsb}L}2p^PD%2LE6uVb}YD8Pc%
z&@)v at qtP^5KbtEaYGi+$?ar^|`V?w`dkN2R0W?v#N?tpUd*=A|=b_yGwlTRj+d<q!
zzr at fpxn!|HF$?s5!yMHGVUzN-da_Ue?ZuDyx4KrQ6~=Yy015g$*(-jLUmvsA2PX2*
z^~%^KF+}%R>TCNa=U!$obF59{9_M~3*_Fc-&7w{gJen}scs_c(;SVNgfuLtUf~O+0
z!zbxZZMOcUCY5g|&C5^wHv9XjjB|DkHR)eiJ0up4<address@hidden&R;`Zat|GfYBEul1F
zo4UScTEgJMEn;7r%UNXcC at RO77fRFjk;0Lg{t5J!`hu>jYA|;*oR+!cWAD$;e>Qb)
z!40A<1^+HkJ?0L!x*q4I{Q3B2hrOQXxVu?oyke=MU(|+_1<8}l529nEzM>esPtind
z$EKih(wxW#p}uCRY88s31AJY6Pxw_n`=~QTKB9dSKOkv}^^^W*xVH0weX*^tbDr<G
zx2X3SdB)lKr7>et8^(?`{ADP`m61NNX5uHjn8lED{;6^sMYh^5&Se86v;E|+F&Vw{
z_65dspN#brM<lec?By=HFWHUuCHd!^&%KMB0k4QkaJ97i3k6b=V)A0nM8%;`q$J-~
zn8gGZ4o?o3<5E at e+Odi}+&BCnuYX3fj2^a}kVklJDUvwN>efl&#m-jtK;AyvEB7du
z(orRRkgKNP;@6g#SfG9E{pcQw1k#!I;EUjnlqVg6ufrMiw(wMRp`&z>zeDcq-#v2A
z`YW<d`bP=gq$sPP8sabGoM5Y!YqZz&=pDOUugD4Rv$lImTH#f(y<*1eTjE5z6F1^-
z5Ra at address@hidden|e?Ivv|fNTWY93*T=LsAuHjA`3-92OL0kgdvi)VX1Mk^
zh6KN%&f>5bUtx7pY^*)Hin=QbvO?llyqKIOKZ7(>7&TTs)!b9PQudL9wj;k$md!m!
zTBB?p{V?7b-%_iMBzxZHM`bO^Y3zJypX}Ns8}KUS{RA#~qot!st=)!3${DN|-;q8h
zv%+V5U&F_7hAN;E)mS_f5OUR-t8D#)6H%uqPu$nU0&xy;N2rFoOz!k7y}h7&zN>!t
z1is3 at Hg!lXU%(#o%raN=0~cZqgu2`?a#JoGIPK4tjtiSq!<7ovblS#Q>rd~jCGNDy
z01=z_#GSUb*O!$4_Fl@@WU|bj`Q!52I%2{H=`pdB^-}V%xa{bLhAHAob_m}TU*gfY
zA88UgAsIMHwLv{ovrsu9GRj^w+v*q^x`!6(m&F{2Zxg3e&JPdpB;@|}`)Kyrd_Mn?
zXO~<N71nPnxG6C<address@hidden>-^B{98+wl1;hEulI!ZjHdZsL<+K<|K$K({v`RaCs
zhH%^UZ_H0)TI+j|#r|W??b%hbi`%npyPWeP9yVAJ8-KS at vUN;MU1K%H3Px~mMK}Ji
zPysazpOLoWXw?tZZS_T^MgGe%G3!8nH($DRR%tb?ur#n_36(;7y~}Jr|HwJTY&G&W
zyS|6_l6{)4N&h9}M5`=0nq%A at HbJZ`Hs`PLTP1g>F}cp)Ri033HSPI&fq!%JvdTMG
z1}>p;x}xU1SZb(&gwPsK<GhkNTkX^AryYGm>2Ss1SaYEX)~Ye at jrEi-*h=oRf^zS8
zLPrGu3Ww=j(W=y`vy{!qWoNIf)44C*&BMjTRJ~;WVkxc|9lq=>WV`vNP;PO%Eq|Kl
zm$a1D)if>eE$LxQ>F9F$AioLSSL{;U6qqndE*q{$=J7q16IHJ?H^h74knLB_SLeXM
zcy>hH*^m at -%Fq^-4^DM=&25)8I{%!lxTAWoz5Jf(O~sQI#oRXkGF(*dWD!(Jv6nwB
z5VR}YIFihW&|KkFI+XviQ=Yjw`uriDpP`d{G3`rJO3WH{SY972<TmH+%s=T2InH>u
zN+*e15w>!P=9saTolzwe<q#FTVmINZaE+)UTjY)05yfid2z9DBG7 at dKWS6q9@)nho
z6=ig5ECr1&ye>S at x7{`}H)yvxemSM!c{$7~8}B8ai#=!2no>2J at f=)5LHV#S1uuxq
zkA!GZ at qnVcs<Wy(s^%rx4Rgo3T!B?sp>`P_SZ=7s$(rB^S9<P{{C>_Wj<Md_QhhR6
zQP`RvUm|*s`IUZxc$&=+`iejJ9zqB5B-~f-z@;gYlvsUE(83J+We>2Q^A?xCi*K}g
z%L2m at ydpf-_tEw=x2+ at 1S=^Nvx+_y=Gn7fJ7~8^f!uU%)7ca(13J0Gr^v4?_$0Jr&
zSlpm!psJ)QgNA!&=e*3#bTtbe!ASkeFxIkEwNS1fjB at wO%gcY|Eam*+ohA(<+Z4~^
z`ozDov^8(j_ZDBU-9j(%C(LjHQAAeDW4HzijZ#uw6$(Vg+Qpooc8hP2{7Ss2eP%9Y
z*oe=D&-?1y+vL4+Tz0N=^$R)V!swOZWWueOka?i-q<SLWiS>#MK2PY5FGt=*O0WcR
zwjx!fQx!vdy;4rEye94y!CUx;YPCUasjpft4-9s2f6eP|OLi at A*6<ybmXQOBk#VoB
zy)9X$M*1 at 1OSVj?EWY7u3d2c>$VqtwS3r at ac%#}WBt(we7vwZ^wD)a-87|X~F`tVX
zh#!Z4_?Fx6<<)kYT)$oGLo&>8ogpEiZp>CQZ|tk?gg0Y{_=JBaG{W~Hjz}G*7kers
z<tHVNE_sLN{><C%ej0p)FRPjwR+_!a4f3?$GI!JbTei`zbmt7;18FbWqd;-htakGP
zlTW7-U$e1-QoO{+2t!Ey$US)iCyAF8=Tr*?bHs0t&OPT??K>mi6#Hr8%<ZF^;!ojd
zf4049{yyh0R~7fIkeXCL{}`6V{})}}e85mfT?enlZ^d)`O`#nA9<fM`m|du&c&*%~
z%wylYpK}N2OKxN6BVMeEM5US!DL2ZygOA(~@@v_TxJtOb_;MgqPALw>?ueUhE@;}J
address@hidden;)lZ0xG<0mrE$sZ at dn=z7>uK+qvh|5LFWlH)
z&9N_^cRg~gar;9>NNrTqAY0R-pP14NPE`rK3f~ZS^E(9t&WTi#+OlUt0mTtzPh|#+
z^)=7?p5NNjHe|!2RF|U8n+7WP%FlypPbu3eyTZM|Rno7K?~r4Pim at y<!F<!0rn at Di
address@hidden|{PJY(t~g%NLsdnvM3y>Y@|HT2{fHb9P1^pZ8Tt^S;mQ8B4zsPd
ztFYVSE*!2v8l#_4L#)><y-ePyb1FSvhtG(U_yvLw{fcaq{8TM+in)q$s&cH4cVvD&
z+i-7O=rrD;e5;>qs-*ZKZw^2AY_{JDO%0#7agHf at 366`rV7X9mbD{C(*LtU>4?Tjf
zstO1$L`lbBdT671c+3+EF><ODPwbB=={W0f{_W5kVYD(<-9_ip_~iLPgO{W?{IT`-
z*dOa6osFZ7nWa}&?Uwj9&K;93RHx%O5gm-=awF9hwbk at RHJ|xjp>$VUcY-U{zSRD=
zyRg?HpW!``!>o-M>u#M at address@hidden>Q+W?PdJw}J at G(v4PBxlRyoTO
zi2Y_r7uI+>address@hidden|ur)Yn^U<AQjG>^7Y=g*}vX8o<~~X>gwVu52~h3jY>gD
address@hidden|{A|R|>guveRS!bhbSSR?_5K(~|$S?pJ?{cpP_s
zP5i3MIGY_6XsnrUTv?=0<q-uw8&0S?`%5|d`TDvC*vC0%d#ACLmV?P#66WY at 1PlMx
zr3Zhj!dne}3;I%eMfWBTY{m0!xkDVu;f=lmo)rIHcN8D8_KK}yU6<6-a!l7yb&jM3
z$~)#}XS?sn2~w)GAu`L^!#OFEOFOG7TZ^Zt3-qw|)7A~dcyjC)bDO);Jgvf<!mM^%
z42ACKw at address@hidden)kV#qL at
zs(^kr85q8UT+$QQ5&M?R8t-d=Dw6Zk-&?=G)V2yGi(iN-
zM7O(F*iJgz1eW^h`En!U<yPv7MJ5!^N;sZqRHp`=p0&>L?%Up5fvQ|V)0>2~mYtmJ
zKAL{|)uDH#|73e_2xp?F$8=S1;)e-u$nMBIx-4jSq>(#HYKSiJSK$HXYsQX>s%VyC
zkD7?zWs_sXpB(%2Fh{3yJ-HO=v*(~=i at RyKhj37%&?R%ueD9w=x$m>u`_*D&JW=(-
zkP;m!oSIrZ;d$H_?R~VL3bd2FoNkrXa<-sT*Tg@)=kgx@;y$;_=pU%3yq<Wmh&OSR
address@hidden|kpM%Sc&i>5o$QKk8omW!yk2;SEF at s1`+i
zjJlycBdPz)`mFl)HP=Gg8j2OTn>ZlK!<F)`%{cI at Q%2{n`~KAPv<$S at KS(@p{vP`?
zshUaF8%!%SJ#dF`wQv)*NtLF%p}2|df$Mp9bI1B><K~*<QJ=Im#h;-nj)nFl+Y!%R
zCP!ul8%Vn%lN9%ihqT?4+1!44Bgqzq7<yS+X)6dH#Rr-URifs!7SS{I&zXmE>iVh*
zJ7a``2MRpZ<@rlw?s!w=ZPy<oG6#9Ka#sD8WUgra*tfc!+(Ea-RXf-?ustM4J_od%
z)p{bSVnPw?5A{k<&+O?Q6=|qC65T#_M>address@hidden
zNu>*<>%PhVNmoK`JbN57Y>yoOgw}B76>C&BU6e2&fFgh64DpNhQ?xVQr at KiWdADTM
z%-EQ(a&_`S#ViJOs at P2JAw`~YA*z5|YYfJl#_Nh&p*g-vp;uue>B~xouH>J{jL8j$
zp6Tnw?<>%&&|b at Q!ubw47v{{d5!?FQ!=B6G)#{9bMU&UZ|Fq2H;(aNBD$-6;6Mv=c
z&?K!_|6WyEp`y3)ykFP+n36NhJ3 at GA7!cPXK2iT3mkR&u)I!AXbwo)cStCugq~8hM
zEGd?0S~IUxHBuMm*GQ?(G<$k*8uqF7=|j3V>_Gm{KkM_UdxT_Ftk*wLjtdX>N;X68
z1&I9(zG5_$+pZW9U$4-cxbZRXbvM{691>qC5|t{{OumlhaSPE>ya!(ne#rObm-g2{
zjnrH8Ii}(Isel~C^SEER8G+n!`6v99 at j-Q5;^LHt)=kk%wFtN8cPgS4u|iL|OK>Hb
zB-T-#(KJ^Z<R-Z-GtzQrdolW=-5IkeuDQCQBzYF*9M4*u`zZI0yF};(sci};7PUsl
zH;xunOgy6Mt;iCx(fNo#uL%<rS<1J{{_KbIQci+nyx&b4h_U*j#$9~-a4&z1-D}(7
zGC2l%uY`xm2K|Kix0YyAqo|GiT)alvQ)N*$;address@hidden>V
zqg1IT+47HKWvGs4R&Hv}E_*k}L7zt+flLNhLbsR{&@j8W0jw#X!xs^HqxX?Tw4*p*
z)j{)7oq~CH&75%ld+&2{S at 9`KGDoY%NtFZh?5e!2cEPsHRVaK#Ua32iXtf at V{>Ri$
zEQuEJQG9c51ltmN8(xfmihWckGz*30zS4PLZLhr#<dcd)hBua3>M>Fef39s`uHZ~{
z4)-pVx{$r<S8?B>t)>%(X^IzktfIBDwOAj&3U?0=V_AG{#b-r3T+cf==T&|||62N0
z88Y08UZ*K17YP1z6wABi_}kIVT_UoKG}BB<SZbBcvf;PT2skWJ7%yaUOXZL98}6P+
address@hidden|t{IVQ!bE-Jn8Hz&m_F=woz83EsNpQ&nHZT$%&KD6;c at address@hidden
z*YFdexTxi|XnB~15>cMGMYUQ}Nx=uZ<+jW{>C}a~adu55Q;address@hidden)!NN@
zrASF~L^05M-|923F_lr(MV<M>Vk7Y^UzqHTOlAUqPk5wQ#}5v6%%77V<t-x}<Sp7(
zP!~6oC4V*N=^UlKq%+S|P#VjYDKA^o5~hKE*sI=+xASkr<-%63v78VYg)WOxsvhbp
zV#naCoXgo!&Q_tuyhZ1;@WxW;Qt+MYeNJlrS^Fo4KhRa$&Nqy?7hfsnx}}`19A8Jc
zBQ6tE!aFi5a)osk{#F!GHCB9!#Ms~EO>@l)Mxk`&Tf-K84>Tfd^rhIZIet5*I6np2
zN7kWEW at B8uX`6AK<|$q+B&u#Ij*8RSqsV)@M;N2Lp?a)nFRiep<=%F-50&B8X+D`6
z7~E`Ppqe|address@hidden
zm7hEM=e={z3jTw}su~-+8Ef$kBXRzIwn%;f$3}ZYf2?!?l`{8E(8bI&m)0G|^HGrd
ziz~-<C7VL=q#r*=;a9&^KW5e3tMW6QTLSfvM^)SCG;ZZ<hdcT%+N#^*+%8v2FkhNa
zhv`qn-Y~T>R?s{7Zv0P0yf}y7K-Yu>sRbS{hzg^k9!+#9@>{qUgxce!>bb_srqhBw
zq6 at sUf49ZCrZ^Y++sPW#$gm~;y#*O-XiazlJIHqxUI at c+f4L6zaJ9r1(Aye9t~h7p
zJhZ*>wWMv8D~xX}4&@#BY%td`)Ryl0;@a--BHyHab>HINTZWtFM%kcaI6 at Hk9e6fv
z5xyzqaW51#)ic%Qu+_6Tx0&svCn&uU5_DSQ0EJaL6rAh+;Mn1*=<)fZ<W97pZisbt
zw952BUs^bZ?h9$6M{wd|QVl7<BHU<ktfCHH=S#DlcfR!(Ac^81ZL(pnSS- at RALH1Y
zUm3J^#Xt#4nNJ(GHjG(address@hidden&yUs%G+sJ+xm9?Nx8q$|fNH=<dds=4<3Hr{l(
zv7%j+-ZEG%%DV#l94vQ^Bic#aDUqsVsOoOqgt*`4Q^rrq08SJB6^;tMxq|Xt=_gt)
z{HuspZRCT2d$xJ at C!S;B6fQ>address@hidden|c{v{kGYz0z{Ta3sp0
zJjFFtyi>^HOFmytkz(address@hidden at tb9wTYx*B at U
z`Wni6$z(Aqdb`D^H|xKPg;;5>mGFUI#aCjzNM~-g;)+sImKO?yci0=-YkE^7*SWtn
zkBv#X(d>9w at G5O~yXgGsl0)h8EOC6y^0-ANt6{csH+v6!QxCe$H>Jjr-R!W?OjTd=
zRIyI#;hb(?>8TlM&DYe7Hm2yl(qp0J-V63wt~DO)?Gx at TmBG2jCed3B+YLX|FSx-%
zRdEcj=XiNRXeOD>jTCw)jtk2phn<C-i+uy7*ZdGozTu}X1BJqeeVO(;&Q5Nhds=t^
zY0Y0T7mJ- at 5cNfsXIKS1TR11I5*W*rvS=K?MW`mW;7yUU_M-V!-Sa{_ at J7u8(+WcY
zewcLKuXEgWUiVD#lnY;!V{k*G9AhxOGdMNvxU(4X>v2b1kr+VlZWq=l!>V?Q?s9i$
zB(ELxUP`deiUs<O`fS`<svYtG9vXaAy|Y5c<-Yi*p=+$uxH>9Y^9gIY65>UnozRJ8
zgf(&=`pLURySS7d@|<*x at th70Vuci$+UmNl_-p8fZ<yn?<A6u-wTv92I_{uhaqKH&
zEyF8q4wr%32>p3Azlu$h2GY;GN-;sPPN*wS0Uw`r=|j^{E!Cl at B~fX7t;kA$dq=e6
zl)Jh6XrPzef&J1gij_<g3}bY8e0%(nuPB`3hoW~<adHa}7FsJ>Dte=9{<F?wZjb+n
ze2l-VUZ%?s27-omd&j$N-Xw43fGskcoKQv#3-vp-aT=#EnC~r?5C;lYTu=T+x^eA=
z{h~>{z~%+cxHtOxhZi#}epl^OXF?6VEmYrk$y+J-Jh&(F9~n+-E1K!MX$Gkb%940C
zYt0?t4)Nvr`fL}|^J9cF!Y=4ubPu=meexF!+lZBascfgQiVJ9W$ry<BHuMw!p3o$+
zhHY1Lj8f at ltL`W!;7*XaXVEy+f<@$QqzpbG)KCP(e6En37bqRv60y at 4Tn|OAauEN6
z)Rh;7zXqM*oN%{DYk8IY9dA(eQjJ%(QZ^QNPRsSf$B~+;<&JU|?TN179o#q6jMyUe
zrN*QP`pVrCx+zRTJGPL-NdJX=;p351`3rr^2J#n_ at roM!YHkIp%%X5jZZEf)^RYg3
z5-SUx<rFl4*`;dX`jLuq4mI&dp?h>(_<address@hidden|qhVn{1^QXaHHojp7iYvG5o7
zofT($XeO=8PSbx_Dn8C3-i{lgU4%upM{H6bl1Ja5p4=(?9gW0)As=nQRBR-z4I`%F
zVtgU~3?6_#pfyO3``|address@hidden<_KDV{t7&1 at address@hidden;8{q
zv<~`%((odP;i~*gZalY+TZzx3wkR5vLd#HRWI;br9lRdRVx8z9vQ}1;Q?xaT=3MYi
z15RTTNl{rr4Dw)k3-b{z9mCBQ9txWj<address@hidden(LLK`FFi6d;4pDT0K#gulQ
zkm*6AW4OJe`(8Lnr7?Up{i{8pc at TZUaG4v(^^TgV$?=wSRgPQ<8r?^I8^c9|JA62h
z7I`UG4v!7>rAj(eI!#WZcgjT5!&sj_QLReclyphhBHz_E(HR0kNBOW7x(u)Xtjm3B
zYw29=J1Eyz*o7#$v1EcZ{D)Tx3EID6z9d{WZs#%#V+$Cx%1C`N(X^La9oPVx^ltc3
z-u2wlwyL(Nu8+aOu+w!_>gr4JwhCxThCG+{(5#M*HMU{T6iJ2dSOe6Hc=ICFA>R;b
zF6=?w1Qk3a&yXLo&-G5HV^o+k`kC7t{7qZqLR>f9g!r_0lh~Rrh~1d at gFOgL)0Q at I
zG(CJ()k$^Ed-hM~KZ-xOS%vLwf|rHyigKi4c&U_#SMkMAS#gFX4Ap*!b})@e*`v~W
zI}7V#M{DNF$zmH_ at yNT3$?5)#mDxteX8%guSvi=#4R#4HVS-`__laezc3Tpn9P(0e
zLBUm)>;7hufu`$*LdeSnqQXLp{53zfX3oz0<~|kr$eq%aR63;r!6w0zk~Lh;GcH_8
z<21bD47 at YxYw{>A5{zi8TP#8td82wnC5L>u6Y?JAHL)M?%!<qq`-twybzhF>fWLfj
ztyc(*RjcC;>address@hidden|t19}S&mUV)XUA1_3oXOYfV^_K&uqW#pPak=m4!koUN-oL
z`m6y(sJ#EDzdk=Q`lU(9ztK-mZ4lig{F`-&USYf~Y}WR+wu(yehw^a#=A1&=zTB8V
zPb&G7oWq<8pu;#YwAlY%PS;g5wdRK?swQg-rHj2#QcTB$WxD5@<+1;oqr$ziM`ex6
zE}s1<XM}GKw#cvCrCnv5uiYJdXWWWF2?aL$)YpW@@dJu=HHUF$Q=jCf(OWb=OUd|3
zVtwbd{A>CD<nq~p{12h+;oYu_j<(J!o;QK2{!+ftY=EJgZaS)@PftmTtA`}z+r(ON
zf;vrK(b`>S^2OUGI2PN^W-rN|7HmSw`1ZQ0xs~2JAryWew9_2JhUk%sL&{4 at laq?@
z!>Q4{D0aGfozfqDB07z(bKJErwtvn!lC{U3D~W+y&X118u7vPKk{&53>(t5SSg|jC
zWGRx;E2<bPqhDs-tLw&1)}ArNqcXO~*)wuFWiQBj=d47_k{thUuiMT1h2?G1ok+A$
z%&d)if=z~UMMfn27DB2M)-};oe5cBa?y6G-mgb+yTbHxx_k}+PJ&D25?h(!e*A(B;
zNWT19KF*aiKa9Df#G2m)O$7!EgU}fB;MfYP4#E`U at Tja%E87EmhVzbnvh9iQc_iAW
zb3!OOdjuVF42?w>^nCm^-Bi9~%--aX`jd27TQ9DzPQ|%3mCQ4^Dz0VuukyvbzyB!m
zKKk$YU%Ip1X78TxDpVQ6-ld{_^i$nCsGRN=C}WAB!s?OMSj%Xkiy~m$plsn?mUk$(
zLf*lg7Wqws9i{Vu1m73eY0u=yRJvWx#l6jsEjKvAAB}Gx-%0q3H$}gWovAc&gQ9%;
z>k+}Z$<ftW-aaRPp>L72Bar4z at _0N$Lit2R_mQERW6=lIA>JJ4DfCcZ77x|cj{c(B
zNcM;yv<AXEZ`z?l?y2Ki>}xI`3T^gQ^Bi+846G!v>|eQ?f{%?Dt-`~oO$Cn{qvcs#
zs_D4?HNGv*H!?*%--rA=dAIW)=M2dE<r^O;<(0e#y|V%`ea7EskGNW9ZFIC)S6L<T
zSNsA|!%r}+vs4s|qXnANs+3T1#}r$p<GdqilYQkQ%>tR;w(cn|zyE@)lyl`P+U?Pq
zs-Il9=-~xq-4K3dlqV)yqeY)pmthUZybT;address@hidden@&>yfMoL6H!BhT~9^m&dmRJb$
z<4vjhW?WL#+vM6Yzqo_S%9h`{KCB||(address@hidden|E%#
z7g#a6gu8FV<_CNYv4fSy{=rEsA?ld%z7XWA8<y$Sbh!JLy`Za|V~wq-ziT+xzu&jP
zQ_<TnQWTeDvxrqw-O^5Vl$#jSGuf{jk49^znN}#uQ>3(MOQ5Tse0v+0&Yk37-e(eu
z+zhVwxAs~B$E3DICuQ>=Oqsg7=#Zv$!e-+mJW>@K^Vygn<cTW%XJNFzsC|vSp7S4D
zEvG5GN0P(0g4KNg_-VK}`6<;y6%1v~TZCtdGYP%on~Kx9Q&A_QdLt`os;a0`OAp+$
z-DiDg-5s2#0_I5bz<address@hidden
zn#s-ZcCuBmmvn5*f9T#8xg0(fDCbRfSMhI`C$a<*7W$i~>ho}kC{uFJ=#_k3<!93}
z{cQYDxT3!yo)35&gB*Xm3OSUnX`!g_JO51|@qF_Ylgy|Cxk at YRT1QPn2e`jt8^rby
z+X>@Lx1w9B_Vc;A`<e{tj{CDS-CfdE&SCQ(l6r^B2N(Hl-sIq>h$s9qqEhWO^BP&0
z8Z#IOdL$jDJRMb6oFSXUr5Y8Q;!pH!_AT;nbWis$k&i at DgD-tvk1^nvd$5WmR;+IB
zps$W*s$KCNjjz}@;kfCW{w8YAm(|@DHU+jiw%JA3Lc7&@DNLmjp)!Hp-jJ`loQYqv
zBV2dGXX78xO{c8et(6rexP`hbeRU3_7_CR+BOAOvM-}HqyV6$Bha&5P!vZILO?-iH
zBUFQN)T?qDe=B%A#PlPfs`fJOs7sE{)SSfQR0p&s*3Luija)0;|2WJ0%aY%btboq9
z(_PtjHQY~X5P8crGaii6iVdS?6p+jnu}4rDH)|g;hIN`k!m6Og-PY5~|H3`f{XL|L
zd<zWr*Y$l3lqQrdmlbTjc4X8SE{ji#T^sv}?}4vHF=HVyforLqsrgQZdzG$n?vc(N
z_Dg<yBsP>9nB-0K_yXPNIM$a5+W6=>nxWiMW0Uy8>Lb*wYGiOInzMLi8-0Lp8XV&8
z=icK|I97WnN+xMq=%v4=uVs+PDz-$P#|_uNQ>D;Y)jR7lQ-t58?qqT5C*r}}ZuM;b
zZa{J#ab5MO-0!>|$b)K;q+puwqkm=O4tXREqxZGMU=_D3x>(!AE#`UFTeDB+#k<&9
z^=tJOS$4aecRl;v^<Dqd0Os(xKs$fg-~nj{%498R3)Kh{QSIh*ro!>7)M1KLI-^y!
zoB6~nbqo^y&address@hidden<7y3%;DCSs37#i`dw840-sVMA~
z`3*I6LqK=?v at H~C!ks<KT&-P_?YQG<&>k5X-W6&W+!8z)xj>wedh$<I1)UDx<C;ZB
z$6iy$Dxyud%}W)RaAW9XVsh8}+;!I at xv#pigI>9P<V&Ew_q``I*iyF3^T|V1M^g#a
zMm*oJEAE)8I6bW>81-G;MK#KF?Nxjy at W;K<d)hC0zXn<nP8t at R;5YbZg(}mJthrnT
z-_>2$G{lLTh4E_h3!Km0)Xh=H(<IDmZU}S2!#)4Goxa1K#h#={De3RvEq^axOn^#P
zP!;wPGyM%?d2y>?ioRkY+*Gt%yDaL3(1qi)<<-;V)1J4E94F^2=m`2pOARAUBB4;3
zP&X-><&n2?FU7&AZHjc3sy`FkRa2LnuFW%1RTI`jSf|2B7d+{m>CgAybRYH|iu4Y5
z4qo;T at C^-yrS0SfSt^W(YOgEHw^h%Mon{z;fAY7rvQo<i;l3)JP$Y8Ad&<+pKhihT
ze?T5VE%Ntp>EMZAed#%=A-l)~rA1qszrr^(ud(zKXJ9AP{%yJL=$0~5`H~I`5?_nJ
zReyco!O&Q#O{i30ivMt+l=KUo;Vj%NWrD7bn8N;1jWK)|53>v6OwD;kO|GY+lX^N=
zGjcF+ConGX(l<W%R_Y_?OG6~96fYknZ2+(5(oTxkVqtVf>|`M7|Ii`6y84-74pQM$
z;(V^8TsHDBGD<oRXHU+-PG_d<mGUC7(lq%n(GxSv6w9loag*>address@hidden
zHk^Bg?UFCJB6u#WlrBb=lK)T|Dnm15hrFGfWNsG6>hn(&<@qZ}Qk2z}5*_ReuEoD+
zO-W^ZKxo7%m{aZ}HHwT6%?bUEbO79F9Z8Yq$<vTRsLW3n0?Pej3~tPiSJhVJLS+>r
zjO3T2i+HY3hZ{|XN3Mpm!kOXG(hgb?{lz3QkA&oJ@){D6yO0Iw2i^_G1kH*rilbaA
z7ZS#ZU$_Sl>HlB~^_zoiqob&ae3CIqBUeaA+LBd(gN5Jdd=jP{AYcpjFJ8nS$Lm-(
z+?ijCwMd8Wpa$$F9Yl+<>F5T##FW^FDxu}95>#{_NNZYw&StS3<`-~Jxn=w#+>DA)
z7p|r6$aJ;|_vf74X&h#g86&Udmt-C-N*|J`B$EteyYL36eaG;_xi)MY?TG*5hTuDR
z4u6*OvSQ4F4zTTX9yu*PlS|3p<*!T!hapQtHCi4eqX^r~(&-=SVb9oKY$IOFAI0xj
z7 at 4>h_$4aMIk-&R77d~=NKcY3^_EwVDEftF(_^eF9*B#f*6cRZzz#?mJd4W^lKB^m
zL(}kcREv#<$5Jv)-Y$<N?^$zv0;{>xD2c5j>address@hidden at 40|P<VkR
zvoy3Hoo6O?7~R7S at j^5YR%9g=rAkr{*+9;cRrDErryU|- at 44tMK8QqS!704J)#UE;
z2lzDZB(maSTt)nq{iN$iQ`sjCA!_y?)Z3+5ahger)address@hidden>
z=2mho at jWc@)%XCM37yScbOcSKRoOMxiCv*?dY1Xw3ATp*A)9Dv7Q<SjZ+s;&n;U?O
address@hidden;8pAr?GM)uMh55^R71(!M?4st__cgBG at Cd`
zQ+6J#=*C0&zIZdMLMO`x`MGpddLb_*`-l^I10zsH+ at 0g#ejoS-d}mw)JpK=N7l8ud
ze&D_A1l__`u^03XeL_D|MjY}ea*OmL>q$Af9K2PE`DhW8%C+GFXbj54YdI0_ at stlh
z|F;0lGlBaX&1X|+H8M_)mwOWh6VW4d9o=I&v=u!eGl`O=>>?|LmUHh!Eb6(!+;mQX
z+S4m+A#_?Q(9Yy1`N)address@hidden(nIw
zh(V01`LnnT`bF1KiOeK>$Vf7joFH)&u?)5dwMTDQNnDw0hkN12yj{q`QHaMyP$gQD
zUWe117g-)1%1WSgh|kY7mIdj5OkzXOU^I={sF!+~39m${YziBPda~y<3m*}9A%TC&
z-N8#)C$dfU$hAmN-bC)uGH4I3i|-*9DvOk?AuY<1aY;;ALtI}dDXf8<nTlt!dGbs7
z9p%_j8pV!)PcO3etOm=ZOX*os1F~`o%SKhXw|FyGj318Os2|vPH2%rXu$`<KTf?T|
zo?I?6vbJQlJYBvh-zPS((sf#iR40?chErJ`R2qNdqPUsZ$#)Xh at ZWG8m%<rw0#ajv
z8;ET5HN8v|XhG79EG4hx>Lh^{VVhBP+!8FQVPn`U8bc7Bf at YvG><+3btP;QSY5ZqC
z4_9INRA5Q$J-th+(30#13xYEK1nyl$#>iLY&a4COf+ujRg|b2w9HDyFhn<CJva&tw
zH--KfGNXc61=vv*XF at ca*mUrehwVgTQ5DF#mFOBRhH7)&aY^JxfAD+sopypeF|!UZ
z<Dq0UNg_MRMwSG9EFIfRkJ1gy0cbm&)AN0~1bh`w<=gNz at c=Xl4Q8#Nvvv>{<eKAt
zXgh62o=EQ^wdE8tm at KA7Rv#7N%5Zsr7X(d13)x<_AL4ol+Q~L>J%!I)5$+AQ7?*>}
zGMc<0cPXX6*$UJfaz#VC5<BTihm#bD8V53St2iB;j+Oa4=nHy|2c!RJ0<8EMWdCq_
address@hidden&{(address@hidden|u#1?ZixlL#?bPPA+DQE-gf_vgk5JxB2
zV%mfhC1yIBuAv=iNAgH6Kw41^dVr7dw}m$Rbo>b(GuS5D9nC{cAeM4bCO4M*1HHeE
z>;y!~W%7~Av;buHMD`eEpoYL6dzq3QA|1#$=EYBORh-246~=Ru;F?*eAsa!Kk%n{^
z-Nsf!*YG at AfcCMn%*9%um#jUFrlk-=>rf0gRXESbpn3E-yAR{nWSt=1U(kUho`hl4
z7qks~#qwA`c9li{Z;u;K0Lui?Pt4(p_z(AsV}OzYl#AXm18d8Av6J)=czZdZWkEKN
zu7JF1FTa(>$!*CPn$K!N-)u8fKYO?~{4hQp--q+(J2@>b$_TrF>tn{YfW?_?kg7|F
z6r%N6AH?A(TouKE9Tt#xaBltuDb5DqnOs?JD7Q`U2~D`Iz{;QTS$2pvq#dB2`-l!=
z4_Q0boSMmWau)iy<sd`50v}!jl%0nS+yzvPePwr1M;6O=V9dn>KFp&{XfxImSh_f_
zgr<O><5(f|6U|0NAzq%a>+A;`z|address@hidden at Ua|u8m)w}%WJ}>>eh+423(!qq!7
z%^+^#A=l|vc8ZNbwYby#8on_12gh)$A#;bb)tKOPh at TD|k41KkP}wHO$m1k4DMNLP
zpiX!wF3eTN^VmG#z&mIG`$3nW9Gry~q4Qj- at CNS#e0vP}^O^2o-B1>r1jw05>X4(L
zG5;e)fay*_o)*D%xySekvT_EY2R|7+RvCX}>*z3c1^tGqp(dFj-;%BLH7kPm;*0n!
z`WLwCH89?I`knq_YmkkN$Fd*`gSqGUB-SB6-OFMDzwa?Ua7}-<lXd~rUQa5>A*rO?
zfRtc!&>KuSGrxg*fu^$Ws3Rze+khw?xLEEcR6?J*a=0OIdnML|79$q&oOGe(*ke`|
zqN+dYOxuw0(AmEY+UPE>!B at wPz_KH8UtAn5MEmeI6w4^{qVXse9R<5rV6)hEHiV6Y
zxH$r7QkZ>1CGaA)mJP$d at fYZ)$8+tt?Fg~%>^5B at E966RP)?y|SPp8#t>+fviRcCM
zvEFDZ+JySzanMxhjq<s3d=ZYI!zhszBDG0lCLu3+0@(YExyci`uRKehBToXXY)v-M
z_b7=wz?p<O!U?VuUct5HF*gqoDS$^}EBXe0+k=v5SZ)UyS%xep)address@hidden
zJCH$1mPRs*L!WUoK8LFY$U2t0$am-bcp6t0_X3_tVJlfjwx2#CwMcJLfo9QrtOWj_
zN;A?MG at 6;|eOei>=I-NPs0TNn-;ZO^Sz!75l%rMX0eYAQX*X0A$K$Jru_vG#JA)>O
zVzZEpWqbrr=5k=}F`y7uvl?_YDFbmg6Cc7gaUt9hO{5C?AMHvD(2v9bI698iMIp3@
zn*ci_hXMJ2;(VNfzu+1CI_?590v<MH{pcF<0)Bt69klUwRuO$+sptYb$v9jGrvj=m
address@hidden>&RBwu}wL!>|_b=9=&>address@hidden)pIkf)PxB!eu49hpb$FfPJ1f|VU%
z%g{>r{f at vL1+DiRbp}o>hYzyetQMO`ih*y=lP3_rH{ewKGIWs50%l!6)>AF}- at Z^D
zAf?ES1Ab`F^EeOA?rcM#>!^=i04$SOSM-`4BSqw0GDn&d=#$YV;address@hidden>
z6+{JLcV-su%gy2U at +PhwM9MgP0F-NCc82DX_MlNOQwu1OKG2n%OrHYB?qzON0o2-a
zdX&uvG;2wlv$Bw%J#j6rp-_y!iSDv>address@hidden|VEqOY)mZcsBEpbcQp
ztuCL%tN3o*T09qxpyfzyI*cmGXL$!XL{rcjJcVn`{f(1B$#r6X*hc6MoP#RkDb7U^
z^qbqs{brBoV^#_F?FO;VI1_wv1GJch_9gq|ko<~lA%wJHn?QNxp>t>sS^{V6 at 55*k
zp3n8gH_$<Ro3rC7Y#(jKnuDgbgI_8Fw~rznL2EvgOOTgjHZ26o{4OhwA93#?es+PX
z>W6!QR_D00+%nK{r?_6+LBQ2EbS3z>1ME>&VFy?<)EkXu0{}zKv^i-G>Wk5NtPZzD
zkoZ>|<>qj(7sfub=BN}~OUeSH9tP!C8UKq%p?vxT6n8Uf0K~43D{|?$B}xEgvjjgv
z|F9yEzpntnM}v~siI;<QUb1xdFCg7cdLHV&SQ<mOf||U<j?>NTF)+kX+zEE<ck at 2f
z02pXEIz*?_r7R3c^NuQ!2|HmI?+GZrG1LW~yZ|`A6Xmk!aO%G+{tZa<5cu>8<mn$I
zLq6KLt=t><d at VUAuO+L<UUHw-0NnY5P66^fLswBXw2K`B1y>1nLNib|{u!5#D{-HI
zvF4D2Bo}nrVo(p$SOV})6uSpCiyHX4KGZQQVGlllD{z_I0B$ULfJ*Uxt^xiD8o3ej
zf|uSRC4SGU)6=9haOVf8$Tvc5x`_57lH8sINl#iFR8uy0376u|2rHqkJ_7ek<(dI{
zJwb{1A!tb#T?E-Rl->i5Sts9?50PiI2MXe3Za(@9YIQ4A!s)atTE!Z%zaVDn2xIu7
z{0{y*)FqcGqrKUGkVgV2o2HP1CqP$qV5g{uB+*rDD;k9M<address@hidden(P`xx^
z6IlS%$9i at address@hidden&GU$Fy8s0mcS15qEyyq2sk
zRA_#3kmk@>`ib^JVNd`K*;#r7u)iRxf_4G6?!abj<h+2I^PpPjjQ+5Ab`<#6gpZ&+
zsG4um3nWW!O4gB!q!)G4JoXHC<0 at f0`Ww#z?eG)&HbwA$U>-F_oD;C2FjpLV=|N!O
zbF at 6$4QuOwtWdrFLlfya+67osrYqPeB%{K3BcKQ3V)0qP at o8uUn+Xpcn*m(~M$Pb=
zLD#am>@=va2&kZss3lqqF&hun at K3;jU$`sYi?rN*!0imCg83Ih4*)sdf^|32#h}3|
zvAc91*tj;iOKyOVkE1X*h%dp-1szfvTUi at 69_*P7n0y%OwU+oFxPu-gvx%S@@6oq(
zH)yTqz=nTe3VJgQU1hat9OPGhyaTkyEtq#D*o*FqcAz_OO&NNZRsf6Sp%17v%3!TQ
zGrHJZP%Jw at 7iwS!`x7gSuX1~FF4Lk_r~q2YWcCU){v)~>P-iU}MMi+iN=7Oqp!w)K
zsMcoqEnGhacfvK%cBJ4Ag06mz&f?biJo^Jm{3R&rt+XRVTpM&BMmYt2k`g4Ev?H&G
z2b8jdZQ&{kcc8LW at address@hidden)P<33)$arTA)1G=nWZ2HS9QRh)Sat
zs3OXsKj|O9^Dgu at s{}k)0Pnzm1GcWk%Ww*Gvx)&{O at X|Kg}Sm7`pF8hI`j=`OPjLl
z=x<Qq<?vcm4s37*MiNj_ at OUnI40^INsJIM>gCX=PC6LvtAy2I+oqc0>m=l<x9(1A(
z!9BLJTkHuo^U0vF-RLCxLbn3KH-TE$0UEJBoUW}6>Z~aAQaaLUbPjz=$FN0gHE7yY
z=mw+$mrjLhu?P!5JX`T4l!Y8fgpSNcIutqtLug%k61?1s831uc!QXsPXO}~_(NVOX
address@hidden|I9d&>*v+;9YrhAj-x_qwD9Gpy^cyIK7&Ha^rGiR#B8>4H
zJq6Y90=35yTyun(QDt-;address@hidden)3a2G#%+TqV$PHy{>UKyH6RN4Y9o
zJak4T;n8RU+t2=n(Oa-Yz<~td<wY=qY+%^yL__D%?yNRii|&C^nSmR_SSI`ql!Y6r
z at A`0(C=u7fakv7g(6NC0>sSgP_7VCOx;8!83+UC=MTbG*A7edWr7uBEErrfn58zBa
z(m at address@hidden>e)YayEedVeeXz^b7ls1A%?7ByqT*-Xf!%IJR at XgD5<
zo<XILnU>address@hidden&rLEfZ-jqWfjT8wYuH|R1-M+ER`K3xJ9
address@hidden|address@hidden at E5z)F{(
z%Df2GYhj4LOJFfG+~pSiO~(S3EMiaDV$=xElwf=t$KVw77sQtu at UsDKhxY>>ECU8U
z4p&tLuDMA!0TMK2cD5gWKHUVJWsd;iPe8AsH&lJ2&address@hidden
zpfGQ-o9F<V0K9gFMKLEt+B&uy4aY8Y3|8JAcSkdzOVbZcgzt`oQ!w?>KvW(51Cg{3
z)X!P)+5h at address@hidden(siCSv~`ehuWz+R7K74bwt4vF9A2}0CLv?^qawM0=lO|
z?wo~Bud&LYIfkRcs1{iKE#%k{1bT|ihMHChr)id;1Y8zh0sD1fRUjv`Xj?WOxT8NC
z2i&{~Sb00lsuijUnf8gT1Pcs>_56T1n+Y+ZMtfK;sIsHbAz2I_FAkhk2XyXqu+Try
zfjI{nsuU7&F}xH#1r&3_te2yFv=m)}zh;1LJ`dcM0g*Ko>=VnXvx-nZPJtC%XNMt^
address@hidden;3#z?<{nip|hd8V`F{D(H8$Wgq_^f&SMK
zX#+aP#>${^V4weKzS^h<(n6e@@j&?f`w_MQl=>q;fHzP<wTFAySWSq_qF|w;tSi{l
z4V9J-KZaa&06NS9##j$$ma>3T1AsO@;G*T|address@hidden)7p&$$7r<Y~
zLDfwHUIDH|eb6T0;4-W-#APLTo(7||15c%c6;hz0`w8_(L(I at t<b;ml3Yf`0SjAFg
zhidjLo5Kdfe6zqRcJOm!sEH4QTJ{0{=K>z~0-P<1Z=vF-0x;4)=p)n{2VlfhbOc4=
zXTYAH!OLMl&jO${U6AKt81*bvnh$_wmw?qyg0{QLPNLpe#J|x{K>Xt1_nJrveI+YI
zb}6v&JW!NV04LXjy at mqsM}T|gq4%)address@hidden(VX8K>dv{3PQgC
z;ZMRE8UfyQfDQ?0O_<SK$btKSHVVkbHvjA0LDeRp?Qq3!mJKTMHpG5;uq*+4Ux5DM
z8F>B#bJqislz|?2XPDo6=s`N!zfkp8L<7Lb)1cea8WhhS$btC~BijM*g0KgW1poDc
z5+y?JN254Ewd#<w*MO5I!(HkDXU&CCw!>XAL0_JM*QYSn|6=4ZSp6%^<Ox_Tz^Z_C
zhXB${f($5%7DMir1itJDck{3u-~tWwK^B3B_Q85z!>m<+p8o=pO$F>+0c*YiBTj)4
z=Ku?&gZ8V7dZK<Xn`DU36sX3}!8pHRJsF^;R{=lwgpB$ZFrX=DoVxIhLV&L;!3GwH
zm%Ctx2{7{!=nybhbC`bybeP|Q-EP8Md=Ob5AnUW>`7S(ipbm(EbAb(^E1Cusw+P-T
z1pV#DjEB6v3)lXy5BdY%JAog{gN-PBmdh~MzZ4)re^7;2p-yiMHY^Li-3gKK7;Jl+
zEdW)!7?js1h$ESmM<(F-yWs6}><dJN1Ti}hBD6VL54bfNkVgaew!`0IVa>_FnOR_~
zix4LZ0VPL6HZBA0=!DMBSQv8%<YZ~Ep%U`GGI|cSX^fh|d<Fr2m4_;8Bjo=Hu$31^
zDFl6~gD{gNfGqFe{vl8ZRUro|fp0eh(wBhPSPIuh0CDZm(cA-%(||+kV8lC+?-CPW
zZ8hP0K3F$`IB~=0Dwum^xXW_D`w^hrc(6+j)He^<cZiEOU?U&I>MO{HNs!r7Aitgg
zhs}WcaSBv)t6<%)*?Z{sS^;VGfb?yFJ1JN-2!4435rt3yB031!1c!hj`UQAq at GcDR
s#sB|bI~4E=Ed%h=|Hh8||Kop%^S}2T{6zw*!{Gq<E^OKT|J9WJA2^N;-T(jq
literal 0
HcmV?d00001
diff --git a/src/python/ChangeLog b/src/python/ChangeLog
deleted file mode 100644
index 419f6b2..0000000
--- a/src/python/ChangeLog
+++ /dev/null
@@ -1,474 +0,0 @@
-2008-12-17 Hynek Hanke <hanke at mach>
-
- * Makefile.in: Create conf/desktop/ directory for
- installation.
-
-2008-12-10 Hynek Hanke <hanke at mach>
-
- * speechd_config/config.py (Tests.diagnostics.decide_to_test): Set port
in
- ~/.profile and place speechd.desktop in ~/.config/autostart
-
- * Makefile.in (all): Handle speechd.desktop.
-
-2008-11-26 Hynek Hanke <hanke at mach>
-
- * Makefile.in (maintainer-clean): Maintainer-clean does clean.
-
-2008-10-15 Hynek Hanke <hanke at mach>
-
- * Makefile.in: Respect ${DESTDIR}.
-
-2008-08-11 Hynek Hanke <hanke at brailcom.org>
-
- * speechd_config/config.py (Tests.audio_try_play): Use proper
os.path.join
- for constructing path to the testing wav file.
-
-2008-07-31 Hynek Hanke <hanke at brailcom.org>
-
- * speechd_config/config.py (Tests.diagnostics): Only try hearing
- test if Speech Dispatcher was started successfuly.
- (Tests.diagnostics.decide_to_test): Do not ask whether to
- start/restart but first try to stop and then subsequently
- start via /etc/init.d/ (this covers both cases).
-
-2008-07-14 Hynek Hanke <hanke at brailcom.org>
-
- * Makefile.in (clean): Delete speechd_config/paths.py
- (install): Include prefix correctly.
- (sysconfdir): Define sysconfdir.
-
-2008-07-12 Hynek Hanke <hanke at brailcom.org>
-
- * speechd_config/config.py (Tests.diagnostics.decide_to_test):
- AudioOutput method is the proper parameter name, not
- DefaultAudioOutput.
-
-2008-07-11 Hynek Hanke <hanke at brailcom.org>
-
- * speechd_config/config.py (Options.__init__): Added basic
- description into help message.
-
- * Makefile.in (all): Typo in directory paths.
-
- * speechd_config/config.py (Options.__init__): Include summary
- message into help.
-
-2008-07-10 Hynek Hanke <hanke at brailcom.org>
-
- * speechd_config/config.py (Options): Restructured.
- test_spd_say: New test.
- Handle SPEECHD_PORT correctly.
- (Tests.write_diagnostics_results): New method.
- Use TMPDIR correctly.
- (Configuration.
- (Configure.speechd_start_system): Allow also restart.
- Bugfixes.
-
-2008-07-09 Hynek Hanke <hanke at brailcom.org>
-
- * speechd_config/config.py (Tests.diagnostics.decide_to_test):
- Also include configuration into debugging archive.
-
- * speechd_config/spd-conf: Do not call main() since it will
- be called automatically on import.
-
- * speechd_config/config.py: Typo.
-
- * Makefile.in (all): speechd_config/paths.py generation moved
- from install.
-
- * setup.py (speechd_config) New module.
-
- * Makefile.in (speechd_config): New module.
- Write necessary paths into speech_config/conf_path.py
-
-2008-06-27 Hynek Hanke <hanke at brailcom.org>
-
- * speechd/client.py (SSIPClient.set_debug): New method.
- (SSIPClient.set_debug_destination): New method.
- (SSIPClient.set_debug): New method.
- (SSIPClient.set_debug_destination): New method.
-
-2008-02-19 Tomas Cerha <cerha at brailcom.org>
-
- * README: New file.
-
- * speechd/client.py (SSIPClient.__init__): Docstring improved.
-
-2008-02-04 Hynek Hanke <hanke at mach>
-
- * Makefile.in: New file. Propagate $prefix correctly.
-
-2008-01-29 Tomas Cerha <cerha at brailcom.org>
-
- * speechd/client.py (SSIPClient.__init__): Use the environment
- variable `SPEECHD_HOST' if defined.
-
-2007-11-25 Tomas Cerha <cerha at brailcom.org>
-
- * speechd/client.py: Handle TypeError while SPEECHD_PORT value
- conversion.
-
-2007-11-22 Tomas Cerha <cerha at brailcom.org>
-
- * speechd/client.py (SSIPClient.__init__): Use the environment
- variable `SPEECHD_PORT' if defined.
-
-2007-07-11 Tomas Cerha <cerha at brailcom.org>
-
- * speechd/client.py (SSIPClient.list_synthesis_voices.split):
- Convert empty strings to `None'. Docstring improved.
-
-2007-07-10 Tomas Cerha <cerha at brailcom.org>
-
- * speechd/_test.py (AutomaticTest.test_callbacks): Test also the
- `END' callback, which doesn't currently work with Flite.
-
-2007-07-04 Tomas Cerha <cerha at brailcom.org>
-
- * speechd/client.py (SSIPClient.resume, SSIPClient.pause): Send
- the right SSIP commands.
-
-2007-07-03 Tomas Cerha <cerha at brailcom.org>
-
- * speechd/client.py (_SSIP_Connection._recv_response): Raise
- exception if the communication thread is not alive.
-
-2007-07-02 Tomas Cerha <cerha at brailcom.org>
-
- * speechd/client.py (SSIPCommunicationError): New class.
- (_SSIP_Connection._recv_response): Quit if the communication
- thread is not alive.
- (_SSIP_Connection.send_command, _SSIP_Connection.send_data): Raise
- `SSIPCommunicationError' on socket error.
-
-2007-07-02 Hynek Hanke <hanke at syrr.buchal.name>
-
- * speechd/_test.py: Voice list test uncommented.
-
-2007-06-26 Tomas Cerha <cerha at brailcom.org>
-
- * speechd/_test.py (VoiceTest.test_lists): New test.
-
- * speechd/client.py (SSIPClient.list_output_modules)
- (SSIPClient.list_synthesis_voices, SSIPClient.set_output_module)
- (SSIPClient.set_synthesis_voice): New methods.
-
-2007-05-03 Hynek Hanke <hanke at brailcom.org>
-
- * Makefile (clean): Remove build.
-
-2007-02-21 Tomas Cerha <cerha at brailcom.org>
-
- * speechd/_test.py (AutomaticTest.test_callbacks): Added warning.
-
-2007-02-17 Hynek Hanke <hanke at brailcom.org>
-
- * speechd/_test.py (AutomaticTest.test_callbacks): Removed comment
- about Scope.SELF not working.
- Added TODO comment about fixing this test.
-
-2007-02-05 Tomas Cerha <cerha at brailcom.org>
-
- * speechd/client.py (_SSIP_Connection.__init__): Thread name
- changed.
- (SSIPClient.__init__): Allocate lock.
- (SSIPClient._callback_handler): Lock before accessing
- `self._callbacks'.
- (SSIPClient.speak): Added more doc. Lock before accessing
- `self._callbacks'.
-
-2007-01-29 Tomas Cerha <cerha at brailcom.org>
-
- * speechd/client.py (SSIPClient._callback_handler)
- (SSIPClient.speak): Removed prints.
-
- * speechd/_test.py (SSIPClientTest.check_callbacks): Wait for
- callbacks after canceling messages.
- (TestSuite): Class removed.
- (tests): Instance removed.
- (SSIPClientTest): Class split into `AutomaticTest' and `VoiceTest'.
- (_SSIPClientTest, AutomaticTest, VoiceTest): New classes.
- (SpeakerTest): Class removed.
- (get_tests): Function removed.
-
-2007-01-26 Tomas Cerha <cerha at brailcom.org>
-
- * speechd/_test.py (SSIPClientTest.check_callbacks): New test.
- (SSIPClientTest.check_notification): Temporarily commented out.
-
- * speechd/client.py
- (_SSIP_Connection._CALLBACK_TYPE_MAP): New constant.
- (_SSIP_Connection.__init__): Initialize `self._callback' instead
- of `self._callbacks'.
- (_SSIP_Connection._communication): Docstring reformatted. The
- whole event dispatching rewritten to use `_CALLBACK_TYPE_MAP' and
- use a single callback function.
- (_SSIP_Connection.send_command): Docstring typo fixed.
- (_SSIP_Connection.send_data): Docstring typo fixed.
- (_SSIP_Connection.send_data): Return also server response data.
- (_SSIP_Connection.set_callback): Argument `events' removed.
- Docstring updated and extended.
- (SSIPClient.__init__): Find out and store the client id. Register
- callback and turn on notifications for all supported event types.
- (SSIPClient._callback_handler): New method.
- (SSIPClient.speak): New keyword arguments `callback' and
- `event_types'. Docstring extended. Convert `text' to `utf-8' if
- it is a unicode instance.
- (SSIPClient.char): Convert `char' to `utf-8' if it is a unicode
- instance.
- (SSIPClient.set_notification): Method removed.
- (SSIPClient.close): Only operate on `self._conn' if it is defined.
-
-2006-11-05 Hynek Hanke <hanke at brailcom.org>
-
- * speechd/client.py: IMPORTANT: On initialization of the
- connection, new lateral thread is started to handle the
- communication. This thread is terminated when the connection is
- closed.
- (_SSIP_Connection._com_buffer): New attribute.
- (_SSIP_Connection._callbacks): New attribute.
- (_SSIP_Connection._ssip_reply_semaphore): New attribute.
- (_SSIP_Connection._communication_thread): New attribute.
- (_SSIP_Connection.send_command): 'or isinstance(args[0], int)'
- added back again. This is valid SSIP.
- (SSIPClient.set_notification): New public API function.
-
- * speechd/_test.py
- (SSIPClientTest.check_notification.my_callback): New test.
-
- * speechd/client.py (_SSIP_Connection.__init__): Start the
- self._communication method in a new thread.
- (_SSIP_Connection._recv_response): 1->True
- (_SSIP_Connection._recv_message): Renamed from _recv_response.
- (SSIPClient.__del__): New destructor.
-
-2006-08-09 Hynek Hanke <hanke at brailcom.org>
-
- * setup.py: Use /usr/bin/env instead of /usr/bin/python
-
- * Makefile (install): Install with correct prefix.
-
-2006-07-13 Hynek Hanke <hanke at brailcom.org>
-
- * Makefile: Only attempt to use distutils if python is installed.
-
-2006-07-11 Hynek Hanke <hanke at chopin>
-
- * speechd/client.py: Typos in docstrings.
- (Speaker): Docstring clarification.
- Method say() removed.
-
-2006-07-11 Tomas Cerha <cerha at brailcom.org>
-
- * speechd/_test.py: `Client' -> `SSIPClient'.
- (ClientTest): -> `SSIPClientTest'.
- (SSIPClientTest.check_escapes)
- (SSIPClientTest.check_voice_properties)
- (SSIPClientTest.check_other_commands): `say' -> `speak'.
- (SpeakerTest): New test class.
-
- * speechd/client.py (Module): Docstring updated.
- (Client): Class renamed to `SSIPClient'.
- (SSIPClient): Docstring improved.
- (SSIPClient.__init__): Don't initialize `self._current_priority'.
- (SSIPClient.set_priority): Added docstring. Always send the
- command. Don't check `self._current_priority'.
- (SSIPClient.say): Method renamed to `speak'.
- (SSIPClient.speak, SSIPClient.char, SSIPClient.key)
- (SSIPClient.sound_icon): The arguent `priority' removed. Don't
- set the priority here.
- (Speaker): New class.
- (Client): New class.
-
-2006-07-10 Tomas Cerha <cerha at brailcom.org>
-
- * speechd/_test.py: Import `speechd' not `client'.
- (Client): Renamed to `ClientTest'.
- (ClientTest._client): New method.
- (ClientTest.check_escapes, ClientTest.check_voice_properties)
- (ClientTest.check_other_commands): New methods.
- (ClientTest.check_it): Method split into the above new methods.
-
- * speechd/client.py: Don't import `string'.
- (SSIPCommandError, SSIPDataError): Docstrings improved.
- (_SSIP_Connection.NEWLINE): Renamed to `_NEWLINE'.
- (_SSIP_Connection.END_OF_DATA_SINGLE): Renamed to
- `_END_OF_DATA_MARKER'.
- (_SSIP_Connection.END_OF_DATA_ESCAPED_SINGLE): Renamed to
- `_END_OF_DATA_MARKER_ESCAPED'.
- (_SSIP_Connection.END_OF_DATA_BEGIN)
- (_SSIP_Connection.END_OF_DATA_ESCAPED_BEGIN): Constants removed.
- (_SSIP_Connection.END_OF_DATA): Renamed to `_END_OF_DATA'.
- (_SSIP_Connection.END_OF_DATA_ESCAPED): Renamed to
- `_END_OF_DATA_ESCAPED'.
- (_SSIP_Connection._readline): Docstring improved. `self.NEWLINE'
- -> `self._NEWLINE'.
- (_SSIP_Connection.send_command): `self.NEWLINE' ->
- `self._NEWLINE'.
- (_SSIP_Connection.send_data): Use new constant names.
- (Client.__init__): Initialize the `_priority' attribute to None.
- (Client._set_priority): Only set the priority if it is different
- than the last priority stored in the `_priority' attribute. Set
- this attribute on change.
- (Client.char): Replace the space by `space' as required by SSIP.
- (Client.__init__): `self._priority' renamed to
- `self._current_priority'.
- (Client._set_priority): Renamed to `_set_priority'.
- (Client.set_priority): `self._priority' ->
- `self._current_priority'.
- (Client.char, Client.key, Client.sound_icon, Client.say):
- `self._set_priority' -> `self.set_priority'.
- (Client.pause): Improved docstring formatting.
-
- * speechd/util.py: File removed.
-
-2006-06-28 Tomas Cerha <cerha at brailcom.org>
-
- * speechd/client.py (SSIPCommandError)
- (_CommunicationError): Renamed to `SSIPError'.
- (CommandError): Renamed to `SSIPCommandError'.
- (SendDataError): Renamed to `SSIPDataError'.
- (_SSIP_Connection.send_command): Added an assertion on the 'scope'
- argument for certain commands.
- (_SSIP_Connection.send_command): `CommandError' ->
- `SSIPCommandError'.
- (_SSIP_Connection.send_data): `SendDataError' -> `SSIPDataError'.
- (Scope, Priority): New classes.
- (Client): Derive from `object'. Some doc added.
- (Client.__init__): The `port' default value set to `None'.
- (Client.__set_priority): Renamed to `_set_priority()'.
- (Client._set_priority): Use the `Priority' constants for
- assertion.
- (__check_scope): Method removed.
- (Client.say, Client.char, Client.key, Client.sound_icon): Use
- `Priority' constants for default values. `__set_priority()' ->
- `_set_priority()'. Docstrings updated.
- (Client.char): Don't replace space by "space".
- (Client.cancel, Client.stop, Client.resume, Client.set_language)
- (Client.set_pitch, Client.set_rate, Client.set_volume)
- (Client.set_punctuation, Client.set_spelling)
- (Client.set_cap_let_recogn, Client.set_voice)
- (Client.set_pause_context): Use `Scope' constants for default
- values. Don't call `__check_scope()'. Docstrings updated.
- (Client.say): Only set the priority if it's not a 'MESSAGE'.
- (Client.set_rate, _SSIP_Connection.send_command): Use the `Scope'
- constants for assertions.
- (_SSIP_Connection.close): Unset the `_socket' attribute.
- (PunctuationMode): New class.
- (Client.__init__, Client._set_priority): Use the `Scope'
- constants.
- (Client.say): Don't set the priority if it is 'MESSAGE'.
- (Client.set_pitch, Client.set_rate, Client.set_volume)
- (Client.set_voice, Client.set_pause_context): Use `isinstance'
- instead of comparint types.
- (Client.set_punctuation): Use the 'PunctuationMode' constants for
- assertions.
-
-2006-06-27 Tomas Cerha <cerha at brailcom.org>
-
- * speechd/client.py (_SSIP_Connection.__init__): Initialize
- the `_buffer' attribute.
- (_SSIP_Connection._readline): Read data from the socket using a
- buffer, not character by character...
- (Client.__init__): Require the `name' argument. Changed the
- default value of `user' to `unknown' and `component' to `default'.
- (Client.pause): Cosmetical change.
-
-2003-11-20 Tomas Cerha <cerha at freebsoft.org>
-
- * speechd/client.py (Client): Changed the port number.
-
- * speechd/_test.py (get_tests): Don't set the language.
-
-2003-07-22 Tomas Cerha <cerha at freebsoft.org>
-
- * speechd/_test.py (Client.check_it): Added tests for `set_rate()'
- and `set_pitch()' methods.
- (Client.check_it): Added test for Czech language.
-
- * speechd/client.py: `Speech Daemon' changed to `Speech
- Dispatcher'.
- (_SSIP_Connection): New Class.
- (Client.RECV_BUFFER_SIZE, Client.SSIP_NEWLINE)
- (Client._SSIP_END_OF_DATA, Client._SSIP_END_OF_DATA_ESCAPED):
- Constants removed.
- (_Socket): Class removed.
- (Client.__init__): Use `_SSIP_Conection' instead of plain socket.
- (Client._send_command, Client._send_data, Client._recv_response):
- Methods removed.
- (Client.close): Close `self._conn' instead of `self._socket'.
- (Client.say, Client.stop): Use `self._conn.send_command()' instead
- of `self._send_command()'.
- (Client.set_language): New method.
- (Client.set_pitch): New method.
- (Client.set_pitch): New method.
- (Client.get_client_list): Use `self._conn.send_command()' instead
- of `self._send_command()'.
-
-2003-07-17 Tomas Cerha <cerha at freebsoft.org>
-
- * speechd/_test.py (Client.check_it): Pass a new `user' argument
- to `Client' constructor.
-
- * speechd/client.py (Client.__init__._Socket): New class.
- (Client.__init__): Use `_Socket' instead of `socket.socket'.
- (Client._recv_response): Use `_Socket.readline()'.
- (module): The commented out C library code removed.
- (Client.say): Use new priority names.
- (Client.__init__): Arguments `client_name' and `connection_name'
- replaced by `user', `client' and `component'. Send `self' in the
- 'SET self CLIENT_NAME' command.
-
-2003-03-30 Tomas Cerha <cerha at brailcom.org>
-
- * speechd/client.py (Client.SPEECH_PORT): Updated docstring.
- (Client.RECV_BUFFER_SIZE, Client.SSIP_NEWLINE)
- (Client._SSIP_END_OF_DATA, Client._SSIP_END_OF_DATA_ESCAPED): New
- constants.
- (Client._send_command, Client._send_data, Client._recv_response):
- Use them.
- (Client._send_data, Client._recv_response): Rewritten, to handle
- multiline responses. Return response data as third item of
- returned tuple.
- (Client.close): Don't send `BYE' command.
- (Client.say): Don't return server response (isolate client from
- those details).
- (Client.get_client_list): New method.
-
-2003-03-29 Tomas Cerha <cerha at freebsoft.org>
-
- * speechd/client.py (SPEECH_PORT, RECV_BUFFER_SIZE): Moved to
- class `Client'.
- (Client): Documentation added.
- (Client.SPEECH_PORT, Client.RECV_BUFFER_SIZE): New constants.
- (Client.__init__): `SPEECH_PORT' -> `self.SPEECH_PORT'
- (Client._send_command): Cosmetic changes.
- (Client._send_data): Cosmetic changes.
- (Client._recv_response): Commant added. `RECV_BUFFER_SIZE' ->
- `self.RECV_BUFFER_SIZE'.
- (Client.close): Documentation added.
- (Client.stop): New keyword argument `all'.
- (Client.say): Documentation added. Return the value of respose
- code and message.
-
-2003-03-27 Tomas Cerha <cerha at freebsoft.org>
-
- * speechd/client.py: Import `string' module.
- (SPEECH_PORT): Added docstring.
- (RECV_BUFFER_SIZE): New constant.
- (_CommunicationError): New class.
- (CommandError): New class.
- (SendDataError.data): New class.
- (Client.__init__): Make `client_name' and `conn_name' keyword
- args.
- (Client._send_command): Convert `args' to strings. Handle server
- response. Return resonse code and message.
- (Client._send_data): New method.
- (Client._recv_response): New method.
- (Client.close): Before closing socket send a `BYE' command.
- (Client.stop): New method.
- (Client.say): New method.
-
- * speechd/_test.py (Client.check_it): Test the `say()' method.
-
diff --git a/src/python/Makefile.am b/src/python/Makefile.am
deleted file mode 100644
index d54489f..0000000
--- a/src/python/Makefile.am
+++ /dev/null
@@ -1,2 +0,0 @@
-## Process this file with automake to produce Makefile.in
-SUBDIRS = speechd speechd_config
diff --git a/src/python/README b/src/python/README
deleted file mode 100644
index 39d74a3..0000000
--- a/src/python/README
+++ /dev/null
@@ -1,10 +0,0 @@
-This is a Python interface to SSIP.
-
-Full range of SSIP commands is implemented including callback handling. See
-the section "Python API" in Speech Dispatcher documentation for more
-information.
-
-If you have any questions, suggestions, etc., feel free to contact us at the
-mailing list <speechd at lists.freebsoft.org>.
-
--- Tomas Cerha <cerha at freebsoft.org>
diff --git a/src/python/speechd/Makefile.am b/src/python/speechd/Makefile.am
deleted file mode 100644
index 59a7afe..0000000
--- a/src/python/speechd/Makefile.am
+++ /dev/null
@@ -1,21 +0,0 @@
-## Process this file with automake to produce Makefile.in
-
-speechd_pythondir = $(pyexecdir)/speechd
-speechd_python_PYTHON = __init__.py _test.py client.py
-
-nodist_speechd_python_PYTHON = paths.py
-
-edit = sed \
- -e 's:@address@hidden:$(bindir):g'
-
-paths.py: Makefile
- rm -f $@
- srcdir=; \
- test -f ./address@hidden || srcdir=$(srcdir)/; \
- $(edit) address@hidden > $@
-
-paths.py: $(srcdir)/paths.py.in
-
-CLEANFILES = paths.py
-
-EXTRA_DIST = paths.py.in
diff --git a/src/python/speechd/__init__.py b/src/python/speechd/__init__.py
deleted file mode 100644
index 2bb2923..0000000
--- a/src/python/speechd/__init__.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright (C) 2001, 2002 Brailcom, o.p.s.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-
-from client import *
-
diff --git a/src/python/speechd/_test.py b/src/python/speechd/_test.py
deleted file mode 100644
index d503e81..0000000
--- a/src/python/speechd/_test.py
+++ /dev/null
@@ -1,136 +0,0 @@
-#!/usr/bin/env python
-
-# Copyright (C) 2003, 2006, 2007 Brailcom, o.p.s.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public Licensex1 as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-
-import unittest
-import time
-
-from client import PunctuationMode, CallbackType, SSIPClient, Scope, Speaker
-
-
-class _SSIPClientTest(unittest.TestCase):
-
- def setUp(self):
- self._client = SSIPClient('test')
- self._client.set_language('en')
- self._client.set_rate(30)
-
- def tearDown(self):
- self._client.close()
-
-class AutomaticTest(_SSIPClientTest):
- """A set of tests which may be evaluated automatically.
-
- Please put all tests which require a user to listen to their output to the
- VoiceTest below.
-
- """
- def test_callbacks(self):
- # TODO: This needs to be fixed. There is no guarantee that
- # the message will start in one second nor is there any
- # guarantee that it will start at all. It can be interrupted
- # by other applications etc. Also there is no guarantee that
- # the cancel will arrive on time and the end callback will be
- # received on time. Also the combination cancel/end does not have
- # to work as expected and SD and the interface can still be ok.
- # -- Hynek Hanke
- self._client.set_output_module('flite')
- called = {CallbackType.BEGIN: [],
- CallbackType.CANCEL: [],
- CallbackType.END: []}
- self._client.speak("This message should get interrupted. It is "
- "hopefully long enough to last more than 1 second.",
- callback=lambda type: called[type].append('msg1'))
- self._client.speak("This second message should not be spoken at all.",
- callback=lambda type: called[type].append('msg2'))
- time.sleep(1)
- self._client.cancel()
- self._client.speak("Hi.",
- callback=lambda type: called[type].append('msg3'))
- # Wait for pending events...
- time.sleep(3)
- started, canceled, ended = [called[t] for t in (CallbackType.BEGIN,
- CallbackType.CANCEL,
- CallbackType.END)]
- assert started == ['msg1', 'msg3'] and ended == ['msg3'] and \
- 'msg1' in canceled and 'msg2' in canceled and \
- 'msg3' not in canceled, \
- (called,
- "This failure only indicates a possible error. The test "
- "depends on proper timing and results may warry depending "
- "on the used output module and other conditions. See the "
- "code of this test method if you want to investigate "
- "further.")
-
-
-
-class VoiceTest(_SSIPClientTest):
- """This set of tests requires a user to listen to it.
-
- The success or failure of the tests defined here can not be detected
- automatically.
-
- """
-
- def test_escapes(self):
- c = self._client
- c.speak("Testing data escapes:")
- c.set_punctuation(PunctuationMode.ALL)
- c.speak(".")
- c.speak("Marker at the end.\r\n.\r\n")
- c.speak(".\r\nMarker at the beginning.")
-
- def test_voice_properties(self):
- c = self._client
- c.speak("Testing voice properties:")
- c.set_pitch(-100)
- c.speak("I am fat Billy")
- c.set_pitch(100)
- c.speak("I am slim Willy")
- c.set_pitch(0)
- c.set_rate(100)
- c.speak("I am quick Dick.")
- c.set_rate(-80)
- c.speak("I am slow Joe.")
- c.set_rate(0)
- c.set_pitch(100)
- c.set_volume(-50)
- c.speak("I am quiet Mariette.")
- c.set_volume(100)
- c.speak("I am noisy Daisy.")
-
- def test_other_commands(self):
- c = self._client
- c.speak("Testing other commands:")
- c.char("a")
- c.key("shift_b")
- c.sound_icon("empty")
-
- def test_lists(self):
- c = self._client
- for module in c.list_output_modules():
- c.set_output_module(module)
- print "**", module
- c.speak(module +"using default voice")
- for name, lang, dialect in c.list_synthesis_voices():
- print " -", module, name, lang, dialect
- c.set_synthesis_voice(name)
- c.speak(module +" using voice "+ name)
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/src/python/speechd/client.py b/src/python/speechd/client.py
deleted file mode 100644
index 13a9c7e..0000000
--- a/src/python/speechd/client.py
+++ /dev/null
@@ -1,1125 +0,0 @@
-# Copyright (C) 2003-2008 Brailcom, o.p.s.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-
-"""Python API to Speech Dispatcher
-
-Basic Python client API to Speech Dispatcher is provided by the 'SSIPClient'
-class. This interface maps directly to available SSIP commands and logic.
-
-A more convenient interface is provided by the 'Speaker' class.
-
-"""
-
-#TODO: Blocking variants for speak, char, key, sound_icon.
-
-import socket, sys, os, subprocess, time, tempfile
-
-try:
- import threading
-except:
- import dummy_threading as threading
-
-import paths
-
-class CallbackType(object):
- """Constants describing the available types of callbacks"""
- INDEX_MARK = 'index_marks'
- """Index mark events are reported when the place they were
- included into the text by the client application is reached
- when speaking them"""
- BEGIN = 'begin'
- """The begin event is reported when Speech Dispatcher starts
- actually speaking the message."""
- END = 'end'
- """The end event is reported after the message has terminated and
- there is no longer any sound from it being produced"""
- CANCEL = 'cancel'
- """The cancel event is reported when a message is canceled either
- on request of the user, because of prioritization of messages or
- due to an error"""
- PAUSE = 'pause'
- """The pause event is reported after speaking of a message
- was paused. It no longer produces any audio."""
- RESUME = 'resume'
- """The resume event is reported right after speaking of a message
- was resumed after previous pause."""
-
-class SSIPError(Exception):
- """Common base class for exceptions during SSIP communication."""
-
-class SSIPCommunicationError(SSIPError):
- """Exception raised when trying to operate on a closed connection."""
-
- _additional_exception = None
-
- def __init__(self, description=None, original_exception=None, **kwargs):
- self._original_exception = original_exception
- self._description = description
- super(SSIPError, self).__init__(**kwargs)
-
- def original_exception(self):
- """Return the original exception if any
-
- If this exception is secondary, being caused by a lower
- level exception, return this original exception, otherwise
- None"""
- return self._original_exception
-
- def set_additional_exception(self, exception):
- """Set an additional exception
-
- See method additional_exception().
- """
- self._additional_exception = exception
-
- def additional_exception(self):
- """Return an additional exception
-
- Additional exceptions araise from failed attempts to resolve
- the former problem"""
- return self._additional_exception
-
- def description(self):
- """Return error description"""
- return self._description
-
- def __str__(self):
- msgs = []
- if self.description():
- msgs.append(self.description())
- if self.original_exception:
- msgs.append("Original error: " + str(self.original_exception()))
- if self.additional_exception:
- msgs.append("Additional error: " +
str(self.additional_exception()))
- return "\n".join(msgs)
-
-class SSIPResponseError(Exception):
- def __init__(self, code, msg, data):
- Exception.__init__(self, "%s: %s" % (code, msg))
- self._code = code
- self._msg = msg
- self._data = data
-
- def code(self):
- """Return the server response error code as integer number."""
- return self._code
-
- def msg(self):
- """Return server response error message as string."""
- return self._msg
-
-
-class SSIPCommandError(SSIPResponseError):
- """Exception raised on error response after sending command."""
-
- def command(self):
- """Return the command string which resulted in this error."""
- return self._data
-
-
-class SSIPDataError(SSIPResponseError):
- """Exception raised on error response after sending data."""
-
- def data(self):
- """Return the data which resulted in this error."""
- return self._data
-
-
-class SpawnError(Exception):
- """Indicates failure in server autospawn."""
-
-class CommunicationMethod(object):
- """Constants describing the possible methods of connection to server."""
- UNIX_SOCKET = 'unix_socket'
- """Unix socket communication using a filesystem path"""
- INET_SOCKET = 'inet_socket'
- """Inet socket communication using a host and port"""
-
-class _SSIP_Connection(object):
- """Implemantation of low level SSIP communication."""
-
- _NEWLINE = "\r\n"
- _END_OF_DATA_MARKER = '.'
- _END_OF_DATA_MARKER_ESCAPED = '..'
- _END_OF_DATA = _NEWLINE + _END_OF_DATA_MARKER + _NEWLINE
- _END_OF_DATA_ESCAPED = _NEWLINE + _END_OF_DATA_MARKER_ESCAPED + _NEWLINE
- # Constants representing \r\n. and \r\n..
- _RAW_DOTLINE = _NEWLINE + _END_OF_DATA_MARKER
- _ESCAPED_DOTLINE = _NEWLINE + _END_OF_DATA_MARKER_ESCAPED
-
- _CALLBACK_TYPE_MAP = {700: CallbackType.INDEX_MARK,
- 701: CallbackType.BEGIN,
- 702: CallbackType.END,
- 703: CallbackType.CANCEL,
- 704: CallbackType.PAUSE,
- 705: CallbackType.RESUME,
- }
-
- def __init__(self, communication_method, socket_path, host, port):
- """Init connection: open the socket to server,
- initialize buffers, launch a communication handling
- thread.
- """
-
- if communication_method == CommunicationMethod.UNIX_SOCKET:
- socket_family = socket.AF_UNIX
- socket_connect_args = socket_path
- elif communication_method == CommunicationMethod.INET_SOCKET:
- assert host and port
- socket_family = socket.AF_INET
- socket_connect_args = (socket.gethostbyname(host), port)
- else:
- raise ValueError("Unsupported communication method")
-
- try:
- self._socket = socket.socket(socket_family, socket.SOCK_STREAM)
- self._socket.connect(socket_connect_args)
- except socket.error, ex:
- raise SSIPCommunicationError("Can't open socket using method "
- + communication_method,
- original_exception = ex)
-
- self._buffer = ""
- self._com_buffer = []
- self._callback = None
- self._ssip_reply_semaphore = threading.Semaphore(0)
- self._communication_thread = \
- threading.Thread(target=self._communication, kwargs={},
- name="SSIP client communication thread")
- self._communication_thread.start()
-
- def close(self):
- """Close the server connection, destroy the communication thread."""
- # Read-write shutdown here is necessary, otherwise the socket.recv()
- # function in the other thread won't return at last on some platforms.
- try:
- self._socket.shutdown(socket.SHUT_RDWR)
- except socket.error:
- pass
- self._socket.close()
- # Wait for the other thread to terminate
- self._communication_thread.join()
-
- def _communication(self):
- """Handle incomming socket communication.
-
- Listens for all incomming communication on the socket, dispatches
- events and puts all other replies into self._com_buffer list in the
- already parsed form as (code, msg, data). Each time a new item is
- appended to the _com_buffer list, the corresponding semaphore
- 'self._ssip_reply_semaphore' is incremented.
-
- This method is designed to run in a separate thread. The thread can be
- interrupted by closing the socket on which it is listening for
- reading."""
-
- while True:
- try:
- code, msg, data = self._recv_message()
- except IOError:
- # If the socket has been closed, exit the thread
- sys.exit()
- if code/100 != 7:
- # This is not an index mark nor an event
- self._com_buffer.append((code, msg, data))
- self._ssip_reply_semaphore.release()
- continue
- # Ignore the event if no callback function has been registered.
- if self._callback is not None:
- type = self._CALLBACK_TYPE_MAP[code]
- if type == CallbackType.INDEX_MARK:
- kwargs = {'index_mark': data[2]}
- else:
- kwargs = {}
- # Get message and client ID of the event
- msg_id, client_id = map(int, data[:2])
- self._callback(msg_id, client_id, type, **kwargs)
-
-
- def _readline(self):
- """Read one whole line from the socket.
-
- Blocks until the line delimiter ('_NEWLINE') is read.
-
- """
- pointer = self._buffer.find(self._NEWLINE)
- while pointer == -1:
- try:
- d = self._socket.recv(1024)
- except:
- raise IOError
- if len(d) == 0:
- raise IOError
- self._buffer += d
- pointer = self._buffer.find(self._NEWLINE)
- line = self._buffer[:pointer]
- self._buffer = self._buffer[pointer+len(self._NEWLINE):]
- return line
-
- def _recv_message(self):
- """Read server response or a callback
- and return the triplet (code, msg, data)."""
- data = []
- c = None
- while True:
- line = self._readline()
- assert len(line) >= 4, "Malformed data received from server!"
- code, sep, text = line[:3], line[3], line[4:]
- assert code.isalnum() and (c is None or code == c) and \
- sep in ('-', ' '), "Malformed data received from server!"
- if sep == ' ':
- msg = text
- return int(code), msg, tuple(data)
- data.append(text)
-
- def _recv_response(self):
- """Read server response from the communication thread
- and return the triplet (code, msg, data)."""
- # TODO: This check is dumb but seems to work. The main thread
- # hangs without it, when the Speech Dispatcher connection is lost.
- if not self._communication_thread.isAlive():
- raise SSIPCommunicationError
- self._ssip_reply_semaphore.acquire()
- # The list is sorted, read the first item
- response = self._com_buffer[0]
- del self._com_buffer[0]
- return response
-
- def send_command(self, command, *args):
- """Send SSIP command with given arguments and read server response.
-
- Arguments can be of any data type -- they are all stringified before
- being sent to the server.
-
- Returns a triplet (code, msg, data), where 'code' is a numeric SSIP
- response code as an integer, 'msg' is an SSIP rsponse message as string
- and 'data' is a tuple of strings (all lines of response data) when a
- response contains some data.
-
- 'SSIPCommandError' is raised in case of non 2xx return code. See SSIP
- documentation for more information about server responses and codes.
-
- 'IOError' is raised when the socket was closed by the remote side.
-
- """
- if __debug__:
- if command in ('SET', 'CANCEL', 'STOP',):
- assert args[0] in (Scope.SELF, Scope.ALL) \
- or isinstance(args[0], int)
- cmd = ' '.join((command,) + tuple(map(str, args)))
- try:
- self._socket.send(cmd + self._NEWLINE)
- except socket.error:
- raise SSIPCommunicationError("Speech Dispatcher connection lost.")
- code, msg, data = self._recv_response()
- if code/100 != 2:
- raise SSIPCommandError(code, msg, cmd)
- return code, msg, data
-
- def send_data(self, data):
- """Send multiline data and read server response.
-
- Returned value is the same as for 'send_command()' method.
-
- 'SSIPDataError' is raised in case of non 2xx return code. See SSIP
- documentation for more information about server responses and codes.
-
- 'IOError' is raised when the socket was closed by the remote side.
-
- """
- # Escape the end-of-data marker even if present at the beginning
- # The start of the string is also the start of a line.
- if data.startswith(self._END_OF_DATA_MARKER):
- l = len(self._END_OF_DATA_MARKER)
- data = self._END_OF_DATA_MARKER_ESCAPED + data[l:]
-
- # Escape the end of data marker at the start of each subsequent
- # line. We can do that by simply replacing \r\n. with \r\n..,
- # since the start of a line is immediately preceded by \r\n,
- # when the line is not the beginning of the string.
- data = data.replace(self._RAW_DOTLINE, self._ESCAPED_DOTLINE)
-
- try:
- self._socket.send(data + self._END_OF_DATA)
- except socket.error:
- raise SSIPCommunicationError("Speech Dispatcher connection lost.")
- code, msg, response_data = self._recv_response()
- if code/100 != 2:
- raise SSIPDataError(code, msg, data)
- return code, msg, response_data
-
- def set_callback(self, callback):
- """Register a callback function for handling asynchronous events.
-
- Arguments:
- callback -- a callable object (function) which will be called to
- handle asynchronous events (arguments described below). Passing
- `None' results in removing the callback function and ignoring
- events. Just one callback may be registered. Attempts to register
- a second callback will result in the former callback being
- replaced.
-
- The callback function must accept three positional arguments
- ('message_id', 'client_id', 'event_type') and an optional keyword
- argument 'index_mark' (when INDEX_MARK events are turned on).
-
- Note, that setting the callback function doesn't turn the events on.
- The user is responsible to turn them on by sending the appropriate `SET
- NOTIFICATION' command.
-
- """
- self._callback = callback
-
-class _CallbackHandler(object):
- """Internal object which handles callbacks."""
-
- def __init__(self, client_id):
- self._client_id = client_id
- self._callbacks = {}
- self._lock = threading.Lock()
-
- def __call__(self, msg_id, client_id, type, **kwargs):
- if client_id != self._client_id:
- # TODO: does that ever happen?
- return
- self._lock.acquire()
- try:
- try:
- callback, event_types = self._callbacks[msg_id]
- except KeyError:
- pass
- else:
- if event_types is None or type in event_types:
- callback(type, **kwargs)
- if type in (CallbackType.END, CallbackType.CANCEL):
- del self._callbacks[msg_id]
- finally:
- self._lock.release()
-
- def add_callback(self, msg_id, callback, event_types):
- self._lock.acquire()
- try:
- self._callbacks[msg_id] = (callback, event_types)
- finally:
- self._lock.release()
-
-class Scope(object):
- """An enumeration of valid SSIP command scopes.
-
- The constants of this class should be used to specify the 'scope' argument
- for the 'Client' methods.
-
- """
- SELF = 'self'
- """The command (mostly a setting) applies to current connection only."""
- ALL = 'all'
- """The command applies to all current Speech Dispatcher connections."""
-
-
-class Priority(object):
- """An enumeration of valid SSIP message priorities.
-
- The constants of this class should be used to specify the 'priority'
- argument for the 'Client' methods. For more information about message
- priorities and their interaction, see the SSIP documentation.
-
- """
- IMPORTANT = 'important'
- TEXT = 'text'
- MESSAGE = 'message'
- NOTIFICATION = 'notification'
- PROGRESS = 'progress'
-
-
-class PunctuationMode(object):
- """Constants for selecting a punctuation mode.
-
- The mode determines which characters should be read.
-
- """
- ALL = 'all'
- """Read all punctuation characters."""
- NONE = 'none'
- """Don't read any punctuation character at all."""
- SOME = 'some'
- """Only the user-defined punctuation characters are read.
-
- The set of characters is specified in Speech Dispatcher configuration.
-
- """
-
-class DataMode(object):
- """Constants specifying the type of data contained within messages
- to be spoken.
-
- """
- TEXT = 'text'
- """Data is plain text."""
- SSML = 'ssml'
- """Data is SSML (Speech Synthesis Markup Language)."""
-
-
-class SSIPClient(object):
- """Basic Speech Dispatcher client interface.
-
- This class provides a Python interface to Speech Dispatcher functionality
- over an SSIP connection. The API maps directly to available SSIP commands.
- Each connection to Speech Dispatcher is represented by one instance of this
- class.
-
- Many commands take the 'scope' argument, thus it is shortly documented
- here. It is either one of 'Scope' constants or a number of connection. By
- specifying the connection number, you are applying the command to a
- particular connection. This feature is only meant to be used by Speech
- Dispatcher control application, however. More datails can be found in
- Speech Dispatcher documentation.
-
- """
-
- DEFAULT_HOST = '127.0.0.1'
- """Default host for server connections."""
- DEFAULT_PORT = 6560
- """Default port number for server connections."""
- DEFAULT_SOCKET_PATH = "~/.speech-dispatcher/speechd.sock"
- """Default name of the communication unix socket"""
-
- def __init__(self, name, component='default', user='unknown', address=None,
- autospawn=None,
- # Deprecated ->
- host=None, port=None, method=None, socket_path=None):
- """Initialize the instance and connect to the server.
-
- Arguments:
- name -- client identification string
- component -- connection identification string. When one client opens
- multiple connections, this can be used to identify each of them.
- user -- user identification string (user name). When multi-user
- acces is expected, this can be used to identify their connections.
- address -- server address as specified in Speech Dispatcher
- documentation (e.g.
"unix:/home/joe/.speech-dispatcher/speechd.sock"
- or "inet:192.168.0.85:6561")
- autospawn -- a flag to specify whether the library should
- try to start the server if it determines its not already
- running or not
-
- Deprecated arguments:
- method -- communication method to use, one of the constants defined
in class
- CommunicationMethod
- socket_path -- for CommunicationMethod.UNIX_SOCKET, socket
- path in filesystem. By default, this is
~/.speech-dispatcher/speechd.sock
- where `~' is the users home directory as determined from the system
- defaults and from the $HOMEDIR environment variable.
- host -- for CommunicationMethod.INET_SOCKET, server hostname
- or IP address as a string. If None, the default value is
- taken from SPEECHD_HOST environment variable (if it
- exists) or from the DEFAULT_HOST attribute of this class.
- port -- for CommunicationMethod.INET_SOCKET method, server
- port as number or None. If None, the default value is
- taken from SPEECHD_PORT environment variable (if it
- exists) or from the DEFAULT_PORT attribute of this class.
-
- For more information on client identification strings see Speech
- Dispatcher documentation.
- """
-
- # Resolve connection parameters:
- connection_args = {'communication_method':
CommunicationMethod.UNIX_SOCKET,
- 'socket_path':
os.path.expanduser(self.DEFAULT_SOCKET_PATH),
- 'host': self.DEFAULT_HOST,
- 'port': self.DEFAULT_PORT,
- }
- # Respect address method argument and SPEECHD_ADDRESS environemt
variable
- _address = address or os.environ.get("SPEECHD_ADDRESS")
-
- if _address:
-
connection_args.update(self._connection_arguments_from_address(_address))
- # Respect the old (deprecated) key arguments and environment variables
- # TODO: Remove this section in 0.8 release
- else:
- # Read the environment variables
- env_speechd_host = os.environ.get("SPEECHD_HOST")
- try:
- env_speechd_port = int(os.environ.get("SPEECHD_PORT"))
- except:
- env_speechd_port = None
- env_speechd_socket_path = os.environ.get("SPEECHD_SOCKET")
- # Prefer old (deprecated) function arguments, but if
- # not specified and old (deprecated) environment variable
- # is set, use the value of the environment variable
- if method:
- connection_args['method'] = method
- if port:
- connection_args['port'] = port
- elif env_speechd_port:
- connection_args['port'] = env_speechd_port
- if socket_path:
- connection_args['socket_path'] = socket_path
- elif env_speechd_socket_path:
- connection_args['socket_path'] = env_speechd_socket_path
- self._connect_with_autospawn(connection_args, autospawn)
- self._initialize_connection(user, name, component)
-
- def _connect_with_autospawn(self, connection_args, autospawn):
- """Establish new connection (and/or autospawn server)"""
- try:
- self._conn = _SSIP_Connection(**connection_args)
- except SSIPCommunicationError, ce:
- # Suppose server might not be running, try the autospawn mechanism
- if autospawn != False:
- # Autospawn is however not guaranteed to start the server. The
server
- # will decide, based on it's configuration, whether to honor
the request.
- try:
- self._server_spawn(connection_args)
- except SpawnError, se:
- ce.set_additional_exception(se)
- raise ce
- self._conn = _SSIP_Connection(**connection_args)
- else:
- raise
-
- def _initialize_connection(self, user, name, component):
- """Initialize connection -- Set client name, get id, register
callbacks etc."""
- full_name = '%s:%s:%s' % (user, name, component)
- self._conn.send_command('SET', Scope.SELF, 'CLIENT_NAME', full_name)
- code, msg, data = self._conn.send_command('HISTORY', 'GET',
'CLIENT_ID')
- self._client_id = int(data[0])
- self._callback_handler = _CallbackHandler(self._client_id)
- self._conn.set_callback(self._callback_handler)
- for event in (CallbackType.INDEX_MARK,
- CallbackType.BEGIN,
- CallbackType.END,
- CallbackType.CANCEL,
- CallbackType.PAUSE,
- CallbackType.RESUME):
- self._conn.send_command('SET', 'self', 'NOTIFICATION', event, 'on')
-
- def _connection_arguments_from_address(self, address):
- """Parse a Speech Dispatcher address line and return a dictionary
- of connection arguments"""
- connection_args = {}
- address_params = address.split(":")
- try:
- _method = address_params[0]
- except:
- raise SSIPCommunicationErrror("Wrong format of server address")
- connection_args['communication_method'] = _method
- if _method == CommunicationMethod.UNIX_SOCKET:
- try:
- connection_args['socket_path'] = address_params[1]
- except IndexError:
- pass # The additional parameters was not set, let's stay with
defaults
- elif _method == CommunicationMethod.INET_SOCKET:
- try:
- connection_args['host'] = address_params[1]
- connection_args['port'] = int(address_params[2])
- except ValueError: # Failed conversion to int
- raise SSIPCommunicationError("Third parameter of inet_socket
address must be a port number")
- except IndexError:
- pass # The additional parameters was not set, let's stay with
defaults
- else:
- raise SSIPCommunicationError("Unknown communication method in
address.");
- return connection_args
-
- def __del__(self):
- """Close the connection"""
- self.close()
-
- def _server_spawn(self, connection_args):
- """Attempts to spawn the speech-dispatcher server."""
- # Check whether we are not connecting to a remote host
- # TODO: This is a hack. inet sockets specific code should
- # belong to _SSIPConnection. We do not however have an _SSIPConnection
- # yet.
- if connection_args['communication_method'] == 'inet_socket':
- addrinfos = socket.getaddrinfo(connection_args['host'],
- connection_args['port'])
- # Check resolved addrinfos for presence of localhost
- ip_addresses = [addrinfo[4][0] for addrinfo in addrinfos]
- localhost=False
- for ip in ip_addresses:
- if ip.startswith("127.") or ip == "::1":
- connection_args['host'] = ip
- localhost=True
- if not localhost:
- # The hostname didn't resolve on localhost in neither case,
- # do not spawn server on localhost...
- raise SpawnError(
- "Can't start server automatically (autospawn), requested
address %s "
- "resolves on %s which seems to be a remote host. You must
start the "
- "server manually or choose another connection address." %
(connection_args['host'],
-
str(ip_addresses),))
- if os.path.exists(paths.SPD_SPAWN_CMD):
- connection_params = []
- for param, value in connection_args.items():
- if param not in ["host",]:
- connection_params += ["--"+param.replace("_","-"),
str(value)]
-
- server = subprocess.Popen([paths.SPD_SPAWN_CMD,
"--spawn"]+connection_params,
- stdin=None, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
- stdout_reply, stderr_reply = server.communicate()
- retcode = server.wait()
- if retcode != 0:
- raise SpawnError("Server refused to autospawn, stating this
reason: %s" % (stderr_reply,))
- return server.pid
- else:
- raise SpawnError("Can't find Speech Dispatcher spawn command %s"
- % (paths.SPD_SPAWN_CMD,))
-
- def set_priority(self, priority):
- """Set the priority category for the following messages.
-
- Arguments:
- priority -- one of the 'Priority' constants.
-
- """
- assert priority in (Priority.IMPORTANT, Priority.MESSAGE,
- Priority.TEXT, Priority.NOTIFICATION,
- Priority.PROGRESS), priority
- self._conn.send_command('SET', Scope.SELF, 'PRIORITY', priority)
-
- def set_data_mode(self, value):
- """Set the data mode for further speech commands.
-
- Arguments:
- value - one of the constants defined by the DataMode class.
-
- """
- if value == DataMode.SSML:
- ssip_val = 'on'
- elif value == DataMode.TEXT:
- ssip_val = 'off'
- else:
- raise ValueError(
- 'Value "%s" is not one of the constants from the DataMode
class.' % \
- value)
- self._conn.send_command('SET', Scope.SELF, 'SSML_MODE', ssip_val)
-
- def speak(self, text, callback=None, event_types=None):
- """Say given message.
-
- Arguments:
- text -- message text to be spoken. This may be either a UTF-8
- encoded byte string or a Python unicode string.
- callback -- a callback handler for asynchronous event notifications.
- A callable object (function) which accepts one positional argument
- `type' and one keyword argument `index_mark'. See below for more
- details.
- event_types -- a tuple of event types for which the callback should
- be called. Each item must be one of `CallbackType' constants.
- None (the default value) means to handle all event types. This
- argument is irrelevant when `callback' is not used.
-
- The callback function will be called whenever one of the events occurs.
- The event type will be passed as argument. Its value is one of the
- `CallbackType' constants. In case of an index mark event, additional
- keyword argument `index_mark' will be passed and will contain the index
- mark identifier as specified within the text.
-
- The callback function should not perform anything complicated and is
- not allowed to issue any further SSIP client commands. An attempt to
- do so would lead to a deadlock in SSIP communication.
-
- This method is non-blocking; it just sends the command, given
- message is queued on the server and the method returns immediately.
-
- """
- self._conn.send_command('SPEAK')
- if isinstance(text, unicode):
- text = text.encode('utf-8')
- result = self._conn.send_data(text)
- if callback:
- msg_id = int(result[2][0])
- # TODO: Here we risk, that the callback arrives earlier, than we
- # add the item to `self._callback_handler'. Such a situation will
- # lead to the callback being ignored.
- self._callback_handler.add_callback(msg_id, callback, event_types)
- return result
-
- def char(self, char):
- """Say given character.
-
- Arguments:
- char -- a character to be spoken. Either a Python unicode string or
- a UTF-8 encoded byte string.
-
- This method is non-blocking; it just sends the command, given
- message is queued on the server and the method returns immediately.
-
- """
- if isinstance(char, unicode):
- char = char.encode('utf-8')
- self._conn.send_command('CHAR', char.replace(' ', 'space'))
-
- def key(self, key):
- """Say given key name.
-
- Arguments:
- key -- the key name (as defined in SSIP); string.
-
- This method is non-blocking; it just sends the command, given
- message is queued on the server and the method returns immediately.
-
- """
- self._conn.send_command('KEY', key)
-
- def sound_icon(self, sound_icon):
- """Output given sound_icon.
-
- Arguments:
- sound_icon -- the name of the sound icon as defined by SSIP; string.
-
- This method is non-blocking; it just sends the command, given message
- is queued on the server and the method returns immediately.
-
- """
- self._conn.send_command('SOUND_ICON', sound_icon)
-
- def cancel(self, scope=Scope.SELF):
- """Immediately stop speaking and discard messages in queues.
-
- Arguments:
- scope -- see the documentation of this class.
-
- """
- self._conn.send_command('CANCEL', scope)
-
-
- def stop(self, scope=Scope.SELF):
- """Immediately stop speaking the currently spoken message.
-
- Arguments:
- scope -- see the documentation of this class.
-
- """
- self._conn.send_command('STOP', scope)
-
- def pause(self, scope=Scope.SELF):
- """Pause speaking and postpone other messages until resume.
-
- This method is non-blocking. However, speaking can continue for a
- short while even after it's called (typically to the end of the
- sentence).
-
- Arguments:
- scope -- see the documentation of this class.
-
- """
- self._conn.send_command('PAUSE', scope)
-
- def resume(self, scope=Scope.SELF):
- """Resume speaking of the currently paused messages.
-
- This method is non-blocking. However, speaking can continue for a
- short while even after it's called (typically to the end of the
- sentence).
-
- Arguments:
- scope -- see the documentation of this class.
-
- """
- self._conn.send_command('RESUME', scope)
-
- def list_output_modules(self):
- """Return names of all active output modules as a tuple of strings."""
- code, msg, data = self._conn.send_command('LIST', 'OUTPUT_MODULES')
- return data
-
- def list_synthesis_voices(self):
- """Return names of all available voices for the current output module.
-
- Returns a tuple of tripplets (name, language, dialect).
-
- 'name' is a string, 'language' is an ISO 639-1 Alpha-2 language code
- and 'dialect' is a string. Language and dialect may be None.
-
- """
- try:
- code, msg, data = self._conn.send_command('LIST',
'SYNTHESIS_VOICES')
- except SSIPCommandError:
- return ()
- def split(item):
- name, lang, dialect = tuple(item.rsplit(' ', 3))
- return (name, lang or None, dialect or None)
- return tuple([split(item) for item in data])
-
- def set_language(self, language, scope=Scope.SELF):
- """Switch to a particular language for further speech commands.
-
- Arguments:
- language -- two letter language code according to RFC 1776 as string.
- scope -- see the documentation of this class.
-
- """
- assert isinstance(language, str) and len(language) == 2
- self._conn.send_command('SET', scope, 'LANGUAGE', language)
-
- def set_output_module(self, name, scope=Scope.SELF):
- """Switch to a particular output module.
-
- Arguments:
- name -- module (string) as returned by 'list_output_modules()'.
- scope -- see the documentation of this class.
-
- """
- self._conn.send_command('SET', scope, 'OUTPUT_MODULE', name)
-
- def set_pitch(self, value, scope=Scope.SELF):
- """Set the pitch for further speech commands.
-
- Arguments:
- value -- integer value within the range from -100 to 100, with 0
- corresponding to the default pitch of the current speech synthesis
- output module, lower values meaning lower pitch and higher values
- meaning higher pitch.
- scope -- see the documentation of this class.
-
- """
- assert isinstance(value, int) and -100 <= value <= 100, value
- self._conn.send_command('SET', scope, 'PITCH', value)
-
- def set_rate(self, value, scope=Scope.SELF):
- """Set the speech rate (speed) for further speech commands.
-
- Arguments:
- value -- integer value within the range from -100 to 100, with 0
- corresponding to the default speech rate of the current speech
- synthesis output module, lower values meaning slower speech and
- higher values meaning faster speech.
- scope -- see the documentation of this class.
-
- """
- assert isinstance(value, int) and -100 <= value <= 100
- self._conn.send_command('SET', scope, 'RATE', value)
-
- def set_volume(self, value, scope=Scope.SELF):
- """Set the speech volume for further speech commands.
-
- Arguments:
- value -- integer value within the range from -100 to 100, with 100
- corresponding to the default speech volume of the current speech
- synthesis output module, lower values meaning softer speech.
- scope -- see the documentation of this class.
-
- """
- assert isinstance(value, int) and -100 <= value <= 100
- self._conn.send_command('SET', scope, 'VOLUME', value)
-
- def set_punctuation(self, value, scope=Scope.SELF):
- """Set the punctuation pronounciation level.
-
- Arguments:
- value -- one of the 'PunctuationMode' constants.
- scope -- see the documentation of this class.
-
- """
- assert value in (PunctuationMode.ALL, PunctuationMode.SOME,
- PunctuationMode.NONE), value
- self._conn.send_command('SET', scope, 'PUNCTUATION', value)
-
- def set_spelling(self, value, scope=Scope.SELF):
- """Toogle the spelling mode or on off.
-
- Arguments:
- value -- if 'True', all incomming messages will be spelled
- instead of being read as normal words. 'False' switches
- this behavior off.
- scope -- see the documentation of this class.
-
- """
- assert value in [True, False]
- if value == True:
- self._conn.send_command('SET', scope, 'SPELLING', "on")
- else:
- self._conn.send_command('SET', scope, 'SPELLING', "off")
-
- def set_cap_let_recogn(self, value, scope=Scope.SELF):
- """Set capital letter recognition mode.
-
- Arguments:
- value -- one of 'none', 'spell', 'icon'. None means no signalization
- of capital letters, 'spell' means capital letters will be spelled
- with a syntetic voice and 'icon' means that the capital-letter icon
- will be prepended before each capital letter.
- scope -- see the documentation of this class.
-
- """
- assert value in ("none", "spell", "icon")
- self._conn.send_command('SET', scope, 'CAP_LET_RECOGN', value)
-
- def set_voice(self, value, scope=Scope.SELF):
- """Set voice by a symbolic name.
-
- Arguments:
- value -- one of the SSIP symbolic voice names: 'MALE1' .. 'MALE3',
- 'FEMALE1' ... 'FEMALE3', 'CHILD_MALE', 'CHILD_FEMALE'
- scope -- see the documentation of this class.
-
- Symbolic voice names are mapped to real synthesizer voices in the
- configuration of the output module. Use the method
- 'set_synthesis_voice()' if you want to work with real voices.
-
- """
- assert isinstance(value, str) and \
- value.lower() in ("male1", "male2", "male3", "female1",
- "female2", "female3", "child_male",
- "child_female")
- self._conn.send_command('SET', scope, 'VOICE', value)
-
- def set_synthesis_voice(self, value, scope=Scope.SELF):
- """Set voice by its real name.
-
- Arguments:
- value -- voice name as returned by 'list_synthesis_voices()'
- scope -- see the documentation of this class.
-
- """
- self._conn.send_command('SET', scope, 'SYNTHESIS_VOICE', value)
-
- def set_pause_context(self, value, scope=Scope.SELF):
- """Set the amount of context when resuming a paused message.
-
- Arguments:
- value -- a positive or negative value meaning how many chunks of data
- after or before the pause should be read when resume() is executed.
- scope -- see the documentation of this class.
-
- """
- assert isinstance(value, int)
- self._conn.send_command('SET', scope, 'PAUSE_CONTEXT', value)
-
- def set_debug(self, val):
- """Switch debugging on and off. When switched on,
- debugging files will be created in the chosen destination
- (see set_debug_destination()) for Speech Dispatcher and all
- its running modules. All logging information will then be
- written into these files with maximal verbosity until switched
- off. You should always first call set_debug_destination.
-
- The intended use of this functionality is to switch debuging
- on for a period of time while the user will repeat the behavior
- and then send the logs to the appropriate bug-reporting place.
-
- Arguments:
- val -- a boolean value determining whether debugging
- is switched on or off
- scope -- see the documentation of this class.
-
- """
- assert isinstance(val, bool)
- if val == True:
- ssip_val = "ON"
- else:
- ssip_val = "OFF"
-
- self._conn.send_command('SET', scope.ALL, 'DEBUG', ssip_val)
-
-
- def set_debug_destination(self, path):
- """Set debug destination.
-
- Arguments:
- path -- path (string) to the directory where debuging
- files will be created
- scope -- see the documentation of this class.
-
- """
- assert isinstance(val, string)
-
- self._conn.send_command('SET', scope.ALL, 'DEBUG_DESTINATION', val)
-
- def block_begin(self):
- """Begin an SSIP block.
-
- See SSIP documentation for more details about blocks.
-
- """
- self._conn.send_command('BLOCK', 'BEGIN')
-
- def block_end(self):
- """Close an SSIP block.
-
- See SSIP documentation for more details about blocks.
-
- """
- self._conn.send_command('BLOCK', 'END')
-
- def close(self):
- """Close the connection to Speech Dispatcher."""
- if hasattr(self, '_conn'):
- self._conn.close()
- del self._conn
-
-
-class Client(SSIPClient):
- """A DEPRECATED backwards-compatible API.
-
- This Class is provided only for backwards compatibility with the prevoius
- unofficial API. It will be removed in future versions. Please use either
- 'SSIPClient' or 'Speaker' interface instead. As deprecated, the API is no
- longer documented.
-
- """
- def __init__(self, name=None, client=None, **kwargs):
- name = name or client or 'python'
- super(Client, self).__init__(name, **kwargs)
-
- def say(self, text, priority=Priority.MESSAGE):
- self.set_priority(priority)
- self.speak(text)
-
- def char(self, char, priority=Priority.TEXT):
- self.set_priority(priority)
- super(Client, self).char(char)
-
- def key(self, key, priority=Priority.TEXT):
- self.set_priority(priority)
- super(Client, self).key(key)
-
- def sound_icon(self, sound_icon, priority=Priority.TEXT):
- self.set_priority(priority)
- super(Client, self).sound_icon(sound_icon)
-
-
-class Speaker(SSIPClient):
- """Extended Speech Dispatcher Interface.
-
- This class provides an extended intercace to Speech Dispatcher
- functionality and tries to hide most of the lower level details of SSIP
- (such as a more sophisticated handling of blocks and priorities and
- advanced event notifications) under a more convenient API.
-
- Please note that the API is not yet stabilized and thus is subject to
- change! Please contact the authors if you plan using it and/or if you have
- any suggestions.
-
- Well, in fact this class is currently not implemented at all. It is just a
- draft. The intention is to hide the SSIP details and provide a generic
- interface practical for screen readers.
-
- """
-
-
-# Deprecated but retained for backwards compatibility
-
-# This class was introduced in 0.7 but later renamed to CommunicationMethod
-class ConnectionMethod(object):
- """Constants describing the possible methods of connection to server.
-
- Retained for backwards compatibility but DEPRECATED. See
CommunicationMethod."""
- UNIX_SOCKET = 'unix_socket'
- """Unix socket communication using a filesystem path"""
- INET_SOCKET = 'inet_socket'
- """Inet socket communication using a host and port"""
diff --git a/src/python/speechd/paths.py.in b/src/python/speechd/paths.py.in
deleted file mode 100644
index a2a9696..0000000
--- a/src/python/speechd/paths.py.in
+++ /dev/null
@@ -1 +0,0 @@
-SPD_SPAWN_CMD = "@bindir@/speech-dispatcher"
diff --git a/src/python/speechd_config/Makefile.am
b/src/python/speechd_config/Makefile.am
deleted file mode 100644
index 8b57ad8..0000000
--- a/src/python/speechd_config/Makefile.am
+++ /dev/null
@@ -1,27 +0,0 @@
-## Process this file with automake to produce Makefile.in
-
-dist_snddata_DATA = test.wav
-
-dist_bin_SCRIPTS = spd-conf
-
-speechd_pythondir = $(pyexecdir)/speechd_config
-speechd_python_PYTHON = __init__.py config.py
-nodist_speechd_python_PYTHON = paths.py
-
-paths_edit = sed \
- -e "s:address@hidden@]:$(spdconforigdir):" \
- -e "s:address@hidden@]:$(spdconfdir):" \
- -e "s:address@hidden@]:$(snddatadir):" \
- -e "s:address@hidden@]:$(spddesktopconforigdir):"
-
-paths.py: Makefile
- rm -f $@
- srcdir=; \
- test -f ./address@hidden || srcdir=$(srcdir)/; \
- $(paths_edit) address@hidden > $@
-
-paths.py: $(srcdir)/paths.py.in
-
-CLEANFILES = paths.py
-
-EXTRA_DIST = paths.py.in
diff --git a/src/python/speechd_config/__init__.py
b/src/python/speechd_config/__init__.py
deleted file mode 100644
index 6f7eef9..0000000
--- a/src/python/speechd_config/__init__.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright (C) 2008 Brailcom, o.p.s.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-
-from config import *
-
diff --git a/src/python/speechd_config/config.py
b/src/python/speechd_config/config.py
deleted file mode 100644
index 26bdccd..0000000
--- a/src/python/speechd_config/config.py
+++ /dev/null
@@ -1,924 +0,0 @@
-# config.py - A simple dialog based tool for basic configuration of
-# Speech Dispatcher and problem diagnostics.
-#
-# Copyright (C) 2008, 2010 Brailcom, o.p.s.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-
-import sys
-import os
-import shutil
-import fileinput
-import socket
-import time
-import datetime
-
-from optparse import OptionParser
-
-# Configuration and sound data paths
-import paths
-
-def report(msg):
- """Output information messages for the user on stdout
- and if desired, by espeak synthesis"""
- print msg
- if options.use_espeak_synthesis:
- os.system("espeak \"" + msg + "\"")
-
-def input_audio_icon():
- """Produce a sound for the event 'input requested' used in question()"""
- if options.use_espeak_synthesis:
- os.system("espeak \"Type in\"")
-
-def question(text, default):
- """Ask a simple question and suggest the default value"""
-
- while 1:
- if isinstance(default, bool):
- if default == True:
- default_str = "yes"
- else:
- default_str = "no"
- else:
- default_str = str(default)
- report(text + " ["+default_str+"] :")
- input_audio_icon()
-
- if not options.dont_ask:
- str_inp = raw_input(">")
-
- # On plain enter, return default
- if options.dont_ask or (len(str_inp) == 0):
- return default
- # If a value was typed, check it and convert it
- elif isinstance(default, bool):
- if str_inp in ["yes","y", "Y", "true","t", "1"]:
- return True
- elif str_inp in ["no", "n", "N", "false", "f", "0"]:
- return False
- else:
- report ("Unknown answer (type 'yes' or 'no')")
- continue
- elif isinstance(default, int):
- return int(str_inp)
- elif isinstance(default, str):
- return str_inp
- else:
- raise TypeError("Invalid type for the default value")
-
-def question_with_suggested_answers(text, default, suggest):
- """Ask a question with suggested answers. If the answer typed is not
- in 'suggest', the user is notified and given an opportunity to correct
- his choice"""
-
- reply = question(text, default)
- while reply not in suggest:
- report("""The value you have chosen is not among the suggested values.
-You have chosen '%s'.""" % reply)
- report("The suggested values are " + str(suggest))
- correct = question("Do you want to correct your answer?", True)
- if correct == True:
- reply = question(text, default)
- else:
- return reply
- return reply
-
-def question_with_required_answers(text, default, required):
- """Ask a question and repeat it until the answer typed in is in
'required'"""
-
- reply = question(text, default)
- while reply not in required:
- report("You have chosen '%s'. Please choose one of %s" % (reply,
str(required)))
- reply = question(text, default)
- return reply
-
-class Options(object):
- """Configuration for spdconf"""
-
- _conf_options = \
- {
- 'create_user_configuration':
- {
- 'descr' : "Create Speech Dispatcher configuration for the given
user",
- 'doc' : None,
- 'type' : bool,
- 'default' : False,
- 'command_line' : ('-u', '--create-user-conf'),
- },
- 'config_basic_settings_user':
- {
- 'descr' : "Configure basic settings in user configuration",
- 'doc' : None,
- 'type' : bool,
- 'default' : False,
- 'command_line' : ('-c', '--config-basic-settings-user'),
- },
- 'config_basic_settings_system':
- {
- 'descr' : "Configure basic settings in system-wide configuration",
- 'doc' : None,
- 'type' : bool,
- 'default' : False,
- 'command_line' : ('-C', '--config-basic-settings-system'),
- },
- 'diagnostics':
- {
- 'descr' : "Diagnose problems with the current setup",
- 'doc' : None,
- 'type' : bool,
- 'default' : False,
- 'command_line' : ('-d', '--diagnostics'),
- },
- 'test_spd_say':
- {
- 'descr' : "Test connection to Speech Dispatcher using spd-say",
- 'doc' : None,
- 'type' : bool,
- 'default' : False,
- 'command_line' : ('-s', '--test-spd-say'),
- },
- 'test_festival':
- {
- 'descr' : "Test whether Festival works as a server",
- 'doc' : None,
- 'type' : bool,
- 'default' : False,
- 'command_line' : ('', '--test-festival'),
- },
- 'test_espeak':
- {
- 'descr' : "Test whether Espeak works as a standalone binary",
- 'doc' : None,
- 'type' : bool,
- 'default' : False,
- 'command_line' : ('', '--test-espeak'),
- },
- 'test_alsa':
- {
- 'descr' : "Test ALSA audio",
- 'doc' : None,
- 'type' : bool,
- 'default' : False,
- 'command_line' : ('', '--test-alsa'),
- },
-
- 'test_pulse':
- {
- 'descr' : "Test Pulse Audio",
- 'doc' : None,
- 'type' : bool,
- 'default' : False,
- 'command_line' : ('', '--test-pulse'),
- },
-
- 'use_espeak_synthesis':
- {
- 'descr' : "Use espeak to synthesize messages",
- 'doc' : None,
- 'type' : bool,
- 'default' : False,
- 'command_line' : ('-e', '--espeak'),
- },
- 'dont_ask':
- {
- 'descr' : "Do not ask any questions, allways use default values",
- 'doc' : None,
- 'type' : bool,
- 'default' : False,
- 'command_line' : ('-n', '--dont-ask'),
- },
- 'debug':
- {
- 'descr' : "Debug a problem and generate a report",
- 'doc' : None,
- 'type' : bool,
- 'default' : False,
- 'command_line' : ('-D', '--debug'),
- },
- }
-
- def __init__(self):
- usage = """%prog [options]
-A simple dialog based tool for basic configuration of Speech Dispatcher
-and problem diagnostics."""
- self.cmdline_parser = OptionParser(usage)
-
- for option, definition in self._conf_options.iteritems():
- # Set object attributes to default values
- def_val = definition.get('default', None)
- setattr(self, option, def_val)
-
- # Fill in the cmdline_parser object
- if definition.has_key('command_line'):
- descr = definition.get('descr', None)
- type = definition.get('type', None)
-
- if definition.has_key('arg_map'):
- type, map = definition['arg_map']
- if type == str:
- type_str = 'string'
- elif type == int:
- type_str = 'int'
- elif type == float:
- type_str = 'float'
- elif type == bool:
- type_str = None
- else:
- raise TypeError("Unknown type")
-
- if type != bool:
- self.cmdline_parser.add_option(type=type_str, dest=option,
- help=descr,
- *definition['command_line'])
- else: # type == bool
- self.cmdline_parser.add_option(action="store_true",
dest=option,
- help=descr,
- *definition['command_line'])
-
- # Set options according to command line flags
- (cmdline_options, args) = self.cmdline_parser.parse_args()
-
- for option, definition in self._conf_options.iteritems():
- val = getattr(cmdline_options, option, None)
- if val != None:
- if definition.has_key('arg_map'):
- former_type, map = definition['arg_map']
- try:
- val = map[val]
- except KeyError:
- raise ValueError("Invalid option value: " +
str(val))
-
- setattr(self, option, val)
-
- #if len(args) != 0:
- # raise ValueError("This command takes no positional arguments
(without - or -- prefix)")
-
-class Tests:
- """Tests of functionality of Speech Dispatcher and its dependencies
- and methods for determination of proper paths"""
-
- def __init__(self):
- self.festival_socket = None
-
- def user_speechd_dir(self):
- """Return user Speech Dispatcher configuration and logging directory"""
- return os.path.expanduser(os.path.join('~', '.speech-dispatcher'))
-
- def user_speechd_dir_exists(self):
- """Determine whether user speechd directory exists"""
- return os.path.exists(self.user_speechd_dir())
-
- def user_conf_dir(self):
- """Return user configuration directory"""
- return os.path.join(self.user_speechd_dir(), "conf")
-
- def system_conf_dir(self):
- """Determine system configuration directory"""
- return paths.SPD_CONF_PATH
-
- def user_conf_dir_exists(self):
- """Determine whether user configuration directory exists"""
- return os.path.exists(self.user_conf_dir())
-
- def festival_connect(self, host="localhost", port=1314):
- """
- Try to connect to festival and determine whether it is possible.
- On success self.festival_socket is initialized with the openned socket.
- """
- self.festival_socket = socket.socket(socket.AF_INET,
socket.SOCK_STREAM)
- try:
- self.festival_socket.connect((socket.gethostbyname(host), port))
- except socket.error, (num, reson):
- report("""ERROR: It was not possible to connect to Festival on the
-given host and port. Connection failed with error %d : %s .""" % (num, reson))
- report("""Hint: Most likely, your Festival server is not running
now
-or not at the default port %d.
-
-Try /etc/init.d/festival start or run 'festival --server' from the command
line.""" % port)
- return False
- return True
-
- def festival_with_freebsoft_utils(self):
- """Test whether festival works and contains working
festival-freebsoft-utils.
- """
- if not self.festival_socket:
- if not self.festival_connect():
- return False
- self.festival_socket.send("(require 'speech-dispatcher)\n")
- reply = self.festival_socket.recv(1024)
- if "LP" in reply:
- report("Festival contains freebsoft-utils.")
- return True
- else:
- report("""ERROR: Your Festival server is working but it doesn't
seem
-to load festival-freebsoft-utils. You need to install festival-freebsoft-utils
-to be able to use Festival with Speech Dispatcher.""")
- return False
-
- def python_speechd_in_path(self):
- """Try whether python speechd library is importable"""
- try:
- import speechd
- except:
- report("""Python can't find the Speech Dispatcher library.
-Is it installed? This won't prevent Speech Dispatcher to work, but no
-Python applications like Orca will be able to use it.
-Search for package like python-speechd, download and install it""")
- return False
- return True
-
- def audio_try_play(self, type):
- """Try to play a sound through the standard playback utility for the
- given audio method."""
- wavfile = os.path.join(paths.SPD_SOUND_DATA_PATH,"test.wav")
-
- if type == 'alsa':
- cmd = "aplay" + " " + wavfile
- elif type == 'pulse':
- cmd = "paplay" + " " + wavfile
- else:
- raise NotImplementedError("Test for this audio system is not
implemented")
-
- try:
- ret = os.system(cmd)
- except:
- report("""Can't execute the %s command, this audio output might
not be available
-on your system, but it might also be a false warning. Please make
-sure your audio is working.""")
- reply = question("Are you sure that %s audio is working?" % type,
False)
- return reply
-
- if ret:
- report("Can't play audio via\n %s" % cmd)
- report("""Your audio doesn't seem to work, please fix audio first
or choose
-a different method.""")
- return False
-
-
- reply = question("Did you hear the sound?", True)
-
- if not reply:
- report("""Please examine the above output from the sound playback
-utility. If everything seems right, are you sure your audio is loud enough and
-not muted in the mixer? Please fix your audio system first or choose a
different
-audio output method in configuration.""")
- return False
- else:
- report("Audio output '%s' works" % type)
- return True
-
- def test_spd_say(self):
- """Test Speech Dispatcher using spd_say"""
-
- report("Testing Speech Dispatcher using spd_say")
-
- while True:
- try:
- ret = os.system("spd-say -P important \"Speech Dispatcher
works\"")
- except:
- report("""Can't execute the spd-say binary,
-it is very likely that Speech Dispatcher is not installed.""")
- return False
- hearing_test = question("Did you hear the message about Speech
Dispatcher working?", True)
- if hearing_test:
- report("Speech Dispatcher is installed and working!")
- return True
- else:
- report("Speech Dispatcher is installed but there is some
problem")
- return False
-
- def test_festival(self):
- """Test whether Festival works as a server"""
- report("Testing whether Festival works as a server")
-
- ret = self.festival_with_freebsoft_utils()
- if not ret:
- report("Festival server is not working.")
- return False
- else:
- report("Festival server seems to work correctly")
- return True
-
- def test_espeak(self):
- """Test the espeak utility"""
-
- report("Testing whether Espeak works")
-
- while True:
- try:
- os.system("espeak \"Espeak seems to work\"")
- except:
- report("""Can't execute the espeak binary, it is likely that
espeak
-is not installed.""")
- return False
-
- report("Espeak is installed")
- return True
-
- def test_alsa(self):
- """Test ALSA sound output"""
- report("Testing ALSA sound output")
- return self.audio_try_play(type='alsa')
-
- def test_pulse(self):
- """Test Pulse Audio sound output"""
- report("Testing PULSE sound output")
- return self.audio_try_play(type='pulse')
-
- def diagnostics(self, speechd_running = True, output_modules=[],
audio_output=[]):
-
- """Perform a complete diagnostics"""
- working_modules = []
- working_audio = []
-
- if speechd_running:
- # Test whether Speech Dispatcher works
- if self.test_spd_say():
- spd_say_working = True
- skip = question("Speech Dispatcher works. Do you want to skip
other tests?",
- True)
- if skip:
- return {'spd_say_working': True}
- else:
- spd_say_working = False
- else:
- spd_say_working = False
-
- if not spd_say_working:
- if not question("""
-Speech Dispatcher isn't running or we can't connect to it (see above),
-do you want to proceed with other tests? (They can help to determine
-what is wrong)""", True):
- return {'spd_say_working': False}
-
- def decide_to_test(identifier, name, listing):
- """"Ask the user whether to test a specific capability"""
- if ((identifier in listing)
- or (not len(listing)
- and question("Do you want to test the %s now?" % name,
True))):
- return True
- else:
- return False
-
- if decide_to_test('festival', "Festival synthesizer", output_modules):
- if self.test_festival():
- working_modules += ["festival"]
-
- if decide_to_test('espeak', "Espeak synthesizer", output_modules):
- if self.test_espeak():
- working_modules += ["espeak"]
-
- if decide_to_test('alsa', "ALSA sound system", audio_output):
- if self.test_alsa():
- working_audio += ["alsa"]
-
- if decide_to_test('pulse', "Pulse Audio sound system", audio_output):
- if self.test_pulse():
- working_audio += ["pulse"]
-
- report("Testing whether Python Speech Dispatcher library is in path
and importable")
- python_speechd_working = test.python_speechd_in_path()
-
- return {'spd_say_working': spd_say_working,
- 'audio': working_audio,
- 'synthesizers': working_modules,
- 'python_speechd' : python_speechd_working}
-
- def write_diagnostics_results(self, results):
- """Write out diagnostics results using report()"""
-
- report("""
-
-Diagnostics results:""")
- if results.has_key('spd_say_working'):
- if results['spd_say_working']:
- report("Speech Dispatcher is working")
- else:
- report("Speech Dispatcher not working through spd-say")
- if results.has_key('synthesizers'):
- report("Synthesizers that were tested and seem to work: %s" %
- str(results['synthesizers']))
- if results.has_key('audio'):
- report("Audio systems that were tested and seem to work: %s" %
- str(results['audio']))
- if results.has_key('python_speechd'):
- if(results['python_speechd']):
- report("Python Speech Dispatcher module is importable")
- else:
- report("""Python Speech Dispatcher module not importable.
-Either not installed or not in path.""")
- report("End of diagnostics results")
-
- def user_configuration_seems_complete(self):
- """Decide if the user configuration seems reasonably complete"""
- if not os.path.exists(os.path.join(self.user_conf_dir(),
"speechd.conf")):
- return False
-
- if not len(os.listdir(self.user_conf_dir())) > 2:
- return False
-
- if not os.path.exists(os.path.join(self.user_conf_dir(), "modules")):
- return False
-
- if not os.path.exists(os.path.join(self.user_conf_dir(), "clients")):
- return False
-
- return True
-
- def debug_and_report(self, type = None):
- """Start Speech Dispatcher in debugging mode, collect the debugging
output
- and offer to send it to the developers"""
-
- report("Starting collecting debugging output, configuration and
logfiles")
-
- if not type:
- type = question_with_required_answers("""
-Do you want to debug 'system' or 'user' Speech Dispatcher?""",
- 'user', ['user', 'system'])
-
- # Try to kill running Speech Dispatcher
- reply = question("""It is necessary to kill the currently running
Speech Dispatcher
-processes. Do you want to do it now?""", True)
- if reply:
- os.system("killall speech-dispatcher")
- else:
- report("""
-You decided not to kill running Speech Dispatcher processes.
-Please make sure your Speech Dispatcher is not running now.""")
- reply = question("Is your Speech Dispatcher not running now?",
True)
- if not reply:
- report("Can't continue, please stop your Speech Dispatcher and
try again")
-
- time.sleep(2)
-
- # All debugging files are written to TMPDIR/speech-dispatcher/
- if os.environ.has_key('TMPDIR'):
- tmpdir = os.environ['TMPDIR']
- else:
- tmpdir = "/tmp/"
- debugdir_path = os.path.join(tmpdir, "speechd-debug")
- date = datetime.date.today()
- debugarchive_path = os.path.join(tmpdir,
"speechd-debug-%d-%d-%d.tar.gz" %
- (date.day, date.month, date.year))
-
- # Start Speech Dispatcher with debugging enabled
- if type == 'user':
- report("Speech Dispatcher will be started now in debugging mode")
- speechd_started = not os.system("speech-dispatcher -D")
- configure_directory = test.user_conf_dir()
- else:
- report("Warning: You must be root or under sudo to do this.")
- report("""
-Please start your system Speech Dispatcher now with parameter '-D'""")
- reply = question("Is your Speech Dispatcher running now?", True)
- if reply:
- speechd_started = True
- else:
- report("Can't continue")
- configure_directory = test.system_conf_dir()
- time.sleep(2)
-
- if not speechd_started:
- reply = question("Speech Dispatcher failed to start, continuing
anyway")
-
- report("Trying to speak some messages")
- ret = os.system("spd-say \"Speech Dispatcher debugging 1\"")
- if not ret:
- os.system("spd-say \"Speech Dispatcher debugging 2\"")
- os.system("spd-say \"Speech Dispatcher debugging 3\"")
- else:
- report("Can't test Speech Dispatcher connection, can't connect")
-
- report("Please wait (about 5 seconds)")
- time.sleep(5)
-
- report("Collecting debugging output and your configuration
information")
-
- os.system("umask 077")
- os.system("tar -cz %s %s > %s" %
- (debugdir_path, configure_directory, debugarchive_path))
- os.system("killall speech-dispatcher")
- os.system("rm -rf %s" % debugdir_path)
-
- report("""
-Please send %s to speechd at bugs.freebsoft.org with
-a short description of what you did. We will get in touch with you soon
-and suggest a solution.""" % debugarchive_path)
-
-test = Tests()
-
-class Configure:
-
- """Setup user configuration and/or set basic options in user/system
configuration"""
-
- default_output_module = None
- default_language = None
- default_audio_method = None
-
- def remove_user_configuration(self):
- """Remove user configuration tree"""
- shutil.rmtree(test.user_conf_dir())
-
- def options_substitute(self, configfile, options):
- """Substitute the given options with given values.
-
- Arguments:
- configfile -- the file path of the configuration file as a string
- options -- a list of tuples (option_name, value)"""
-
- # Parse config file in-place and replace the desired options+values
- for line in fileinput.input(configfile, inplace=True, backup=".bak"):
- # Check if the current line contains any of the desired options
- for opt, value in options.iteritems():
- if opt in line:
- # Now count unknown words and try to judge if this is
- # real configuration or just a comment
- unknown = 0
- for word in line.split():
- if word =='#' or word == '\t':
- continue
- elif word == opt:
- # If a foreign word went before our option
identifier,
- # we are not in code but in comments
- if unknown != 0:
- unknown = 2
- break
- else:
- unknown += 1
-
- # Only consider the line as the actual code when the
keyword
- # is followed by exactly one word value. Otherwise
consider this
- # line as plain comment and leave intact
- if unknown == 1:
- # Convert value into string representation in spd_val
- if isinstance(value, bool):
- if value == True:
- spd_val = "1"
- elif value == False:
- spd_val = "2"
- elif isinstance(value, int):
- spd_val = str(value)
- else:
- spd_val = str(value)
-
- print opt + " " + spd_val
- break
-
- else:
- print line,
-
- def create_user_configuration(self):
- """Create user configuration in the standard location"""
-
- # Ask before touching things that we do not have to!
- if test.user_speechd_dir_exists():
- if test.user_conf_dir_exists():
- if test.user_configuration_seems_complete():
- reply = question(
- """User configuration already exists.
-Do you want to rewrite it with a new one?""", False)
- if reply == False:
- report("Keeping configuration intact and continuing
with settings.")
- return
- else:
- self.remove_user_configuration()
- else:
- reply = question(
- """User configuration already exists, but it seems to
be incomplete.
-Do you want to keep it?""", False)
- if reply == False:
- self.remove_user_configuration()
- else:
- report("Keeping configuration intact and aborting.")
- return
-
- # TODO: Check for permissions on logfiles and pid
- else:
- report("Creating " + test.user_speechd_dir())
- os.mkdir(test.user_speechd_dir())
-
- # Copy the original intact configuration files
- # creating a conf/ subdirectory
- shutil.copytree(paths.SPD_CONF_ORIG_PATH, test.user_conf_dir())
-
- report("User configuration created in %s" % test.user_conf_dir())
-
- def configure_basic_settings(self, type='user'):
- """Ask for basic settings and rewrite them in the configuration file"""
-
- if type == 'user':
- report("Configuring user settings for Speech Dispatcher")
- elif type == 'system':
- report("Warning: You must be root or under sudo to do this.")
- report("Configuring system settings for Speech Dispatcher")
- else:
- raise ValueError("Invalid configuration type")
-
- # Now determine the most important config option
- self.default_output_module = question_with_suggested_answers(
- "Default output module",
- "espeak",
- ["espeak", "flite", "festival", "cicero", "ibmtts"])
-
- self.default_language = question(
- "Default language (two-letter iso language code like \"en\" or
\"cs\")",
- "en")
-
- self.default_audio_method = question_with_suggested_answers(
- "Default audio output method",
- "pulse",
- ["pulse", "alsa", "oss", "pulse,alsa"])
-
- self.default_speech_rate = question(
- "Default speech rate (on the scale of -100..100, 0 is default, 50
is faster, -50 is slower)",
- "0")
-
- self.default_speech_pitch = question(
- "Default speech pitch (on the scale of -100..100, 0 is default, 50
is higher, -50 is lower)",
- "0")
-
- # Substitute given configuration options
- if type == 'user':
- configfile = os.path.join(test.user_conf_dir(), "speechd.conf")
- elif type == 'system':
- configfile = os.path.join(test.system_conf_dir(), "speechd.conf")
-
- self.options_substitute(configfile,
- {"DefaultModule": self.default_output_module,
- "DefaultLanguage": self.default_language,
- "AudioOutputMethod":
self.default_audio_method,
- "DefaultRate": self.default_speech_rate,
- "DefaultPitch": self.default_speech_pitch,
- "DefaultLanguage": self.default_language,
- })
- if type == 'user':
- self.setup_autostart = question(
- """Do you want to have Speech Dispatcher automatically started
from ~/.config/autostart ?
-This is usually not necessary, most applications will start Speech Dispatcher
automatically.""",
- False)
- if self.setup_autostart:
- os.system("""cp %s ~/.config/autostart/""" %
os.path.join(paths.SPD_DESKTOP_CONF_PATH,
-
"speechd.desktop"))
-
- report("""
-Configuration written to %s
-Basic configuration now complete. You might still need to fine tune it by
-manually editing the configuration file above. Especially if you need to
-use special audio settings, non-standard synthesizer ports etc.""" %
configfile)
-
- def speechd_start_user(self):
- """Start Speech Dispatcher in user-mode"""
-
- report("Starting Speech Dispatcher in user-mode")
-
- err = os.system("speech-dispatcher")
- if err:
- report("Can't start Speech Dispatcher. Exited with status %d" %
err)
- reply = question("""Perhaps this is because your Speech Dispatcher
is already running.
-Do you want to kill all running Speech Dispatchers and try again?""", True)
- if reply:
- os.system("killall speech-dispatcher")
- err = os.system("speech-dispatcher")
- if err:
- report("Can't start Speech Dispatcher")
- return False
- else:
- return False
- return True
-
- def speechd_start_system(self):
- """Start Speech Dispatcher in system-mode"""
-
- report("Warning: You must be root or under sudo to do this.")
- report("Starting Speech Dispatcher in system-mode")
-
- reply = question("Is your system using an
/etc/init.d/speech-dispatcher script?",
- True)
- if reply:
- report("Stopping Speech Dispatcher in case any is running already")
- os.system("/etc/init.d/speech-dispatcher stop")
- report("Starting Speech Dispatcher via
/etc/init.d/speech-dispatcher")
- ret = os.system("/etc/init.d/speech-dispatcher start")
- if ret:
- report("Can't start Speech Dispatcher. Exited with status %d"
% ret)
- return False
- else:
- report("""Do not know how to start system Speech Dispatcher,
-you have to start it manually to continue.""")
- reply = question("Have you started Speech Dispatcher now?", True)
- if not reply:
- report("Can't continue")
- return False
- return True
-
- def complete_config(self):
- """Create a complete configuration, run diagnosis and if necessary,
debugging"""
-
- speechd_type = question_with_required_answers(
- "Do you want to create/setup a 'user' or 'system' configuration",
- 'user', ['user', 'system'])
-
- if speechd_type == 'user':
- self.create_user_configuration()
- self.configure_basic_settings(type='user')
- elif speechd_type == 'system':
- self.configure_basic_settings(type='system')
- else:
- raise ValueError("Invalid configuration type")
-
- reply = question("Do you want to start/restart Speech Dispatcher now
and run some tests?", True)
- if not reply:
- report("Your configuration is now done but not tested")
- return
- else:
- if speechd_type == 'user':
- started = self.speechd_start_user()
- elif speechd_type == 'system':
- started = self.speechd_start_system()
-
- if not started:
- report("Your Speech Dispatcher is not running")
-
- result = test.diagnostics(speechd_running = started,
- audio_output=[self.default_audio_method],
- output_modules=[self.default_output_module])
- test.write_diagnostics_results(result)
-
- if not started:
- reply = question("Do you want to run debugging now and send a
request for help to the developers?",
- False)
- if reply:
- test.debug_and_report(type=speechd_type)
-
-
-# Basic objects
-options = Options()
-configure = Configure()
-test = Tests()
-
-
-def main():
-
- report("\nSpeech Dispatcher configuration tool\n")
-
- if options.create_user_configuration:
- # Check for and/or create basic user configuration
- configure.create_user_configuration()
- reply = question("Do you want to continue with basic settings?", True)
- if reply:
- configure.configure_basic_settings(type='user')
- elif options.config_basic_settings_user:
- configure.configure_basic_settings(type='user')
-
- elif options.config_basic_settings_system:
- configure.configure_basic_settings(type='system')
-
- elif options.test_festival:
- test.test_festival()
-
- elif options.test_spd_say:
- test.test_spd_say()
-
- elif options.test_espeak:
- test.test_espeak()
-
- elif options.test_alsa:
- test.audio_try_play(type='alsa')
-
- elif options.test_pulse:
- test.audio_try_play(type='pulse')
-
- elif options.diagnostics:
- ret = test.diagnostics()
- test.write_diagnostics_results(ret)
-
- elif options.debug:
- test.debug_and_report()
-
- else:
- reply = question("Do you want to setup a completely new
configuration?", True)
- if reply:
- configure.complete_config()
- else:
- reply = question("Do you want to run diagnosis of problems?", True)
- if reply:
- ret=test.diagnostics()
- test.write_diagnostics_results(ret)
- else:
- report("""Please run this command again and select what you
want to do
-or read the quick help available through '-h' or '--help'.""")
-
-if __name__ == "__main__":
- sys.exit(main())
diff --git a/src/python/speechd_config/paths.py.in
b/src/python/speechd_config/paths.py.in
deleted file mode 100644
index 326e954..0000000
--- a/src/python/speechd_config/paths.py.in
+++ /dev/null
@@ -1,4 +0,0 @@
-SPD_CONF_ORIG_PATH="@spdconforigdir@"
-SPD_CONF_PATH="@spdconfdir@"
-SPD_SOUND_DATA_PATH="@snddatadir@"
-SPD_DESKTOP_CONF_PATH="@spddesktopconforigdir@"
diff --git a/src/python/speechd_config/spd-conf
b/src/python/speechd_config/spd-conf
deleted file mode 100644
index 3cc90b5..0000000
--- a/src/python/speechd_config/spd-conf
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/usr/bin/python
-
-# Helper script to be put in /usr/bin/ or a similar location
-# calling the appropriate python tool
-
-import speechd_config
-
-if __name__=='__main__':
- import sys
- sys.exit(speechd_config.main())
diff --git a/src/python/speechd_config/speechd.desktop
b/src/python/speechd_config/speechd.desktop
deleted file mode 100644
index 4a9e8d5..0000000
--- a/src/python/speechd_config/speechd.desktop
+++ /dev/null
@@ -1,11 +0,0 @@
-
-[Desktop Entry]
-Type=Application
-Encoding=UTF-8
-Version=1.0
-Name=Speech Dispatcher
-Comment=Interface to Text to Speech services
-TryExec=speech-dispatcher
-Exec=speech-dispatcher
-X-GNOME-Autostart-enabled=true
-X-GNOME-Autostart-Phase=Initialization
diff --git a/src/python/speechd_config/test.wav
b/src/python/speechd_config/test.wav
deleted file mode 100644
index
1c49b4abdd29f5dab872f96691d2c67d2ecb545a..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 17410
zcmW+;1$0wO*PXc%Ph*u-ptQKVJLTa#+~MKw?(XjHZVz{VxVu|{QmS!J?wy(ceE-T?
z2}yJB%pBQ!pEDEMH*eA;l7>*1hMoT!G;address@hidden;d
zM@>--UBvZf>BPkqr;TwF+ at Ji#9U$Y`DSS;{jr*b%Xa`Qi%XL?kry|P8NcW=rx1k{U
zN(SiSqUvyQxRk=k*`&VcJHLo+l1t(5^2x9$)X_aOIyEDd=lOX7s~dSr1QdZ*!CrJd
zJuU4BRUpsR)address@hidden<uth<H6`gsb}L}2p^PD%2LE6uVb}YD8Pc%
z&@)v at qtP^5KbtEaYGi+$?ar^|`V?w`dkN2R0W?v#N?tpUd*=A|=b_yGwlTRj+d<q!
zzr at fpxn!|HF$?s5!yMHGVUzN-da_Ue?ZuDyx4KrQ6~=Yy015g$*(-jLUmvsA2PX2*
z^~%^KF+}%R>TCNa=U!$obF59{9_M~3*_Fc-&7w{gJen}scs_c(;SVNgfuLtUf~O+0
z!zbxZZMOcUCY5g|&C5^wHv9XjjB|DkHR)eiJ0up4<address@hidden&R;`Zat|GfYBEul1F
zo4UScTEgJMEn;7r%UNXcC at RO77fRFjk;0Lg{t5J!`hu>jYA|;*oR+!cWAD$;e>Qb)
z!40A<1^+HkJ?0L!x*q4I{Q3B2hrOQXxVu?oyke=MU(|+_1<8}l529nEzM>esPtind
z$EKih(wxW#p}uCRY88s31AJY6Pxw_n`=~QTKB9dSKOkv}^^^W*xVH0weX*^tbDr<G
zx2X3SdB)lKr7>et8^(?`{ADP`m61NNX5uHjn8lED{;6^sMYh^5&Se86v;E|+F&Vw{
z_65dspN#brM<lec?By=HFWHUuCHd!^&%KMB0k4QkaJ97i3k6b=V)A0nM8%;`q$J-~
zn8gGZ4o?o3<5E at e+Odi}+&BCnuYX3fj2^a}kVklJDUvwN>efl&#m-jtK;AyvEB7du
z(orRRkgKNP;@6g#SfG9E{pcQw1k#!I;EUjnlqVg6ufrMiw(wMRp`&z>zeDcq-#v2A
z`YW<d`bP=gq$sPP8sabGoM5Y!YqZz&=pDOUugD4Rv$lImTH#f(y<*1eTjE5z6F1^-
z5Ra at address@hidden|e?Ivv|fNTWY93*T=LsAuHjA`3-92OL0kgdvi)VX1Mk^
zh6KN%&f>5bUtx7pY^*)Hin=QbvO?llyqKIOKZ7(>7&TTs)!b9PQudL9wj;k$md!m!
zTBB?p{V?7b-%_iMBzxZHM`bO^Y3zJypX}Ns8}KUS{RA#~qot!st=)!3${DN|-;q8h
zv%+V5U&F_7hAN;E)mS_f5OUR-t8D#)6H%uqPu$nU0&xy;N2rFoOz!k7y}h7&zN>!t
z1is3 at Hg!lXU%(#o%raN=0~cZqgu2`?a#JoGIPK4tjtiSq!<7ovblS#Q>rd~jCGNDy
z01=z_#GSUb*O!$4_Fl@@WU|bj`Q!52I%2{H=`pdB^-}V%xa{bLhAHAob_m}TU*gfY
zA88UgAsIMHwLv{ovrsu9GRj^w+v*q^x`!6(m&F{2Zxg3e&JPdpB;@|}`)Kyrd_Mn?
zXO~<N71nPnxG6C<address@hidden>-^B{98+wl1;hEulI!ZjHdZsL<+K<|K$K({v`RaCs
zhH%^UZ_H0)TI+j|#r|W??b%hbi`%npyPWeP9yVAJ8-KS at vUN;MU1K%H3Px~mMK}Ji
zPysazpOLoWXw?tZZS_T^MgGe%G3!8nH($DRR%tb?ur#n_36(;7y~}Jr|HwJTY&G&W
zyS|6_l6{)4N&h9}M5`=0nq%A at HbJZ`Hs`PLTP1g>F}cp)Ri033HSPI&fq!%JvdTMG
z1}>p;x}xU1SZb(&gwPsK<GhkNTkX^AryYGm>2Ss1SaYEX)~Ye at jrEi-*h=oRf^zS8
zLPrGu3Ww=j(W=y`vy{!qWoNIf)44C*&BMjTRJ~;WVkxc|9lq=>WV`vNP;PO%Eq|Kl
zm$a1D)if>eE$LxQ>F9F$AioLSSL{;U6qqndE*q{$=J7q16IHJ?H^h74knLB_SLeXM
zcy>hH*^m at -%Fq^-4^DM=&25)8I{%!lxTAWoz5Jf(O~sQI#oRXkGF(*dWD!(Jv6nwB
z5VR}YIFihW&|KkFI+XviQ=Yjw`uriDpP`d{G3`rJO3WH{SY972<TmH+%s=T2InH>u
zN+*e15w>!P=9saTolzwe<q#FTVmINZaE+)UTjY)05yfid2z9DBG7 at dKWS6q9@)nho
z6=ig5ECr1&ye>S at x7{`}H)yvxemSM!c{$7~8}B8ai#=!2no>2J at f=)5LHV#S1uuxq
zkA!GZ at qnVcs<Wy(s^%rx4Rgo3T!B?sp>`P_SZ=7s$(rB^S9<P{{C>_Wj<Md_QhhR6
zQP`RvUm|*s`IUZxc$&=+`iejJ9zqB5B-~f-z@;gYlvsUE(83J+We>2Q^A?xCi*K}g
z%L2m at ydpf-_tEw=x2+ at 1S=^Nvx+_y=Gn7fJ7~8^f!uU%)7ca(13J0Gr^v4?_$0Jr&
zSlpm!psJ)QgNA!&=e*3#bTtbe!ASkeFxIkEwNS1fjB at wO%gcY|Eam*+ohA(<+Z4~^
z`ozDov^8(j_ZDBU-9j(%C(LjHQAAeDW4HzijZ#uw6$(Vg+Qpooc8hP2{7Ss2eP%9Y
z*oe=D&-?1y+vL4+Tz0N=^$R)V!swOZWWueOka?i-q<SLWiS>#MK2PY5FGt=*O0WcR
zwjx!fQx!vdy;4rEye94y!CUx;YPCUasjpft4-9s2f6eP|OLi at A*6<ybmXQOBk#VoB
zy)9X$M*1 at 1OSVj?EWY7u3d2c>$VqtwS3r at ac%#}WBt(we7vwZ^wD)a-87|X~F`tVX
zh#!Z4_?Fx6<<)kYT)$oGLo&>8ogpEiZp>CQZ|tk?gg0Y{_=JBaG{W~Hjz}G*7kers
z<tHVNE_sLN{><C%ej0p)FRPjwR+_!a4f3?$GI!JbTei`zbmt7;18FbWqd;-htakGP
zlTW7-U$e1-QoO{+2t!Ey$US)iCyAF8=Tr*?bHs0t&OPT??K>mi6#Hr8%<ZF^;!ojd
zf4049{yyh0R~7fIkeXCL{}`6V{})}}e85mfT?enlZ^d)`O`#nA9<fM`m|du&c&*%~
z%wylYpK}N2OKxN6BVMeEM5US!DL2ZygOA(~@@v_TxJtOb_;MgqPALw>?ueUhE@;}J
address@hidden;)lZ0xG<0mrE$sZ at dn=z7>uK+qvh|5LFWlH)
z&9N_^cRg~gar;9>NNrTqAY0R-pP14NPE`rK3f~ZS^E(9t&WTi#+OlUt0mTtzPh|#+
z^)=7?p5NNjHe|!2RF|U8n+7WP%FlypPbu3eyTZM|Rno7K?~r4Pim at y<!F<!0rn at Di
address@hidden|{PJY(t~g%NLsdnvM3y>Y@|HT2{fHb9P1^pZ8Tt^S;mQ8B4zsPd
ztFYVSE*!2v8l#_4L#)><y-ePyb1FSvhtG(U_yvLw{fcaq{8TM+in)q$s&cH4cVvD&
z+i-7O=rrD;e5;>qs-*ZKZw^2AY_{JDO%0#7agHf at 366`rV7X9mbD{C(*LtU>4?Tjf
zstO1$L`lbBdT671c+3+EF><ODPwbB=={W0f{_W5kVYD(<-9_ip_~iLPgO{W?{IT`-
z*dOa6osFZ7nWa}&?Uwj9&K;93RHx%O5gm-=awF9hwbk at RHJ|xjp>$VUcY-U{zSRD=
zyRg?HpW!``!>o-M>u#M at address@hidden>Q+W?PdJw}J at G(v4PBxlRyoTO
zi2Y_r7uI+>address@hidden|ur)Yn^U<AQjG>^7Y=g*}vX8o<~~X>gwVu52~h3jY>gD
address@hidden|{A|R|>guveRS!bhbSSR?_5K(~|$S?pJ?{cpP_s
zP5i3MIGY_6XsnrUTv?=0<q-uw8&0S?`%5|d`TDvC*vC0%d#ACLmV?P#66WY at 1PlMx
zr3Zhj!dne}3;I%eMfWBTY{m0!xkDVu;f=lmo)rIHcN8D8_KK}yU6<6-a!l7yb&jM3
z$~)#}XS?sn2~w)GAu`L^!#OFEOFOG7TZ^Zt3-qw|)7A~dcyjC)bDO);Jgvf<!mM^%
z42ACKw at address@hidden)kV#qL at
zs(^kr85q8UT+$QQ5&M?R8t-d=Dw6Zk-&?=G)V2yGi(iN-
zM7O(F*iJgz1eW^h`En!U<yPv7MJ5!^N;sZqRHp`=p0&>L?%Up5fvQ|V)0>2~mYtmJ
zKAL{|)uDH#|73e_2xp?F$8=S1;)e-u$nMBIx-4jSq>(#HYKSiJSK$HXYsQX>s%VyC
zkD7?zWs_sXpB(%2Fh{3yJ-HO=v*(~=i at RyKhj37%&?R%ueD9w=x$m>u`_*D&JW=(-
zkP;m!oSIrZ;d$H_?R~VL3bd2FoNkrXa<-sT*Tg@)=kgx@;y$;_=pU%3yq<Wmh&OSR
address@hidden|kpM%Sc&i>5o$QKk8omW!yk2;SEF at s1`+i
zjJlycBdPz)`mFl)HP=Gg8j2OTn>ZlK!<F)`%{cI at Q%2{n`~KAPv<$S at KS(@p{vP`?
zshUaF8%!%SJ#dF`wQv)*NtLF%p}2|df$Mp9bI1B><K~*<QJ=Im#h;-nj)nFl+Y!%R
zCP!ul8%Vn%lN9%ihqT?4+1!44Bgqzq7<yS+X)6dH#Rr-URifs!7SS{I&zXmE>iVh*
zJ7a``2MRpZ<@rlw?s!w=ZPy<oG6#9Ka#sD8WUgra*tfc!+(Ea-RXf-?ustM4J_od%
z)p{bSVnPw?5A{k<&+O?Q6=|qC65T#_M>address@hidden
zNu>*<>%PhVNmoK`JbN57Y>yoOgw}B76>C&BU6e2&fFgh64DpNhQ?xVQr at KiWdADTM
z%-EQ(a&_`S#ViJOs at P2JAw`~YA*z5|YYfJl#_Nh&p*g-vp;uue>B~xouH>J{jL8j$
zp6Tnw?<>%&&|b at Q!ubw47v{{d5!?FQ!=B6G)#{9bMU&UZ|Fq2H;(aNBD$-6;6Mv=c
z&?K!_|6WyEp`y3)ykFP+n36NhJ3 at GA7!cPXK2iT3mkR&u)I!AXbwo)cStCugq~8hM
zEGd?0S~IUxHBuMm*GQ?(G<$k*8uqF7=|j3V>_Gm{KkM_UdxT_Ftk*wLjtdX>N;X68
z1&I9(zG5_$+pZW9U$4-cxbZRXbvM{691>qC5|t{{OumlhaSPE>ya!(ne#rObm-g2{
zjnrH8Ii}(Isel~C^SEER8G+n!`6v99 at j-Q5;^LHt)=kk%wFtN8cPgS4u|iL|OK>Hb
zB-T-#(KJ^Z<R-Z-GtzQrdolW=-5IkeuDQCQBzYF*9M4*u`zZI0yF};(sci};7PUsl
zH;xunOgy6Mt;iCx(fNo#uL%<rS<1J{{_KbIQci+nyx&b4h_U*j#$9~-a4&z1-D}(7
zGC2l%uY`xm2K|Kix0YyAqo|GiT)alvQ)N*$;address@hidden>V
zqg1IT+47HKWvGs4R&Hv}E_*k}L7zt+flLNhLbsR{&@j8W0jw#X!xs^HqxX?Tw4*p*
z)j{)7oq~CH&75%ld+&2{S at 9`KGDoY%NtFZh?5e!2cEPsHRVaK#Ua32iXtf at V{>Ri$
zEQuEJQG9c51ltmN8(xfmihWckGz*30zS4PLZLhr#<dcd)hBua3>M>Fef39s`uHZ~{
z4)-pVx{$r<S8?B>t)>%(X^IzktfIBDwOAj&3U?0=V_AG{#b-r3T+cf==T&|||62N0
z88Y08UZ*K17YP1z6wABi_}kIVT_UoKG}BB<SZbBcvf;PT2skWJ7%yaUOXZL98}6P+
address@hidden|t{IVQ!bE-Jn8Hz&m_F=woz83EsNpQ&nHZT$%&KD6;c at address@hidden
z*YFdexTxi|XnB~15>cMGMYUQ}Nx=uZ<+jW{>C}a~adu55Q;address@hidden)!NN@
zrASF~L^05M-|923F_lr(MV<M>Vk7Y^UzqHTOlAUqPk5wQ#}5v6%%77V<t-x}<Sp7(
zP!~6oC4V*N=^UlKq%+S|P#VjYDKA^o5~hKE*sI=+xASkr<-%63v78VYg)WOxsvhbp
zV#naCoXgo!&Q_tuyhZ1;@WxW;Qt+MYeNJlrS^Fo4KhRa$&Nqy?7hfsnx}}`19A8Jc
zBQ6tE!aFi5a)osk{#F!GHCB9!#Ms~EO>@l)Mxk`&Tf-K84>Tfd^rhIZIet5*I6np2
zN7kWEW at B8uX`6AK<|$q+B&u#Ij*8RSqsV)@M;N2Lp?a)nFRiep<=%F-50&B8X+D`6
z7~E`Ppqe|address@hidden
zm7hEM=e={z3jTw}su~-+8Ef$kBXRzIwn%;f$3}ZYf2?!?l`{8E(8bI&m)0G|^HGrd
ziz~-<C7VL=q#r*=;a9&^KW5e3tMW6QTLSfvM^)SCG;ZZ<hdcT%+N#^*+%8v2FkhNa
zhv`qn-Y~T>R?s{7Zv0P0yf}y7K-Yu>sRbS{hzg^k9!+#9@>{qUgxce!>bb_srqhBw
zq6 at sUf49ZCrZ^Y++sPW#$gm~;y#*O-XiazlJIHqxUI at c+f4L6zaJ9r1(Aye9t~h7p
zJhZ*>wWMv8D~xX}4&@#BY%td`)Ryl0;@a--BHyHab>HINTZWtFM%kcaI6 at Hk9e6fv
z5xyzqaW51#)ic%Qu+_6Tx0&svCn&uU5_DSQ0EJaL6rAh+;Mn1*=<)fZ<W97pZisbt
zw952BUs^bZ?h9$6M{wd|QVl7<BHU<ktfCHH=S#DlcfR!(Ac^81ZL(pnSS- at RALH1Y
zUm3J^#Xt#4nNJ(GHjG(address@hidden&yUs%G+sJ+xm9?Nx8q$|fNH=<dds=4<3Hr{l(
zv7%j+-ZEG%%DV#l94vQ^Bic#aDUqsVsOoOqgt*`4Q^rrq08SJB6^;tMxq|Xt=_gt)
z{HuspZRCT2d$xJ at C!S;B6fQ>address@hidden|c{v{kGYz0z{Ta3sp0
zJjFFtyi>^HOFmytkz(address@hidden at tb9wTYx*B at U
z`Wni6$z(Aqdb`D^H|xKPg;;5>mGFUI#aCjzNM~-g;)+sImKO?yci0=-YkE^7*SWtn
zkBv#X(d>9w at G5O~yXgGsl0)h8EOC6y^0-ANt6{csH+v6!QxCe$H>Jjr-R!W?OjTd=
zRIyI#;hb(?>8TlM&DYe7Hm2yl(qp0J-V63wt~DO)?Gx at TmBG2jCed3B+YLX|FSx-%
zRdEcj=XiNRXeOD>jTCw)jtk2phn<C-i+uy7*ZdGozTu}X1BJqeeVO(;&Q5Nhds=t^
zY0Y0T7mJ- at 5cNfsXIKS1TR11I5*W*rvS=K?MW`mW;7yUU_M-V!-Sa{_ at J7u8(+WcY
zewcLKuXEgWUiVD#lnY;!V{k*G9AhxOGdMNvxU(4X>v2b1kr+VlZWq=l!>V?Q?s9i$
zB(ELxUP`deiUs<O`fS`<svYtG9vXaAy|Y5c<-Yi*p=+$uxH>9Y^9gIY65>UnozRJ8
zgf(&=`pLURySS7d@|<*x at th70Vuci$+UmNl_-p8fZ<yn?<A6u-wTv92I_{uhaqKH&
zEyF8q4wr%32>p3Azlu$h2GY;GN-;sPPN*wS0Uw`r=|j^{E!Cl at B~fX7t;kA$dq=e6
zl)Jh6XrPzef&J1gij_<g3}bY8e0%(nuPB`3hoW~<adHa}7FsJ>Dte=9{<F?wZjb+n
ze2l-VUZ%?s27-omd&j$N-Xw43fGskcoKQv#3-vp-aT=#EnC~r?5C;lYTu=T+x^eA=
z{h~>{z~%+cxHtOxhZi#}epl^OXF?6VEmYrk$y+J-Jh&(F9~n+-E1K!MX$Gkb%940C
zYt0?t4)Nvr`fL}|^J9cF!Y=4ubPu=meexF!+lZBascfgQiVJ9W$ry<BHuMw!p3o$+
zhHY1Lj8f at ltL`W!;7*XaXVEy+f<@$QqzpbG)KCP(e6En37bqRv60y at 4Tn|OAauEN6
z)Rh;7zXqM*oN%{DYk8IY9dA(eQjJ%(QZ^QNPRsSf$B~+;<&JU|?TN179o#q6jMyUe
zrN*QP`pVrCx+zRTJGPL-NdJX=;p351`3rr^2J#n_ at roM!YHkIp%%X5jZZEf)^RYg3
z5-SUx<rFl4*`;dX`jLuq4mI&dp?h>(_<address@hidden|qhVn{1^QXaHHojp7iYvG5o7
zofT($XeO=8PSbx_Dn8C3-i{lgU4%upM{H6bl1Ja5p4=(?9gW0)As=nQRBR-z4I`%F
zVtgU~3?6_#pfyO3``|address@hidden<_KDV{t7&1 at address@hidden;8{q
zv<~`%((odP;i~*gZalY+TZzx3wkR5vLd#HRWI;br9lRdRVx8z9vQ}1;Q?xaT=3MYi
z15RTTNl{rr4Dw)k3-b{z9mCBQ9txWj<address@hidden(LLK`FFi6d;4pDT0K#gulQ
zkm*6AW4OJe`(8Lnr7?Up{i{8pc at TZUaG4v(^^TgV$?=wSRgPQ<8r?^I8^c9|JA62h
z7I`UG4v!7>rAj(eI!#WZcgjT5!&sj_QLReclyphhBHz_E(HR0kNBOW7x(u)Xtjm3B
zYw29=J1Eyz*o7#$v1EcZ{D)Tx3EID6z9d{WZs#%#V+$Cx%1C`N(X^La9oPVx^ltc3
z-u2wlwyL(Nu8+aOu+w!_>gr4JwhCxThCG+{(5#M*HMU{T6iJ2dSOe6Hc=ICFA>R;b
zF6=?w1Qk3a&yXLo&-G5HV^o+k`kC7t{7qZqLR>f9g!r_0lh~Rrh~1d at gFOgL)0Q at I
zG(CJ()k$^Ed-hM~KZ-xOS%vLwf|rHyigKi4c&U_#SMkMAS#gFX4Ap*!b})@e*`v~W
zI}7V#M{DNF$zmH_ at yNT3$?5)#mDxteX8%guSvi=#4R#4HVS-`__laezc3Tpn9P(0e
zLBUm)>;7hufu`$*LdeSnqQXLp{53zfX3oz0<~|kr$eq%aR63;r!6w0zk~Lh;GcH_8
z<21bD47 at YxYw{>A5{zi8TP#8td82wnC5L>u6Y?JAHL)M?%!<qq`-twybzhF>fWLfj
ztyc(*RjcC;>address@hidden|t19}S&mUV)XUA1_3oXOYfV^_K&uqW#pPak=m4!koUN-oL
z`m6y(sJ#EDzdk=Q`lU(9ztK-mZ4lig{F`-&USYf~Y}WR+wu(yehw^a#=A1&=zTB8V
zPb&G7oWq<8pu;#YwAlY%PS;g5wdRK?swQg-rHj2#QcTB$WxD5@<+1;oqr$ziM`ex6
zE}s1<XM}GKw#cvCrCnv5uiYJdXWWWF2?aL$)YpW@@dJu=HHUF$Q=jCf(OWb=OUd|3
zVtwbd{A>CD<nq~p{12h+;oYu_j<(J!o;QK2{!+ftY=EJgZaS)@PftmTtA`}z+r(ON
zf;vrK(b`>S^2OUGI2PN^W-rN|7HmSw`1ZQ0xs~2JAryWew9_2JhUk%sL&{4 at laq?@
z!>Q4{D0aGfozfqDB07z(bKJErwtvn!lC{U3D~W+y&X118u7vPKk{&53>(t5SSg|jC
zWGRx;E2<bPqhDs-tLw&1)}ArNqcXO~*)wuFWiQBj=d47_k{thUuiMT1h2?G1ok+A$
z%&d)if=z~UMMfn27DB2M)-};oe5cBa?y6G-mgb+yTbHxx_k}+PJ&D25?h(!e*A(B;
zNWT19KF*aiKa9Df#G2m)O$7!EgU}fB;MfYP4#E`U at Tja%E87EmhVzbnvh9iQc_iAW
zb3!OOdjuVF42?w>^nCm^-Bi9~%--aX`jd27TQ9DzPQ|%3mCQ4^Dz0VuukyvbzyB!m
zKKk$YU%Ip1X78TxDpVQ6-ld{_^i$nCsGRN=C}WAB!s?OMSj%Xkiy~m$plsn?mUk$(
zLf*lg7Wqws9i{Vu1m73eY0u=yRJvWx#l6jsEjKvAAB}Gx-%0q3H$}gWovAc&gQ9%;
z>k+}Z$<ftW-aaRPp>L72Bar4z at _0N$Lit2R_mQERW6=lIA>JJ4DfCcZ77x|cj{c(B
zNcM;yv<AXEZ`z?l?y2Ki>}xI`3T^gQ^Bi+846G!v>|eQ?f{%?Dt-`~oO$Cn{qvcs#
zs_D4?HNGv*H!?*%--rA=dAIW)=M2dE<r^O;<(0e#y|V%`ea7EskGNW9ZFIC)S6L<T
zSNsA|!%r}+vs4s|qXnANs+3T1#}r$p<GdqilYQkQ%>tR;w(cn|zyE@)lyl`P+U?Pq
zs-Il9=-~xq-4K3dlqV)yqeY)pmthUZybT;address@hidden@&>yfMoL6H!BhT~9^m&dmRJb$
z<4vjhW?WL#+vM6Yzqo_S%9h`{KCB||(address@hidden|E%#
z7g#a6gu8FV<_CNYv4fSy{=rEsA?ld%z7XWA8<y$Sbh!JLy`Za|V~wq-ziT+xzu&jP
zQ_<TnQWTeDvxrqw-O^5Vl$#jSGuf{jk49^znN}#uQ>3(MOQ5Tse0v+0&Yk37-e(eu
z+zhVwxAs~B$E3DICuQ>=Oqsg7=#Zv$!e-+mJW>@K^Vygn<cTW%XJNFzsC|vSp7S4D
zEvG5GN0P(0g4KNg_-VK}`6<;y6%1v~TZCtdGYP%on~Kx9Q&A_QdLt`os;a0`OAp+$
z-DiDg-5s2#0_I5bz<address@hidden
zn#s-ZcCuBmmvn5*f9T#8xg0(fDCbRfSMhI`C$a<*7W$i~>ho}kC{uFJ=#_k3<!93}
z{cQYDxT3!yo)35&gB*Xm3OSUnX`!g_JO51|@qF_Ylgy|Cxk at YRT1QPn2e`jt8^rby
z+X>@Lx1w9B_Vc;A`<e{tj{CDS-CfdE&SCQ(l6r^B2N(Hl-sIq>h$s9qqEhWO^BP&0
z8Z#IOdL$jDJRMb6oFSXUr5Y8Q;!pH!_AT;nbWis$k&i at DgD-tvk1^nvd$5WmR;+IB
zps$W*s$KCNjjz}@;kfCW{w8YAm(|@DHU+jiw%JA3Lc7&@DNLmjp)!Hp-jJ`loQYqv
zBV2dGXX78xO{c8et(6rexP`hbeRU3_7_CR+BOAOvM-}HqyV6$Bha&5P!vZILO?-iH
zBUFQN)T?qDe=B%A#PlPfs`fJOs7sE{)SSfQR0p&s*3Luija)0;|2WJ0%aY%btboq9
z(_PtjHQY~X5P8crGaii6iVdS?6p+jnu}4rDH)|g;hIN`k!m6Og-PY5~|H3`f{XL|L
zd<zWr*Y$l3lqQrdmlbTjc4X8SE{ji#T^sv}?}4vHF=HVyforLqsrgQZdzG$n?vc(N
z_Dg<yBsP>9nB-0K_yXPNIM$a5+W6=>nxWiMW0Uy8>Lb*wYGiOInzMLi8-0Lp8XV&8
z=icK|I97WnN+xMq=%v4=uVs+PDz-$P#|_uNQ>D;Y)jR7lQ-t58?qqT5C*r}}ZuM;b
zZa{J#ab5MO-0!>|$b)K;q+puwqkm=O4tXREqxZGMU=_D3x>(!AE#`UFTeDB+#k<&9
z^=tJOS$4aecRl;v^<Dqd0Os(xKs$fg-~nj{%498R3)Kh{QSIh*ro!>7)M1KLI-^y!
zoB6~nbqo^y&address@hidden<7y3%;DCSs37#i`dw840-sVMA~
z`3*I6LqK=?v at H~C!ks<KT&-P_?YQG<&>k5X-W6&W+!8z)xj>wedh$<I1)UDx<C;ZB
z$6iy$Dxyud%}W)RaAW9XVsh8}+;!I at xv#pigI>9P<V&Ew_q``I*iyF3^T|V1M^g#a
zMm*oJEAE)8I6bW>81-G;MK#KF?Nxjy at W;K<d)hC0zXn<nP8t at R;5YbZg(}mJthrnT
z-_>2$G{lLTh4E_h3!Km0)Xh=H(<IDmZU}S2!#)4Goxa1K#h#={De3RvEq^axOn^#P
zP!;wPGyM%?d2y>?ioRkY+*Gt%yDaL3(1qi)<<-;V)1J4E94F^2=m`2pOARAUBB4;3
zP&X-><&n2?FU7&AZHjc3sy`FkRa2LnuFW%1RTI`jSf|2B7d+{m>CgAybRYH|iu4Y5
z4qo;T at C^-yrS0SfSt^W(YOgEHw^h%Mon{z;fAY7rvQo<i;l3)JP$Y8Ad&<+pKhihT
ze?T5VE%Ntp>EMZAed#%=A-l)~rA1qszrr^(ud(zKXJ9AP{%yJL=$0~5`H~I`5?_nJ
zReyco!O&Q#O{i30ivMt+l=KUo;Vj%NWrD7bn8N;1jWK)|53>v6OwD;kO|GY+lX^N=
zGjcF+ConGX(l<W%R_Y_?OG6~96fYknZ2+(5(oTxkVqtVf>|`M7|Ii`6y84-74pQM$
z;(V^8TsHDBGD<oRXHU+-PG_d<mGUC7(lq%n(GxSv6w9loag*>address@hidden
zHk^Bg?UFCJB6u#WlrBb=lK)T|Dnm15hrFGfWNsG6>hn(&<@qZ}Qk2z}5*_ReuEoD+
zO-W^ZKxo7%m{aZ}HHwT6%?bUEbO79F9Z8Yq$<vTRsLW3n0?Pej3~tPiSJhVJLS+>r
zjO3T2i+HY3hZ{|XN3Mpm!kOXG(hgb?{lz3QkA&oJ@){D6yO0Iw2i^_G1kH*rilbaA
z7ZS#ZU$_Sl>HlB~^_zoiqob&ae3CIqBUeaA+LBd(gN5Jdd=jP{AYcpjFJ8nS$Lm-(
z+?ijCwMd8Wpa$$F9Yl+<>F5T##FW^FDxu}95>#{_NNZYw&StS3<`-~Jxn=w#+>DA)
z7p|r6$aJ;|_vf74X&h#g86&Udmt-C-N*|J`B$EteyYL36eaG;_xi)MY?TG*5hTuDR
z4u6*OvSQ4F4zTTX9yu*PlS|3p<*!T!hapQtHCi4eqX^r~(&-=SVb9oKY$IOFAI0xj
z7 at 4>h_$4aMIk-&R77d~=NKcY3^_EwVDEftF(_^eF9*B#f*6cRZzz#?mJd4W^lKB^m
zL(}kcREv#<$5Jv)-Y$<N?^$zv0;{>xD2c5j>address@hidden at 40|P<VkR
zvoy3Hoo6O?7~R7S at j^5YR%9g=rAkr{*+9;cRrDErryU|- at 44tMK8QqS!704J)#UE;
z2lzDZB(maSTt)nq{iN$iQ`sjCA!_y?)Z3+5ahger)address@hidden>
z=2mho at jWc@)%XCM37yScbOcSKRoOMxiCv*?dY1Xw3ATp*A)9Dv7Q<SjZ+s;&n;U?O
address@hidden;8pAr?GM)uMh55^R71(!M?4st__cgBG at Cd`
zQ+6J#=*C0&zIZdMLMO`x`MGpddLb_*`-l^I10zsH+ at 0g#ejoS-d}mw)JpK=N7l8ud
ze&D_A1l__`u^03XeL_D|MjY}ea*OmL>q$Af9K2PE`DhW8%C+GFXbj54YdI0_ at stlh
z|F;0lGlBaX&1X|+H8M_)mwOWh6VW4d9o=I&v=u!eGl`O=>>?|LmUHh!Eb6(!+;mQX
z+S4m+A#_?Q(9Yy1`N)address@hidden(nIw
zh(V01`LnnT`bF1KiOeK>$Vf7joFH)&u?)5dwMTDQNnDw0hkN12yj{q`QHaMyP$gQD
zUWe117g-)1%1WSgh|kY7mIdj5OkzXOU^I={sF!+~39m${YziBPda~y<3m*}9A%TC&
z-N8#)C$dfU$hAmN-bC)uGH4I3i|-*9DvOk?AuY<1aY;;ALtI}dDXf8<nTlt!dGbs7
z9p%_j8pV!)PcO3etOm=ZOX*os1F~`o%SKhXw|FyGj318Os2|vPH2%rXu$`<KTf?T|
zo?I?6vbJQlJYBvh-zPS((sf#iR40?chErJ`R2qNdqPUsZ$#)Xh at ZWG8m%<rw0#ajv
z8;ET5HN8v|XhG79EG4hx>Lh^{VVhBP+!8FQVPn`U8bc7Bf at YvG><+3btP;QSY5ZqC
z4_9INRA5Q$J-th+(30#13xYEK1nyl$#>iLY&a4COf+ujRg|b2w9HDyFhn<CJva&tw
zH--KfGNXc61=vv*XF at ca*mUrehwVgTQ5DF#mFOBRhH7)&aY^JxfAD+sopypeF|!UZ
z<Dq0UNg_MRMwSG9EFIfRkJ1gy0cbm&)AN0~1bh`w<=gNz at c=Xl4Q8#Nvvv>{<eKAt
zXgh62o=EQ^wdE8tm at KA7Rv#7N%5Zsr7X(d13)x<_AL4ol+Q~L>J%!I)5$+AQ7?*>}
zGMc<0cPXX6*$UJfaz#VC5<BTihm#bD8V53St2iB;j+Oa4=nHy|2c!RJ0<8EMWdCq_
address@hidden&{(address@hidden|u#1?ZixlL#?bPPA+DQE-gf_vgk5JxB2
zV%mfhC1yIBuAv=iNAgH6Kw41^dVr7dw}m$Rbo>b(GuS5D9nC{cAeM4bCO4M*1HHeE
z>;y!~W%7~Av;buHMD`eEpoYL6dzq3QA|1#$=EYBORh-246~=Ru;F?*eAsa!Kk%n{^
z-Nsf!*YG at AfcCMn%*9%um#jUFrlk-=>rf0gRXESbpn3E-yAR{nWSt=1U(kUho`hl4
z7qks~#qwA`c9li{Z;u;K0Lui?Pt4(p_z(AsV}OzYl#AXm18d8Av6J)=czZdZWkEKN
zu7JF1FTa(>$!*CPn$K!N-)u8fKYO?~{4hQp--q+(J2@>b$_TrF>tn{YfW?_?kg7|F
z6r%N6AH?A(TouKE9Tt#xaBltuDb5DqnOs?JD7Q`U2~D`Iz{;QTS$2pvq#dB2`-l!=
z4_Q0boSMmWau)iy<sd`50v}!jl%0nS+yzvPePwr1M;6O=V9dn>KFp&{XfxImSh_f_
zgr<O><5(f|6U|0NAzq%a>+A;`z|address@hidden at Ua|u8m)w}%WJ}>>eh+423(!qq!7
z%^+^#A=l|vc8ZNbwYby#8on_12gh)$A#;bb)tKOPh at TD|k41KkP}wHO$m1k4DMNLP
zpiX!wF3eTN^VmG#z&mIG`$3nW9Gry~q4Qj- at CNS#e0vP}^O^2o-B1>r1jw05>X4(L
zG5;e)fay*_o)*D%xySekvT_EY2R|7+RvCX}>*z3c1^tGqp(dFj-;%BLH7kPm;*0n!
z`WLwCH89?I`knq_YmkkN$Fd*`gSqGUB-SB6-OFMDzwa?Ua7}-<lXd~rUQa5>A*rO?
zfRtc!&>KuSGrxg*fu^$Ws3Rze+khw?xLEEcR6?J*a=0OIdnML|79$q&oOGe(*ke`|
zqN+dYOxuw0(AmEY+UPE>!B at wPz_KH8UtAn5MEmeI6w4^{qVXse9R<5rV6)hEHiV6Y
zxH$r7QkZ>1CGaA)mJP$d at fYZ)$8+tt?Fg~%>^5B at E966RP)?y|SPp8#t>+fviRcCM
zvEFDZ+JySzanMxhjq<s3d=ZYI!zhszBDG0lCLu3+0@(YExyci`uRKehBToXXY)v-M
z_b7=wz?p<O!U?VuUct5HF*gqoDS$^}EBXe0+k=v5SZ)UyS%xep)address@hidden
zJCH$1mPRs*L!WUoK8LFY$U2t0$am-bcp6t0_X3_tVJlfjwx2#CwMcJLfo9QrtOWj_
zN;A?MG at 6;|eOei>=I-NPs0TNn-;ZO^Sz!75l%rMX0eYAQX*X0A$K$Jru_vG#JA)>O
zVzZEpWqbrr=5k=}F`y7uvl?_YDFbmg6Cc7gaUt9hO{5C?AMHvD(2v9bI698iMIp3@
zn*ci_hXMJ2;(VNfzu+1CI_?590v<MH{pcF<0)Bt69klUwRuO$+sptYb$v9jGrvj=m
address@hidden>&RBwu}wL!>|_b=9=&>address@hidden)pIkf)PxB!eu49hpb$FfPJ1f|VU%
z%g{>r{f at vL1+DiRbp}o>hYzyetQMO`ih*y=lP3_rH{ewKGIWs50%l!6)>AF}- at Z^D
zAf?ES1Ab`F^EeOA?rcM#>!^=i04$SOSM-`4BSqw0GDn&d=#$YV;address@hidden>
z6+{JLcV-su%gy2U at +PhwM9MgP0F-NCc82DX_MlNOQwu1OKG2n%OrHYB?qzON0o2-a
zdX&uvG;2wlv$Bw%J#j6rp-_y!iSDv>address@hidden|VEqOY)mZcsBEpbcQp
ztuCL%tN3o*T09qxpyfzyI*cmGXL$!XL{rcjJcVn`{f(1B$#r6X*hc6MoP#RkDb7U^
z^qbqs{brBoV^#_F?FO;VI1_wv1GJch_9gq|ko<~lA%wJHn?QNxp>t>sS^{V6 at 55*k
zp3n8gH_$<Ro3rC7Y#(jKnuDgbgI_8Fw~rznL2EvgOOTgjHZ26o{4OhwA93#?es+PX
z>W6!QR_D00+%nK{r?_6+LBQ2EbS3z>1ME>&VFy?<)EkXu0{}zKv^i-G>Wk5NtPZzD
zkoZ>|<>qj(7sfub=BN}~OUeSH9tP!C8UKq%p?vxT6n8Uf0K~43D{|?$B}xEgvjjgv
z|F9yEzpntnM}v~siI;<QUb1xdFCg7cdLHV&SQ<mOf||U<j?>NTF)+kX+zEE<ck at 2f
z02pXEIz*?_r7R3c^NuQ!2|HmI?+GZrG1LW~yZ|`A6Xmk!aO%G+{tZa<5cu>8<mn$I
zLq6KLt=t><d at VUAuO+L<UUHw-0NnY5P66^fLswBXw2K`B1y>1nLNib|{u!5#D{-HI
zvF4D2Bo}nrVo(p$SOV})6uSpCiyHX4KGZQQVGlllD{z_I0B$ULfJ*Uxt^xiD8o3ej
zf|uSRC4SGU)6=9haOVf8$Tvc5x`_57lH8sINl#iFR8uy0376u|2rHqkJ_7ek<(dI{
zJwb{1A!tb#T?E-Rl->i5Sts9?50PiI2MXe3Za(@9YIQ4A!s)atTE!Z%zaVDn2xIu7
z{0{y*)FqcGqrKUGkVgV2o2HP1CqP$qV5g{uB+*rDD;k9M<address@hidden(P`xx^
z6IlS%$9i at address@hidden&GU$Fy8s0mcS15qEyyq2sk
zRA_#3kmk@>`ib^JVNd`K*;#r7u)iRxf_4G6?!abj<h+2I^PpPjjQ+5Ab`<#6gpZ&+
zsG4um3nWW!O4gB!q!)G4JoXHC<0 at f0`Ww#z?eG)&HbwA$U>-F_oD;C2FjpLV=|N!O
zbF at 6$4QuOwtWdrFLlfya+67osrYqPeB%{K3BcKQ3V)0qP at o8uUn+Xpcn*m(~M$Pb=
zLD#am>@=va2&kZss3lqqF&hun at K3;jU$`sYi?rN*!0imCg83Ih4*)sdf^|32#h}3|
zvAc91*tj;iOKyOVkE1X*h%dp-1szfvTUi at 69_*P7n0y%OwU+oFxPu-gvx%S@@6oq(
zH)yTqz=nTe3VJgQU1hat9OPGhyaTkyEtq#D*o*FqcAz_OO&NNZRsf6Sp%17v%3!TQ
zGrHJZP%Jw at 7iwS!`x7gSuX1~FF4Lk_r~q2YWcCU){v)~>P-iU}MMi+iN=7Oqp!w)K
zsMcoqEnGhacfvK%cBJ4Ag06mz&f?biJo^Jm{3R&rt+XRVTpM&BMmYt2k`g4Ev?H&G
z2b8jdZQ&{kcc8LW at address@hidden)P<33)$arTA)1G=nWZ2HS9QRh)Sat
zs3OXsKj|O9^Dgu at s{}k)0Pnzm1GcWk%Ww*Gvx)&{O at X|Kg}Sm7`pF8hI`j=`OPjLl
z=x<Qq<?vcm4s37*MiNj_ at OUnI40^INsJIM>gCX=PC6LvtAy2I+oqc0>m=l<x9(1A(
z!9BLJTkHuo^U0vF-RLCxLbn3KH-TE$0UEJBoUW}6>Z~aAQaaLUbPjz=$FN0gHE7yY
z=mw+$mrjLhu?P!5JX`T4l!Y8fgpSNcIutqtLug%k61?1s831uc!QXsPXO}~_(NVOX
address@hidden|I9d&>*v+;9YrhAj-x_qwD9Gpy^cyIK7&Ha^rGiR#B8>4H
zJq6Y90=35yTyun(QDt-;address@hidden)3a2G#%+TqV$PHy{>UKyH6RN4Y9o
zJak4T;n8RU+t2=n(Oa-Yz<~td<wY=qY+%^yL__D%?yNRii|&C^nSmR_SSI`ql!Y6r
z at A`0(C=u7fakv7g(6NC0>sSgP_7VCOx;8!83+UC=MTbG*A7edWr7uBEErrfn58zBa
z(m at address@hidden>e)YayEedVeeXz^b7ls1A%?7ByqT*-Xf!%IJR at XgD5<
zo<XILnU>address@hidden&rLEfZ-jqWfjT8wYuH|R1-M+ER`K3xJ9
address@hidden|address@hidden at E5z)F{(
z%Df2GYhj4LOJFfG+~pSiO~(S3EMiaDV$=xElwf=t$KVw77sQtu at UsDKhxY>>ECU8U
z4p&tLuDMA!0TMK2cD5gWKHUVJWsd;iPe8AsH&lJ2&address@hidden
zpfGQ-o9F<V0K9gFMKLEt+B&uy4aY8Y3|8JAcSkdzOVbZcgzt`oQ!w?>KvW(51Cg{3
z)X!P)+5h at address@hidden(siCSv~`ehuWz+R7K74bwt4vF9A2}0CLv?^qawM0=lO|
z?wo~Bud&LYIfkRcs1{iKE#%k{1bT|ihMHChr)id;1Y8zh0sD1fRUjv`Xj?WOxT8NC
z2i&{~Sb00lsuijUnf8gT1Pcs>_56T1n+Y+ZMtfK;sIsHbAz2I_FAkhk2XyXqu+Try
zfjI{nsuU7&F}xH#1r&3_te2yFv=m)}zh;1LJ`dcM0g*Ko>=VnXvx-nZPJtC%XNMt^
address@hidden;3#z?<{nip|hd8V`F{D(H8$Wgq_^f&SMK
zX#+aP#>${^V4weKzS^h<(n6e@@j&?f`w_MQl=>q;fHzP<wTFAySWSq_qF|w;tSi{l
z4V9J-KZaa&06NS9##j$$ma>3T1AsO@;G*T|address@hidden)7p&$$7r<Y~
zLDfwHUIDH|eb6T0;4-W-#APLTo(7||15c%c6;hz0`w8_(L(I at t<b;ml3Yf`0SjAFg
zhidjLo5Kdfe6zqRcJOm!sEH4QTJ{0{=K>z~0-P<1Z=vF-0x;4)=p)n{2VlfhbOc4=
zXTYAH!OLMl&jO${U6AKt81*bvnh$_wmw?qyg0{QLPNLpe#J|x{K>Xt1_nJrveI+YI
zb}6v&JW!NV04LXjy at mqsM}T|gq4%)address@hidden(VX8K>dv{3PQgC
z;ZMRE8UfyQfDQ?0O_<SK$btKSHVVkbHvjA0LDeRp?Qq3!mJKTMHpG5;uq*+4Ux5DM
z8F>B#bJqislz|?2XPDo6=s`N!zfkp8L<7Lb)1cea8WhhS$btC~BijM*g0KgW1poDc
z5+y?JN254Ewd#<w*MO5I!(HkDXU&CCw!>XAL0_JM*QYSn|6=4ZSp6%^<Ox_Tz^Z_C
zhXB${f($5%7DMir1itJDck{3u-~tWwK^B3B_Q85z!>m<+p8o=pO$F>+0c*YiBTj)4
z=Ku?&gZ8V7dZK<Xn`DU36sX3}!8pHRJsF^;R{=lwgpB$ZFrX=DoVxIhLV&L;!3GwH
zm%Ctx2{7{!=nybhbC`bybeP|Q-EP8Md=Ob5AnUW>`7S(ipbm(EbAb(^E1Cusw+P-T
z1pV#DjEB6v3)lXy5BdY%JAog{gN-PBmdh~MzZ4)re^7;2p-yiMHY^Li-3gKK7;Jl+
zEdW)!7?js1h$ESmM<(F-yWs6}><dJN1Ti}hBD6VL54bfNkVgaew!`0IVa>_FnOR_~
zix4LZ0VPL6HZBA0=!DMBSQv8%<YZ~Ep%U`GGI|cSX^fh|d<Fr2m4_;8Bjo=Hu$31^
zDFl6~gD{gNfGqFe{vl8ZRUro|fp0eh(wBhPSPIuh0CDZm(cA-%(||+kV8lC+?-CPW
zZ8hP0K3F$`IB~=0Dwum^xXW_D`w^hrc(6+j)He^<cZiEOU?U&I>MO{HNs!r7Aitgg
zhs}WcaSBv)t6<%)*?Z{sS^;VGfb?yFJ1JN-2!4435rt3yB031!1c!hj`UQAq at GcDR
s#sB|bI~4E=Ed%h=|Hh8||Kop%^S}2T{6zw*!{Gq<E^OKT|J9WJA2^N;-T(jq
--
1.7.2.2
- [PATCH 1/6] move src/c/clients to src/clients, william hubbs, 2010/09/15
- [PATCH 3/6] move src/cl to src/api/cl, william hubbs, 2010/09/15
- [PATCH 4/6] move src/guile to src/api/guile, william hubbs, 2010/09/15
- [PATCH 6/6] remove DIST_SUBDIRS line from src/Makefile.am, william hubbs, 2010/09/15
- [PATCH 2/6] move src/c/api to src/api/c, william hubbs, 2010/09/15
- [PATCH 5/6] move src/python to src/api/python,
william hubbs <=
- [PATCH 1/6] move src/c/clients to src/clients, Andrei Kholodnyi, 2010/09/15
- [PATCH 1/6] move src/c/clients to src/clients, william hubbs, 2010/09/15
- [PATCH 2/6] move src/c/api to src/api/c, william hubbs, 2010/09/15
- [PATCH 3/6] move src/cl to src/api/cl, william hubbs, 2010/09/15
- [PATCH 4/6] move src/guile to src/api/guile, william hubbs, 2010/09/15
- [PATCH 5/6] move src/python to src/api/python, william hubbs, 2010/09/15
- [PATCH 6/6] remove DIST_SUBDIRS line from src/Makefile.am, william hubbs, 2010/09/15
- [PATCH 1/6] move src/c/clients to src/clients, Andrei Kholodnyi, 2010/09/16