1
# Copyright (C) 2003-2008 Brailcom, o.p.s.
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Python API to Speech Dispatcher
19
Basic Python client API to Speech Dispatcher is provided by the 'SSIPClient'
20
class. This interface maps directly to available SSIP commands and logic.
22
A more convenient interface is provided by the 'Speaker' class.
26
#TODO: Blocking variants for speak, char, key, sound_icon.
28
import socket, sys, os, subprocess, time, tempfile
33
import dummy_threading as threading
37
class CallbackType(object):
38
"""Constants describing the available types of callbacks"""
39
INDEX_MARK = 'index_marks'
40
"""Index mark events are reported when the place they were
41
included into the text by the client application is reached
44
"""The begin event is reported when Speech Dispatcher starts
45
actually speaking the message."""
47
"""The end event is reported after the message has terminated and
48
there is no longer any sound from it being produced"""
50
"""The cancel event is reported when a message is canceled either
51
on request of the user, because of prioritization of messages or
54
"""The pause event is reported after speaking of a message
55
was paused. It no longer produces any audio."""
57
"""The resume event is reported right after speaking of a message
58
was resumed after previous pause."""
60
class SSIPError(Exception):
61
"""Common base class for exceptions during SSIP communication."""
63
class SSIPCommunicationError(SSIPError):
64
"""Exception raised when trying to operate on a closed connection."""
66
_additional_exception = None
68
def __init__(self, description=None, original_exception=None, **kwargs):
69
self._original_exception = original_exception
70
self._description = description
71
super(SSIPError, self).__init__(**kwargs)
73
def original_exception(self):
74
"""Return the original exception if any
76
If this exception is secondary, being caused by a lower
77
level exception, return this original exception, otherwise
79
return self._original_exception
81
def set_additional_exception(self, exception):
82
"""Set an additional exception
84
See method additional_exception().
86
self._additional_exception = exception
88
def additional_exception(self):
89
"""Return an additional exception
91
Additional exceptions araise from failed attempts to resolve
93
return self._additional_exception
95
def description(self):
96
"""Return error description"""
97
return self._description
101
if self.description():
102
msgs.append(self.description())
103
if self.original_exception:
104
msgs.append("Original error: " + str(self.original_exception()))
105
if self.additional_exception:
106
msgs.append("Additional error: " + str(self.additional_exception()))
107
return "\n".join(msgs)
109
class SSIPResponseError(Exception):
110
def __init__(self, code, msg, data):
111
Exception.__init__(self, "%s: %s" % (code, msg))
117
"""Return the server response error code as integer number."""
121
"""Return server response error message as string."""
125
class SSIPCommandError(SSIPResponseError):
126
"""Exception raised on error response after sending command."""
129
"""Return the command string which resulted in this error."""
133
class SSIPDataError(SSIPResponseError):
134
"""Exception raised on error response after sending data."""
137
"""Return the data which resulted in this error."""
141
class SpawnError(Exception):
142
"""Indicates failure in server autospawn."""
144
class CommunicationMethod(object):
145
"""Constants describing the possible methods of connection to server."""
146
UNIX_SOCKET = 'unix_socket'
147
"""Unix socket communication using a filesystem path"""
148
INET_SOCKET = 'inet_socket'
149
"""Inet socket communication using a host and port"""
151
class _SSIP_Connection(object):
152
"""Implemantation of low level SSIP communication."""
155
_END_OF_DATA_MARKER = b'.'
156
_END_OF_DATA_MARKER_ESCAPED = b'..'
157
_END_OF_DATA = _NEWLINE + _END_OF_DATA_MARKER + _NEWLINE
158
_END_OF_DATA_ESCAPED = _NEWLINE + _END_OF_DATA_MARKER_ESCAPED + _NEWLINE
159
# Constants representing \r\n. and \r\n..
160
_RAW_DOTLINE = _NEWLINE + _END_OF_DATA_MARKER
161
_ESCAPED_DOTLINE = _NEWLINE + _END_OF_DATA_MARKER_ESCAPED
163
_CALLBACK_TYPE_MAP = {700: CallbackType.INDEX_MARK,
164
701: CallbackType.BEGIN,
165
702: CallbackType.END,
166
703: CallbackType.CANCEL,
167
704: CallbackType.PAUSE,
168
705: CallbackType.RESUME,
171
def __init__(self, communication_method, socket_path, host, port):
172
"""Init connection: open the socket to server,
173
initialize buffers, launch a communication handling
177
if communication_method == CommunicationMethod.UNIX_SOCKET:
178
socket_family = socket.AF_UNIX
179
socket_connect_args = socket_path
180
elif communication_method == CommunicationMethod.INET_SOCKET:
182
socket_family = socket.AF_INET
183
socket_connect_args = (socket.gethostbyname(host), port)
185
raise ValueError("Unsupported communication method")
188
self._socket = socket.socket(socket_family, socket.SOCK_STREAM)
189
self._socket.connect(socket_connect_args)
190
except socket.error as ex:
191
raise SSIPCommunicationError("Can't open socket using method "
192
+ communication_method,
193
original_exception = ex)
196
self._com_buffer = []
197
self._callback = None
198
self._ssip_reply_semaphore = threading.Semaphore(0)
199
self._communication_thread = \
200
threading.Thread(target=self._communication, kwargs={},
201
name="SSIP client communication thread")
202
self._communication_thread.start()
205
"""Close the server connection, destroy the communication thread."""
206
# Read-write shutdown here is necessary, otherwise the socket.recv()
207
# function in the other thread won't return at last on some platforms.
209
self._socket.shutdown(socket.SHUT_RDWR)
213
# Wait for the other thread to terminate
214
self._communication_thread.join()
216
def _communication(self):
217
"""Handle incomming socket communication.
219
Listens for all incomming communication on the socket, dispatches
220
events and puts all other replies into self._com_buffer list in the
221
already parsed form as (code, msg, data). Each time a new item is
222
appended to the _com_buffer list, the corresponding semaphore
223
'self._ssip_reply_semaphore' is incremented.
225
This method is designed to run in a separate thread. The thread can be
226
interrupted by closing the socket on which it is listening for
231
code, msg, data = self._recv_message()
233
# If the socket has been closed, exit the thread
236
# This is not an index mark nor an event
237
self._com_buffer.append((code, msg, data))
238
self._ssip_reply_semaphore.release()
240
# Ignore the event if no callback function has been registered.
241
if self._callback is not None:
242
type = self._CALLBACK_TYPE_MAP[code]
243
if type == CallbackType.INDEX_MARK:
244
kwargs = {'index_mark': data[2]}
247
# Get message and client ID of the event
248
msg_id, client_id = map(int, data[:2])
249
self._callback(msg_id, client_id, type, **kwargs)
253
"""Read one whole line from the socket.
255
Blocks until the line delimiter ('_NEWLINE') is read.
258
pointer = self._buffer.find(self._NEWLINE)
261
d = self._socket.recv(1024)
267
pointer = self._buffer.find(self._NEWLINE)
268
line = self._buffer[:pointer]
269
self._buffer = self._buffer[pointer+len(self._NEWLINE):]
270
return line.decode('utf-8')
272
def _recv_message(self):
273
"""Read server response or a callback
274
and return the triplet (code, msg, data)."""
278
line = self._readline()
279
assert len(line) >= 4, "Malformed data received from server!"
280
code, sep, text = line[:3], line[3], line[4:]
281
assert code.isalnum() and (c is None or code == c) and \
282
sep in ('-', ' '), "Malformed data received from server!"
285
return int(code), msg, tuple(data)
288
def _recv_response(self):
289
"""Read server response from the communication thread
290
and return the triplet (code, msg, data)."""
291
# TODO: This check is dumb but seems to work. The main thread
292
# hangs without it, when the Speech Dispatcher connection is lost.
293
if not self._communication_thread.isAlive():
294
raise SSIPCommunicationError
295
self._ssip_reply_semaphore.acquire()
296
# The list is sorted, read the first item
297
response = self._com_buffer[0]
298
del self._com_buffer[0]
301
def send_command(self, command, *args):
302
"""Send SSIP command with given arguments and read server response.
304
Arguments can be of any data type -- they are all stringified before
305
being sent to the server.
307
Returns a triplet (code, msg, data), where 'code' is a numeric SSIP
308
response code as an integer, 'msg' is an SSIP rsponse message as string
309
and 'data' is a tuple of strings (all lines of response data) when a
310
response contains some data.
312
'SSIPCommandError' is raised in case of non 2xx return code. See SSIP
313
documentation for more information about server responses and codes.
315
'IOError' is raised when the socket was closed by the remote side.
319
if command in ('SET', 'CANCEL', 'STOP',):
320
assert args[0] in (Scope.SELF, Scope.ALL) \
321
or isinstance(args[0], int)
322
cmd = ' '.join((command,) + tuple(map(str, args)))
324
self._socket.send(cmd.encode('utf-8') + self._NEWLINE)
326
raise SSIPCommunicationError("Speech Dispatcher connection lost.")
327
code, msg, data = self._recv_response()
329
raise SSIPCommandError(code, msg, cmd)
330
return code, msg, data
332
def send_data(self, data):
333
"""Send multiline data and read server response.
335
Returned value is the same as for 'send_command()' method.
337
'SSIPDataError' is raised in case of non 2xx return code. See SSIP
338
documentation for more information about server responses and codes.
340
'IOError' is raised when the socket was closed by the remote side.
343
data = data.encode('utf-8')
344
# Escape the end-of-data marker even if present at the beginning
345
# The start of the string is also the start of a line.
346
if data.startswith(self._END_OF_DATA_MARKER):
347
l = len(self._END_OF_DATA_MARKER)
348
data = self._END_OF_DATA_MARKER_ESCAPED + data[l:]
350
# Escape the end of data marker at the start of each subsequent
351
# line. We can do that by simply replacing \r\n. with \r\n..,
352
# since the start of a line is immediately preceded by \r\n,
353
# when the line is not the beginning of the string.
354
data = data.replace(self._RAW_DOTLINE, self._ESCAPED_DOTLINE)
357
self._socket.send(data + self._END_OF_DATA)
359
raise SSIPCommunicationError("Speech Dispatcher connection lost.")
360
code, msg, response_data = self._recv_response()
362
raise SSIPDataError(code, msg, data)
363
return code, msg, response_data
365
def set_callback(self, callback):
366
"""Register a callback function for handling asynchronous events.
369
callback -- a callable object (function) which will be called to
370
handle asynchronous events (arguments described below). Passing
371
`None' results in removing the callback function and ignoring
372
events. Just one callback may be registered. Attempts to register
373
a second callback will result in the former callback being
376
The callback function must accept three positional arguments
377
('message_id', 'client_id', 'event_type') and an optional keyword
378
argument 'index_mark' (when INDEX_MARK events are turned on).
380
Note, that setting the callback function doesn't turn the events on.
381
The user is responsible to turn them on by sending the appropriate `SET
382
NOTIFICATION' command.
385
self._callback = callback
387
class _CallbackHandler(object):
388
"""Internal object which handles callbacks."""
390
def __init__(self, client_id):
391
self._client_id = client_id
393
self._lock = threading.Lock()
395
def __call__(self, msg_id, client_id, type, **kwargs):
396
if client_id != self._client_id:
397
# TODO: does that ever happen?
402
callback, event_types = self._callbacks[msg_id]
406
if event_types is None or type in event_types:
407
callback(type, **kwargs)
408
if type in (CallbackType.END, CallbackType.CANCEL):
409
del self._callbacks[msg_id]
413
def add_callback(self, msg_id, callback, event_types):
416
self._callbacks[msg_id] = (callback, event_types)
421
"""An enumeration of valid SSIP command scopes.
423
The constants of this class should be used to specify the 'scope' argument
424
for the 'Client' methods.
428
"""The command (mostly a setting) applies to current connection only."""
430
"""The command applies to all current Speech Dispatcher connections."""
433
class Priority(object):
434
"""An enumeration of valid SSIP message priorities.
436
The constants of this class should be used to specify the 'priority'
437
argument for the 'Client' methods. For more information about message
438
priorities and their interaction, see the SSIP documentation.
441
IMPORTANT = 'important'
444
NOTIFICATION = 'notification'
445
PROGRESS = 'progress'
448
class PunctuationMode(object):
449
"""Constants for selecting a punctuation mode.
451
The mode determines which characters should be read.
455
"""Read all punctuation characters."""
457
"""Don't read any punctuation character at all."""
459
"""Only the user-defined punctuation characters are read.
461
The set of characters is specified in Speech Dispatcher configuration.
465
class DataMode(object):
466
"""Constants specifying the type of data contained within messages
471
"""Data is plain text."""
473
"""Data is SSML (Speech Synthesis Markup Language)."""
476
class SSIPClient(object):
477
"""Basic Speech Dispatcher client interface.
479
This class provides a Python interface to Speech Dispatcher functionality
480
over an SSIP connection. The API maps directly to available SSIP commands.
481
Each connection to Speech Dispatcher is represented by one instance of this
484
Many commands take the 'scope' argument, thus it is shortly documented
485
here. It is either one of 'Scope' constants or a number of connection. By
486
specifying the connection number, you are applying the command to a
487
particular connection. This feature is only meant to be used by Speech
488
Dispatcher control application, however. More datails can be found in
489
Speech Dispatcher documentation.
493
DEFAULT_HOST = '127.0.0.1'
494
"""Default host for server connections."""
496
"""Default port number for server connections."""
497
DEFAULT_SOCKET_PATH = "speech-dispatcher/speechd.sock"
498
"""Default name of the communication unix socket"""
500
def __init__(self, name, component='default', user='unknown', address=None,
503
host=None, port=None, method=None, socket_path=None):
504
"""Initialize the instance and connect to the server.
507
name -- client identification string
508
component -- connection identification string. When one client opens
509
multiple connections, this can be used to identify each of them.
510
user -- user identification string (user name). When multi-user
511
acces is expected, this can be used to identify their connections.
512
address -- server address as specified in Speech Dispatcher
513
documentation (e.g. "unix:/run/user/joe/speech-dispatcher/speechd.sock"
514
or "inet:192.168.0.85:6561")
515
autospawn -- a flag to specify whether the library should
516
try to start the server if it determines its not already
519
Deprecated arguments:
520
method -- communication method to use, one of the constants defined in class
522
socket_path -- for CommunicationMethod.UNIX_SOCKET, socket
523
path in filesystem. By default, this is $XDG_RUNTIME_DIR/speech-dispatcher/speechd.sock
524
where $XDG_RUNTIME_DIR is determined using the XDG Base Directory
526
host -- for CommunicationMethod.INET_SOCKET, server hostname
527
or IP address as a string. If None, the default value is
528
taken from SPEECHD_HOST environment variable (if it
529
exists) or from the DEFAULT_HOST attribute of this class.
530
port -- for CommunicationMethod.INET_SOCKET method, server
531
port as number or None. If None, the default value is
532
taken from SPEECHD_PORT environment variable (if it
533
exists) or from the DEFAULT_PORT attribute of this class.
535
For more information on client identification strings see Speech
536
Dispatcher documentation.
539
_home = os.path.expanduser("~")
540
_runtime_dir = os.environ.get('XDG_RUNTIME_DIR', os.environ.get('XDG_CACHE_HOME', os.path.join(_home, '.cache')))
541
_sock_path = os.path.join(_runtime_dir, self.DEFAULT_SOCKET_PATH)
542
# Resolve connection parameters:
543
connection_args = {'communication_method': CommunicationMethod.UNIX_SOCKET,
544
'socket_path': _sock_path,
545
'host': self.DEFAULT_HOST,
546
'port': self.DEFAULT_PORT,
548
# Respect address method argument and SPEECHD_ADDRESS environemt variable
549
_address = address or os.environ.get("SPEECHD_ADDRESS")
552
connection_args.update(self._connection_arguments_from_address(_address))
553
# Respect the old (deprecated) key arguments and environment variables
554
# TODO: Remove this section in 0.8 release
556
# Read the environment variables
557
env_speechd_host = os.environ.get("SPEECHD_HOST")
559
env_speechd_port = int(os.environ.get("SPEECHD_PORT"))
561
env_speechd_port = None
562
env_speechd_socket_path = os.environ.get("SPEECHD_SOCKET")
563
# Prefer old (deprecated) function arguments, but if
564
# not specified and old (deprecated) environment variable
565
# is set, use the value of the environment variable
567
connection_args['method'] = method
569
connection_args['port'] = port
570
elif env_speechd_port:
571
connection_args['port'] = env_speechd_port
573
connection_args['socket_path'] = socket_path
574
elif env_speechd_socket_path:
575
connection_args['socket_path'] = env_speechd_socket_path
576
self._connect_with_autospawn(connection_args, autospawn)
577
self._initialize_connection(user, name, component)
579
def _connect_with_autospawn(self, connection_args, autospawn):
580
"""Establish new connection (and/or autospawn server)"""
582
self._conn = _SSIP_Connection(**connection_args)
583
except SSIPCommunicationError as ce:
584
# Suppose server might not be running, try the autospawn mechanism
585
if autospawn != False:
586
# Autospawn is however not guaranteed to start the server. The server
587
# will decide, based on it's configuration, whether to honor the request.
589
self._server_spawn(connection_args)
590
except SpawnError as se:
591
ce.set_additional_exception(se)
593
self._conn = _SSIP_Connection(**connection_args)
597
def _initialize_connection(self, user, name, component):
598
"""Initialize connection -- Set client name, get id, register callbacks etc."""
599
full_name = '%s:%s:%s' % (user, name, component)
600
self._conn.send_command('SET', Scope.SELF, 'CLIENT_NAME', full_name)
601
code, msg, data = self._conn.send_command('HISTORY', 'GET', 'CLIENT_ID')
602
self._client_id = int(data[0])
603
self._callback_handler = _CallbackHandler(self._client_id)
604
self._conn.set_callback(self._callback_handler)
605
for event in (CallbackType.INDEX_MARK,
610
CallbackType.RESUME):
611
self._conn.send_command('SET', 'self', 'NOTIFICATION', event, 'on')
613
def _connection_arguments_from_address(self, address):
614
"""Parse a Speech Dispatcher address line and return a dictionary
615
of connection arguments"""
617
address_params = address.split(":")
619
_method = address_params[0]
621
raise SSIPCommunicationErrror("Wrong format of server address")
622
connection_args['communication_method'] = _method
623
if _method == CommunicationMethod.UNIX_SOCKET:
625
connection_args['socket_path'] = address_params[1]
627
pass # The additional parameters was not set, let's stay with defaults
628
elif _method == CommunicationMethod.INET_SOCKET:
630
connection_args['host'] = address_params[1]
631
connection_args['port'] = int(address_params[2])
632
except ValueError: # Failed conversion to int
633
raise SSIPCommunicationError("Third parameter of inet_socket address must be a port number")
635
pass # The additional parameters was not set, let's stay with defaults
637
raise SSIPCommunicationError("Unknown communication method in address.");
638
return connection_args
641
"""Close the connection"""
644
def _server_spawn(self, connection_args):
645
"""Attempts to spawn the speech-dispatcher server."""
646
# Check whether we are not connecting to a remote host
647
# TODO: This is a hack. inet sockets specific code should
648
# belong to _SSIPConnection. We do not however have an _SSIPConnection
650
if connection_args['communication_method'] == 'inet_socket':
651
addrinfos = socket.getaddrinfo(connection_args['host'],
652
connection_args['port'])
653
# Check resolved addrinfos for presence of localhost
654
ip_addresses = [addrinfo[4][0] for addrinfo in addrinfos]
656
for ip in ip_addresses:
657
if ip.startswith("127.") or ip == "::1":
658
connection_args['host'] = ip
661
# The hostname didn't resolve on localhost in neither case,
662
# do not spawn server on localhost...
664
"Can't start server automatically (autospawn), requested address %s "
665
"resolves on %s which seems to be a remote host. You must start the "
666
"server manually or choose another connection address." % (connection_args['host'],
668
if os.path.exists(paths.SPD_SPAWN_CMD):
669
connection_params = []
670
for param, value in connection_args.items():
671
if param not in ["host",]:
672
connection_params += ["--"+param.replace("_","-"), str(value)]
674
server = subprocess.Popen([paths.SPD_SPAWN_CMD, "--spawn"]+connection_params,
675
stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
676
stdout_reply, stderr_reply = server.communicate()
677
retcode = server.wait()
679
raise SpawnError("Server refused to autospawn, stating this reason: %s" % (stderr_reply,))
682
raise SpawnError("Can't find Speech Dispatcher spawn command %s"
683
% (paths.SPD_SPAWN_CMD,))
685
def set_priority(self, priority):
686
"""Set the priority category for the following messages.
689
priority -- one of the 'Priority' constants.
692
assert priority in (Priority.IMPORTANT, Priority.MESSAGE,
693
Priority.TEXT, Priority.NOTIFICATION,
694
Priority.PROGRESS), priority
695
self._conn.send_command('SET', Scope.SELF, 'PRIORITY', priority)
697
def set_data_mode(self, value):
698
"""Set the data mode for further speech commands.
701
value - one of the constants defined by the DataMode class.
704
if value == DataMode.SSML:
706
elif value == DataMode.TEXT:
710
'Value "%s" is not one of the constants from the DataMode class.' % \
712
self._conn.send_command('SET', Scope.SELF, 'SSML_MODE', ssip_val)
714
def speak(self, text, callback=None, event_types=None):
715
"""Say given message.
718
text -- message text to be spoken. This may be either a UTF-8
719
encoded byte string or a Python unicode string.
720
callback -- a callback handler for asynchronous event notifications.
721
A callable object (function) which accepts one positional argument
722
`type' and one keyword argument `index_mark'. See below for more
724
event_types -- a tuple of event types for which the callback should
725
be called. Each item must be one of `CallbackType' constants.
726
None (the default value) means to handle all event types. This
727
argument is irrelevant when `callback' is not used.
729
The callback function will be called whenever one of the events occurs.
730
The event type will be passed as argument. Its value is one of the
731
`CallbackType' constants. In case of an index mark event, additional
732
keyword argument `index_mark' will be passed and will contain the index
733
mark identifier as specified within the text.
735
The callback function should not perform anything complicated and is
736
not allowed to issue any further SSIP client commands. An attempt to
737
do so would lead to a deadlock in SSIP communication.
739
This method is non-blocking; it just sends the command, given
740
message is queued on the server and the method returns immediately.
743
self._conn.send_command('SPEAK')
744
result = self._conn.send_data(text)
746
msg_id = int(result[2][0])
747
# TODO: Here we risk, that the callback arrives earlier, than we
748
# add the item to `self._callback_handler'. Such a situation will
749
# lead to the callback being ignored.
750
self._callback_handler.add_callback(msg_id, callback, event_types)
753
def char(self, char):
754
"""Say given character.
757
char -- a character to be spoken. Either a Python unicode string or
758
a UTF-8 encoded byte string.
760
This method is non-blocking; it just sends the command, given
761
message is queued on the server and the method returns immediately.
764
self._conn.send_command('CHAR', char.replace(' ', 'space'))
767
"""Say given key name.
770
key -- the key name (as defined in SSIP); string.
772
This method is non-blocking; it just sends the command, given
773
message is queued on the server and the method returns immediately.
776
self._conn.send_command('KEY', key)
778
def sound_icon(self, sound_icon):
779
"""Output given sound_icon.
782
sound_icon -- the name of the sound icon as defined by SSIP; string.
784
This method is non-blocking; it just sends the command, given message
785
is queued on the server and the method returns immediately.
788
self._conn.send_command('SOUND_ICON', sound_icon)
790
def cancel(self, scope=Scope.SELF):
791
"""Immediately stop speaking and discard messages in queues.
794
scope -- see the documentation of this class.
797
self._conn.send_command('CANCEL', scope)
800
def stop(self, scope=Scope.SELF):
801
"""Immediately stop speaking the currently spoken message.
804
scope -- see the documentation of this class.
807
self._conn.send_command('STOP', scope)
809
def pause(self, scope=Scope.SELF):
810
"""Pause speaking and postpone other messages until resume.
812
This method is non-blocking. However, speaking can continue for a
813
short while even after it's called (typically to the end of the
817
scope -- see the documentation of this class.
820
self._conn.send_command('PAUSE', scope)
822
def resume(self, scope=Scope.SELF):
823
"""Resume speaking of the currently paused messages.
825
This method is non-blocking. However, speaking can continue for a
826
short while even after it's called (typically to the end of the
830
scope -- see the documentation of this class.
833
self._conn.send_command('RESUME', scope)
835
def list_output_modules(self):
836
"""Return names of all active output modules as a tuple of strings."""
837
code, msg, data = self._conn.send_command('LIST', 'OUTPUT_MODULES')
840
def list_synthesis_voices(self):
841
"""Return names of all available voices for the current output module.
843
Returns a tuple of tripplets (name, language, dialect).
845
'name' is a string, 'language' is an ISO 639-1 Alpha-2 language code
846
and 'dialect' is a string. Language and dialect may be None.
850
code, msg, data = self._conn.send_command('LIST', 'SYNTHESIS_VOICES')
851
except SSIPCommandError:
854
name, lang, dialect = tuple(item.rsplit(' ', 3))
855
return (name, lang or None, dialect or None)
856
return tuple([split(item) for item in data])
858
def set_language(self, language, scope=Scope.SELF):
859
"""Switch to a particular language for further speech commands.
862
language -- two letter language code according to RFC 1776 as string.
863
scope -- see the documentation of this class.
866
assert isinstance(language, str) and len(language) == 2
867
self._conn.send_command('SET', scope, 'LANGUAGE', language)
869
def set_output_module(self, name, scope=Scope.SELF):
870
"""Switch to a particular output module.
873
name -- module (string) as returned by 'list_output_modules()'.
874
scope -- see the documentation of this class.
877
self._conn.send_command('SET', scope, 'OUTPUT_MODULE', name)
879
def set_pitch(self, value, scope=Scope.SELF):
880
"""Set the pitch for further speech commands.
883
value -- integer value within the range from -100 to 100, with 0
884
corresponding to the default pitch of the current speech synthesis
885
output module, lower values meaning lower pitch and higher values
886
meaning higher pitch.
887
scope -- see the documentation of this class.
890
assert isinstance(value, int) and -100 <= value <= 100, value
891
self._conn.send_command('SET', scope, 'PITCH', value)
893
def set_rate(self, value, scope=Scope.SELF):
894
"""Set the speech rate (speed) for further speech commands.
897
value -- integer value within the range from -100 to 100, with 0
898
corresponding to the default speech rate of the current speech
899
synthesis output module, lower values meaning slower speech and
900
higher values meaning faster speech.
901
scope -- see the documentation of this class.
904
assert isinstance(value, int) and -100 <= value <= 100
905
self._conn.send_command('SET', scope, 'RATE', value)
907
def set_volume(self, value, scope=Scope.SELF):
908
"""Set the speech volume for further speech commands.
911
value -- integer value within the range from -100 to 100, with 100
912
corresponding to the default speech volume of the current speech
913
synthesis output module, lower values meaning softer speech.
914
scope -- see the documentation of this class.
917
assert isinstance(value, int) and -100 <= value <= 100
918
self._conn.send_command('SET', scope, 'VOLUME', value)
920
def set_punctuation(self, value, scope=Scope.SELF):
921
"""Set the punctuation pronounciation level.
924
value -- one of the 'PunctuationMode' constants.
925
scope -- see the documentation of this class.
928
assert value in (PunctuationMode.ALL, PunctuationMode.SOME,
929
PunctuationMode.NONE), value
930
self._conn.send_command('SET', scope, 'PUNCTUATION', value)
932
def set_spelling(self, value, scope=Scope.SELF):
933
"""Toogle the spelling mode or on off.
936
value -- if 'True', all incomming messages will be spelled
937
instead of being read as normal words. 'False' switches
939
scope -- see the documentation of this class.
942
assert value in [True, False]
944
self._conn.send_command('SET', scope, 'SPELLING', "on")
946
self._conn.send_command('SET', scope, 'SPELLING', "off")
948
def set_cap_let_recogn(self, value, scope=Scope.SELF):
949
"""Set capital letter recognition mode.
952
value -- one of 'none', 'spell', 'icon'. None means no signalization
953
of capital letters, 'spell' means capital letters will be spelled
954
with a syntetic voice and 'icon' means that the capital-letter icon
955
will be prepended before each capital letter.
956
scope -- see the documentation of this class.
959
assert value in ("none", "spell", "icon")
960
self._conn.send_command('SET', scope, 'CAP_LET_RECOGN', value)
962
def set_voice(self, value, scope=Scope.SELF):
963
"""Set voice by a symbolic name.
966
value -- one of the SSIP symbolic voice names: 'MALE1' .. 'MALE3',
967
'FEMALE1' ... 'FEMALE3', 'CHILD_MALE', 'CHILD_FEMALE'
968
scope -- see the documentation of this class.
970
Symbolic voice names are mapped to real synthesizer voices in the
971
configuration of the output module. Use the method
972
'set_synthesis_voice()' if you want to work with real voices.
975
assert isinstance(value, str) and \
976
value.lower() in ("male1", "male2", "male3", "female1",
977
"female2", "female3", "child_male",
979
self._conn.send_command('SET', scope, 'VOICE', value)
981
def set_synthesis_voice(self, value, scope=Scope.SELF):
982
"""Set voice by its real name.
985
value -- voice name as returned by 'list_synthesis_voices()'
986
scope -- see the documentation of this class.
989
self._conn.send_command('SET', scope, 'SYNTHESIS_VOICE', value)
991
def set_pause_context(self, value, scope=Scope.SELF):
992
"""Set the amount of context when resuming a paused message.
995
value -- a positive or negative value meaning how many chunks of data
996
after or before the pause should be read when resume() is executed.
997
scope -- see the documentation of this class.
1000
assert isinstance(value, int)
1001
self._conn.send_command('SET', scope, 'PAUSE_CONTEXT', value)
1003
def set_debug(self, val):
1004
"""Switch debugging on and off. When switched on,
1005
debugging files will be created in the chosen destination
1006
(see set_debug_destination()) for Speech Dispatcher and all
1007
its running modules. All logging information will then be
1008
written into these files with maximal verbosity until switched
1009
off. You should always first call set_debug_destination.
1011
The intended use of this functionality is to switch debuging
1012
on for a period of time while the user will repeat the behavior
1013
and then send the logs to the appropriate bug-reporting place.
1016
val -- a boolean value determining whether debugging
1017
is switched on or off
1018
scope -- see the documentation of this class.
1021
assert isinstance(val, bool)
1027
self._conn.send_command('SET', scope.ALL, 'DEBUG', ssip_val)
1030
def set_debug_destination(self, path):
1031
"""Set debug destination.
1034
path -- path (string) to the directory where debuging
1035
files will be created
1036
scope -- see the documentation of this class.
1039
assert isinstance(val, string)
1041
self._conn.send_command('SET', scope.ALL, 'DEBUG_DESTINATION', val)
1043
def block_begin(self):
1044
"""Begin an SSIP block.
1046
See SSIP documentation for more details about blocks.
1049
self._conn.send_command('BLOCK', 'BEGIN')
1051
def block_end(self):
1052
"""Close an SSIP block.
1054
See SSIP documentation for more details about blocks.
1057
self._conn.send_command('BLOCK', 'END')
1060
"""Close the connection to Speech Dispatcher."""
1061
if hasattr(self, '_conn'):
1066
class Client(SSIPClient):
1067
"""A DEPRECATED backwards-compatible API.
1069
This Class is provided only for backwards compatibility with the prevoius
1070
unofficial API. It will be removed in future versions. Please use either
1071
'SSIPClient' or 'Speaker' interface instead. As deprecated, the API is no
1075
def __init__(self, name=None, client=None, **kwargs):
1076
name = name or client or 'python'
1077
super(Client, self).__init__(name, **kwargs)
1079
def say(self, text, priority=Priority.MESSAGE):
1080
self.set_priority(priority)
1083
def char(self, char, priority=Priority.TEXT):
1084
self.set_priority(priority)
1085
super(Client, self).char(char)
1087
def key(self, key, priority=Priority.TEXT):
1088
self.set_priority(priority)
1089
super(Client, self).key(key)
1091
def sound_icon(self, sound_icon, priority=Priority.TEXT):
1092
self.set_priority(priority)
1093
super(Client, self).sound_icon(sound_icon)
1096
class Speaker(SSIPClient):
1097
"""Extended Speech Dispatcher Interface.
1099
This class provides an extended intercace to Speech Dispatcher
1100
functionality and tries to hide most of the lower level details of SSIP
1101
(such as a more sophisticated handling of blocks and priorities and
1102
advanced event notifications) under a more convenient API.
1104
Please note that the API is not yet stabilized and thus is subject to
1105
change! Please contact the authors if you plan using it and/or if you have
1108
Well, in fact this class is currently not implemented at all. It is just a
1109
draft. The intention is to hide the SSIP details and provide a generic
1110
interface practical for screen readers.
1115
# Deprecated but retained for backwards compatibility
1117
# This class was introduced in 0.7 but later renamed to CommunicationMethod
1118
class ConnectionMethod(object):
1119
"""Constants describing the possible methods of connection to server.
1121
Retained for backwards compatibility but DEPRECATED. See CommunicationMethod."""
1122
UNIX_SOCKET = 'unix_socket'
1123
"""Unix socket communication using a filesystem path"""
1124
INET_SOCKET = 'inet_socket'
1125
"""Inet socket communication using a host and port"""