diff --git a/libspeechd.so b/libspeechd.so new file mode 120000 index 0000000..4880363 --- /dev/null +++ b/libspeechd.so @@ -0,0 +1 @@ +libspeechd.so.2.6.0 \ No newline at end of file diff --git a/libspeechd.so.2 b/libspeechd.so.2 new file mode 120000 index 0000000..4880363 --- /dev/null +++ b/libspeechd.so.2 @@ -0,0 +1 @@ +libspeechd.so.2.6.0 \ No newline at end of file diff --git a/libspeechd.so.2.6.0 b/libspeechd.so.2.6.0 new file mode 100755 index 0000000..e0b3b35 Binary files /dev/null and b/libspeechd.so.2.6.0 differ diff --git a/stardew-access/LinuxSpeech/__init__.py b/stardew-access/LinuxSpeech/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/stardew-access/LinuxSpeech/_test.py b/stardew-access/LinuxSpeech/_test.py deleted file mode 100644 index fc5d88b..0000000 --- a/stardew-access/LinuxSpeech/_test.py +++ /dev/null @@ -1,9 +0,0 @@ -import wrapper - -speech = wrapper.Speech - -speech.Initialize(self=speech) - -speech.Say(self=speech, text="hello", interrupt=False) - -speech.Close(self=speech) \ No newline at end of file diff --git a/stardew-access/LinuxSpeech/_test_interrupt.py b/stardew-access/LinuxSpeech/_test_interrupt.py deleted file mode 100644 index 2c1de01..0000000 --- a/stardew-access/LinuxSpeech/_test_interrupt.py +++ /dev/null @@ -1,14 +0,0 @@ -import wrapper -import time - -speech = wrapper.Speech - -speech.Initialize(self=speech) - -speech.Say(self=speech, text="This is a very very very long string.", interrupt=False) - -time.sleep(1) - -speech.Say(self=speech, text="I interrupted :)", interrupt=True) - -speech.Close(self=speech) \ No newline at end of file diff --git a/stardew-access/LinuxSpeech/speechd/__init__.py b/stardew-access/LinuxSpeech/speechd/__init__.py deleted file mode 100644 index ee45936..0000000 --- a/stardew-access/LinuxSpeech/speechd/__init__.py +++ /dev/null @@ -1,17 +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 Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . - -from .client import * - diff --git a/stardew-access/LinuxSpeech/speechd/client.py b/stardew-access/LinuxSpeech/speechd/client.py deleted file mode 100644 index b009e3f..0000000 --- a/stardew-access/LinuxSpeech/speechd/client.py +++ /dev/null @@ -1,1186 +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 Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . - -"""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 - -from . 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 = b"\r\n" - _END_OF_DATA_MARKER = b'.' - _END_OF_DATA_MARKER_ESCAPED = b'..' - _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 as ex: - raise SSIPCommunicationError("Can't open socket using method " - + communication_method, - original_exception = ex) - - self._buffer = b"" - 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", - daemon=True) - 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.decode('utf-8') - - 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.is_alive(): - 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.encode('utf-8') + 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. - - """ - data = data.encode('utf-8') - # 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 some of the user-defined punctuation characters are read.""" - MOST = 'most' - """Only most of 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:/run/user/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 $XDG_RUNTIME_DIR/speech-dispatcher/speechd.sock - where $XDG_RUNTIME_DIR is determined using the XDG Base Directory - Specification. - 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. - """ - - _home = os.path.expanduser("~") - _runtime_dir = os.environ.get('XDG_RUNTIME_DIR', os.environ.get('XDG_CACHE_HOME', os.path.join(_home, '.cache'))) - _sock_path = os.path.join(_runtime_dir, self.DEFAULT_SOCKET_PATH) - # Resolve connection parameters: - connection_args = {'communication_method': CommunicationMethod.UNIX_SOCKET, - 'socket_path': _sock_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 as 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 as 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),)) - cmd = os.getenv("SPEECHD_CMD") - if not cmd: - cmd = paths.SPD_SPAWN_CMD - if os.path.exists(cmd): - connection_params = [] - for param, value in connection_args.items(): - if param not in ["host",]: - connection_params += ["--"+param.replace("_","-"), str(value)] - - server = subprocess.Popen([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" - % (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') - 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. - - """ - 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, variant). - - 'name' is a string, 'language' is an ISO 639-1 Alpha-2/3 language code - and 'variant' is a string. Language and variant may be None. - - """ - try: - code, msg, data = self._conn.send_command('LIST', 'SYNTHESIS_VOICES') - except SSIPCommandError: - return () - def split(item): - name, lang, variant = tuple(item.rsplit('\t', 3)) - return (name, lang or None, variant 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/three letter language code according to RFC 1766 as string, possibly with a region qualification. - scope -- see the documentation of this class. - - """ - assert isinstance(language, str) - self._conn.send_command('SET', scope, 'LANGUAGE', language) - - def get_language(self): - """Get the current language.""" - code, msg, data = self._conn.send_command('GET', 'LANGUAGE') - if data: - return data[0] - return None - - 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 get_output_module(self): - """Get the current output module.""" - code, msg, data = self._conn.send_command('GET', 'OUTPUT_MODULE') - if data: - return data[0] - return None - - 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 get_pitch(self): - """Get the current pitch.""" - code, msg, data = self._conn.send_command('GET', 'PITCH') - if data: - return data[0] - return None - - def set_pitch_range(self, value, scope=Scope.SELF): - """Set the pitch range for further speech commands. - - Arguments: - value -- integer value within the range from -100 to 100, with 0 - corresponding to the default pitch range of the current speech synthesis - output module, lower values meaning lower pitch range and higher values - meaning higher pitch range. - scope -- see the documentation of this class. - - """ - assert isinstance(value, int) and -100 <= value <= 100, value - self._conn.send_command('SET', scope, 'PITCH_RANGE', 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 get_rate(self): - """Get the current speech rate (speed).""" - code, msg, data = self._conn.send_command('GET', 'RATE') - if data: - return data[0] - return None - - 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 get_volume(self): - """Get the speech volume.""" - code, msg, data = self._conn.send_command('GET', 'VOLUME') - if data: - return data[0] - return None - - 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.MOST, - PunctuationMode.SOME, PunctuationMode.NONE), value - self._conn.send_command('SET', scope, 'PUNCTUATION', value) - - def get_punctuation(self): - """Get the punctuation pronounciation level.""" - code, msg, data = self._conn.send_command('GET', 'PUNCTUATION') - if data: - return data[0] - return None - - 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_TYPE', 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/stardew-access/LinuxSpeech/speechd/paths.py b/stardew-access/LinuxSpeech/speechd/paths.py deleted file mode 100644 index da8f18a..0000000 --- a/stardew-access/LinuxSpeech/speechd/paths.py +++ /dev/null @@ -1 +0,0 @@ -SPD_SPAWN_CMD = "/usr/local/bin/speech-dispatcher" diff --git a/stardew-access/LinuxSpeech/wrapper.py b/stardew-access/LinuxSpeech/wrapper.py deleted file mode 100644 index 6b53f61..0000000 --- a/stardew-access/LinuxSpeech/wrapper.py +++ /dev/null @@ -1,18 +0,0 @@ -import speechd - -class Speech: - client = None - - def Initialize(self): - self.client = speechd.SSIPClient('stardew-access') - - def Say(self, text, interrupt): - if(self.client is not None): - if(interrupt): - self.client.stop() - - self.client.speak(text) - - def Close(self): - if(self.client is not None): - self.client.close() \ No newline at end of file diff --git a/stardew-access/ScreenReader.cs b/stardew-access/ScreenReader.cs index bdd4fca..f0360fd 100644 --- a/stardew-access/ScreenReader.cs +++ b/stardew-access/ScreenReader.cs @@ -8,32 +8,11 @@ namespace stardew_access public class ScreenReader { public IAccessibleOutput? screenReader = null; - public dynamic wrapperInstance = null; public string prevText = "", prevTextTile = " ", prevChatText = "", prevMenuText = ""; /// Initializes the screen reader. public void InitializeScreenReader() { - MainClass.monitor.Log($"here! {RuntimeInformation.OSDescription}", LogLevel.Debug); - if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - MainClass.monitor.Log($"here!", LogLevel.Debug); - //instance of python engine - var engine = Python.CreateEngine(); - //reading code from file - var source = engine.CreateScriptSourceFromFile(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "LinuxSpeech", "wrapper.py")); - var scope = engine.CreateScope(); - //executing script in scope - source.Execute(scope); - var wrapper = scope.GetVariable("Speech"); - //initializing class - wrapperInstance = engine.Operations.CreateInstance(wrapper); - wrapperInstance.Initialize(); - - MainClass.monitor.Log($"here!", LogLevel.Debug); - - return; - } if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return; @@ -78,12 +57,6 @@ namespace stardew_access /// Whether to skip the currently speaking text or not. public void Say(string text, bool interrupt) { - if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - wrapperInstance.Say(text, interrupt); - return; - } - if (screenReader == null) return; diff --git a/stardew-access/stardew-access.csproj b/stardew-access/stardew-access.csproj index 45a9d76..e324c22 100644 --- a/stardew-access/stardew-access.csproj +++ b/stardew-access/stardew-access.csproj @@ -7,8 +7,7 @@ enable preview AnyCPU - true - PackageReference + true