[orca] Fix for bgo#618927 - Need to add Open TTS to speech servers.
- From: Mesar Hameed <mhameed src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [orca] Fix for bgo#618927 - Need to add Open TTS to speech servers.
- Date: Wed, 26 May 2010 16:52:23 +0000 (UTC)
commit 2b78bab2e4f2da279557b197cd8d85b936e31a41
Author: Steve Holmes <steve holmes88 gmail com>
Date: Mon May 17 14:38:27 2010 -0700
Fix for bgo#618927 - Need to add Open TTS to speech servers.
openttsfactory.py is cloned from speechdispatcherfactory.py and calls
opentts instead of speechd. This allows a user to choose either
Speech Dispatcher or Open TTS.
src/orca/Makefile.am | 1 +
src/orca/openttsfactory.py | 427 ++++++++++++++++++++++++++++++++++++++++++++
src/orca/settings.py | 1 +
3 files changed, 429 insertions(+), 0 deletions(-)
---
diff --git a/src/orca/Makefile.am b/src/orca/Makefile.am
index 931d57c..697cd00 100644
--- a/src/orca/Makefile.am
+++ b/src/orca/Makefile.am
@@ -40,6 +40,7 @@ orca_python_PYTHON = \
liveregions.py \
mag.py \
mouse_review.py \
+ openttsfactory.py \
orca.py \
orca_console_prefs.py \
orca_gtkbuilder.py \
diff --git a/src/orca/openttsfactory.py b/src/orca/openttsfactory.py
new file mode 100644
index 0000000..c652446
--- /dev/null
+++ b/src/orca/openttsfactory.py
@@ -0,0 +1,427 @@
+# Copyright 2006, 2007, 2008, 2009 Brailcom, o.p.s.
+#
+# This is basically cloned from speechdispatcherfactory.py
+# Author: Steve Holmes <steve holmes88 gmail com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Library General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library 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
+# Library General Public License for more details.
+#
+# You should have received a copy of the GNU Library General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
+# Boston MA 02110-1301 USA.
+
+# # [[[TODO: richb - Pylint is giving us a bunch of warnings along these
+# lines throughout this file:
+#
+# W0142:202:SpeechServer._send_command: Used * or ** magic
+#
+# So for now, we just disable these warnings in this module.]]]
+#
+# pylint: disable-msg=W0142
+
+"""Provides an Orca speech server for Open TTS backend.
+
+NOTE: THIS IS EXPERIMENTAL ONLY AND IS NOT A SUPPORTED COMPONENT OF ORCA.
+"""
+
+__id__ = "$Id$"
+__version__ = "$Revision$"
+__date__ = "$Date$"
+__author__ = "Steve Holmes <steve holmes88 gmail com>"
+__copyright__ = "Copyright (c) 2006-2008 Brailcom, o.p.s."
+__license__ = "LGPL"
+
+import gobject
+
+import debug
+import speechserver
+import settings
+import orca
+from acss import ACSS
+from orca_i18n import _
+
+try:
+ import opentts
+except:
+ _opentts_available = False
+else:
+ _opentts_available = True
+ try:
+ getattr(opentts, "CallbackType")
+ except AttributeError:
+ _opentts_version_ok = False
+ else:
+ _opentts_version_ok = True
+
+class SpeechServer(speechserver.SpeechServer):
+ # See the parent class for documentation.
+
+ _active_servers = {}
+
+ DEFAULT_SERVER_ID = 'default'
+
+ # Translators: "Default Synthesizer" will appear in the list of available
+ # speech engines as a special item. It refers to the default engine
+ # configured within the speech subsystem. Apart from this item, the user
+ # will have a chance to select a particular speech engine by its real
+ # name, such as Festival, IBMTTS, etc.
+ #
+ _SERVER_NAMES = {DEFAULT_SERVER_ID: _("Default Synthesizer")}
+
+ KEY_NAMES = {
+ '_': 'underscore',
+ 'space': 'space',
+ '"': 'double-quote',
+ }
+
+
+ def getFactoryName():
+ # Translators: this is the name of a speech synthesis system
+ # called "Open TTS".
+ #
+ return _("Open TTS")
+ getFactoryName = staticmethod(getFactoryName)
+
+ def getSpeechServers():
+ servers = []
+ default = SpeechServer._getSpeechServer(SpeechServer.DEFAULT_SERVER_ID)
+ if default is not None:
+ servers.append(default)
+ for module in default.list_output_modules():
+ servers.append(SpeechServer._getSpeechServer(module))
+ return servers
+ getSpeechServers = staticmethod(getSpeechServers)
+
+ def _getSpeechServer(cls, serverId):
+ """Return an active server for given id.
+
+ Attempt to create the server if it doesn't exist yet. Returns None
+ when it is not possible to create the server.
+
+ """
+ if serverId not in cls._active_servers:
+ cls(serverId)
+ # Don't return the instance, unless it is succesfully added
+ # to `_active_Servers'.
+ return cls._active_servers.get(serverId)
+ _getSpeechServer = classmethod(_getSpeechServer)
+
+ def getSpeechServer(info=None):
+ if info is not None:
+ thisId = info[1]
+ else:
+ thisId = SpeechServer.DEFAULT_SERVER_ID
+ return SpeechServer._getSpeechServer(thisId)
+ getSpeechServer = staticmethod(getSpeechServer)
+
+ def shutdownActiveServers():
+ for server in SpeechServer._active_servers.values():
+ server.shutdown()
+ shutdownActiveServers = staticmethod(shutdownActiveServers)
+
+ # *** Instance methods ***
+
+ def __init__(self, serverId):
+ super(SpeechServer, self).__init__()
+ self._id = serverId
+ self._client = None
+ self._current_voice_properties = {}
+ self._acss_manipulators = (
+ (ACSS.RATE, self._set_rate),
+ (ACSS.AVERAGE_PITCH, self._set_pitch),
+ (ACSS.GAIN, self._set_volume),
+ (ACSS.FAMILY, self._set_family),
+ )
+ if not _opentts_available:
+ debug.println(debug.LEVEL_WARNING,
+ "Open TTS interface not installed.")
+ return
+ if not _opentts_version_ok:
+ debug.println(debug.LEVEL_WARNING,
+ "Open TTS version 1.0 or later is required.")
+ return
+ # The following constants must be initialized in runtime since they
+ # depend on the opentts module being available.
+ self._PUNCTUATION_MODE_MAP = {
+ settings.PUNCTUATION_STYLE_ALL: opentts.PunctuationMode.ALL,
+ settings.PUNCTUATION_STYLE_MOST: opentts.PunctuationMode.SOME,
+ settings.PUNCTUATION_STYLE_SOME: opentts.PunctuationMode.SOME,
+ settings.PUNCTUATION_STYLE_NONE: opentts.PunctuationMode.NONE,
+ }
+ self._CALLBACK_TYPE_MAP = {
+ opentts.CallbackType.BEGIN: speechserver.SayAllContext.PROGRESS,
+ opentts.CallbackType.CANCEL: speechserver.SayAllContext.INTERRUPTED,
+ opentts.CallbackType.END: speechserver.SayAllContext.COMPLETED,
+ #opentts.CallbackType.INDEX_MARK:speechserver.SayAllContext.PROGRESS,
+ }
+ # Translators: This string will appear in the list of
+ # available voices for the current speech engine. %s will be
+ # replaced by the name of the current speech engine, such as
+ # "Festival default voice" or "IBMTTS default voice". It
+ # refers to the default voice configured for given speech
+ # engine within the speech subsystem. Apart from this item,
+ # the list will contain the names of all available "real"
+ # voices provided by the speech engine.
+ #
+ self._default_voice_name = _("%s default voice") % serverId
+
+ try:
+ self._init()
+ except:
+ debug.println(debug.LEVEL_WARNING,
+ "Open TTS service failed to connect:")
+ debug.printException(debug.LEVEL_WARNING)
+ else:
+ SpeechServer._active_servers[serverId] = self
+
+ def _init(self):
+ self._client = client = opentts.SSIPClient('Orca', component=self._id)
+ if self._id != self.DEFAULT_SERVER_ID:
+ client.set_output_module(self._id)
+ self._current_voice_properties = {}
+ mode = self._PUNCTUATION_MODE_MAP[settings.verbalizePunctuationStyle]
+ client.set_punctuation(mode)
+
+ def updatePunctuationLevel(self):
+ """ Punctuation level changed, inform this speechServer. """
+ mode = self._PUNCTUATION_MODE_MAP[settings.verbalizePunctuationStyle]
+ self._client.set_punctuation(mode)
+
+ def _send_command(self, command, *args, **kwargs):
+ if hasattr(opentts, 'SSIPCommunicationError'):
+ try:
+ return command(*args, **kwargs)
+ except opentts.SSIPCommunicationError:
+ debug.println(debug.LEVEL_CONFIGURATION,
+ "Open TTS connection lost. "
+ "Trying to reconnect.")
+ self.reset()
+ return command(*args, **kwargs)
+ else:
+ # It is not possible tho catch the error with older SD versions.
+ return command(*args, **kwargs)
+
+ def _set_rate(self, acss_rate):
+ rate = int(2 * max(0, min(99, acss_rate)) - 98)
+ self._send_command(self._client.set_rate, rate)
+
+ def _set_pitch(self, acss_pitch):
+ pitch = int(20 * max(0, min(9, acss_pitch)) - 90)
+ self._send_command(self._client.set_pitch, pitch)
+
+ def _set_volume(self, acss_volume):
+ volume = int(15 * max(0, min(9, acss_volume)) - 35)
+ self._send_command(self._client.set_volume, volume)
+
+ def _set_family(self, acss_family):
+ locale = acss_family[speechserver.VoiceFamily.LOCALE]
+ if locale:
+ lang = locale.split('_')[0]
+ if lang and len(lang) == 2:
+ self._send_command(self._client.set_language, lang)
+ try:
+ # This command is not available with older SD versions.
+ set_synthesis_voice = self._client.set_synthesis_voice
+ except AttributeError:
+ pass
+ else:
+ name = acss_family[speechserver.VoiceFamily.NAME]
+ if name != self._default_voice_name:
+ self._send_command(set_synthesis_voice, name)
+
+ def _apply_acss(self, acss):
+ if acss is None:
+ acss = settings.voices[settings.DEFAULT_VOICE]
+ current = self._current_voice_properties
+ for acss_property, method in self._acss_manipulators:
+ value = acss.get(acss_property)
+ if value is not None and current.get(acss_property) != value:
+ method(value)
+ current[acss_property] = value
+
+ def _speak(self, text, acss, **kwargs):
+ # Replace no break space characters with plain spaces since some
+ # synthesizers cannot handle them. See bug #591734.
+ #
+ text = text.decode("UTF-8").replace(u'\u00a0', ' ').encode("UTF-8")
+
+ # Replace newline followed by full stop, since
+ # this seems to crash sd, see bgo#618334.
+ #
+ text = text.replace('\n.', '\n')
+
+ self._apply_acss(acss)
+ self._send_command(self._client.speak, text, **kwargs)
+
+ def _say_all(self, iterator, orca_callback):
+ """Process another sayAll chunk.
+
+ Called by the gidle thread.
+
+ """
+ try:
+ context, acss = iterator.next()
+ except StopIteration:
+ pass
+ else:
+ def callback(callbackType, index_mark=None):
+ # This callback is called in Open TTS listener thread.
+ # No subsequent Open TTS interaction is allowed here,
+ # so we pass the calls to the gidle thread.
+ t = self._CALLBACK_TYPE_MAP[callbackType]
+ if t == speechserver.SayAllContext.PROGRESS:
+ if index_mark:
+ context.currentOffset = int(index_mark)
+ else:
+ context.currentOffset = context.startOffset
+ elif t == speechserver.SayAllContext.COMPLETED:
+ context.currentOffset = context.endOffset
+ gobject.idle_add(orca_callback, context, t)
+ if t == speechserver.SayAllContext.COMPLETED:
+ gobject.idle_add(self._say_all, iterator, orca_callback)
+ self._speak(context.utterance, acss, callback=callback,
+ event_types=self._CALLBACK_TYPE_MAP.keys())
+ return False # to indicate, that we don't want to be called again.
+
+ def _cancel(self):
+ self._send_command(self._client.cancel)
+
+ def _change_default_speech_rate(self, decrease=False):
+ acss = settings.voices[settings.DEFAULT_VOICE]
+ delta = settings.speechRateDelta * (decrease and -1 or +1)
+ rate = acss[ACSS.RATE]
+ acss[ACSS.RATE] = max(0, min(99, rate + delta))
+ debug.println(debug.LEVEL_CONFIGURATION,
+ "Speech rate is now %d" % rate)
+ # Translators: This string announces speech rate change.
+ self.speak(decrease and _("slower.") or _("faster."), acss=acss)
+
+ def _change_default_speech_pitch(self, decrease=False):
+ acss = settings.voices[settings.DEFAULT_VOICE]
+ delta = settings.speechPitchDelta * (decrease and -1 or +1)
+ pitch = acss[ACSS.AVERAGE_PITCH]
+ acss[ACSS.AVERAGE_PITCH] = max(0, min(9, pitch + delta))
+ debug.println(debug.LEVEL_CONFIGURATION,
+ "Speech pitch is now %d" % pitch)
+ # Translators: This string announces speech pitch change.
+ self.speak(decrease and _("lower.") or _("higher."), acss=acss)
+
+ def getInfo(self):
+ return [self._SERVER_NAMES.get(self._id, self._id), self._id]
+
+ def getVoiceFamilies(self):
+ # Always offer the configured default voice with a language
+ # set according to the current locale.
+ from locale import getlocale, LC_MESSAGES
+ locale = getlocale(LC_MESSAGES)[0]
+ if locale is None or locale == 'C':
+ lang = None
+ else:
+ lang = locale.split('_')[0]
+ voices = ((self._default_voice_name, lang, None),)
+ try:
+ # This command is not available with older SD versions.
+ list_synthesis_voices = self._client.list_synthesis_voices
+ except AttributeError:
+ pass
+ else:
+ voices += self._send_command(list_synthesis_voices)
+ families = [speechserver.VoiceFamily({ \
+ speechserver.VoiceFamily.NAME: name,
+ #speechserver.VoiceFamily.GENDER: speechserver.VoiceFamily.MALE,
+ speechserver.VoiceFamily.LOCALE: lang})
+ for name, lang, dialect in voices]
+ return families
+
+ def speak(self, text=None, acss=None, interrupt=True):
+ #if interrupt:
+ # self._cancel()
+
+ if text:
+ self._speak(text, acss)
+
+ def queueText(self, text="", acss=None):
+ if text:
+ self._speak(text, acss)
+
+ def speakUtterances(self, utteranceList, acss=None, interrupt=True):
+ #if interrupt:
+ # self._cancel()
+ for utterance in utteranceList:
+ if utterance:
+ self._speak(utterance, acss)
+
+ def sayAll(self, utteranceIterator, progressCallback):
+ gobject.idle_add(self._say_all, utteranceIterator, progressCallback)
+
+ def speakCharacter(self, character, acss=None):
+ self._apply_acss(acss)
+ if character == '\n':
+ self._send_command(self._client.sound_icon, 'end-of-line')
+ else:
+ self._send_command(self._client.char, character)
+
+ def speakKeyEvent(self, event_string, eventType):
+ if eventType == orca.KeyEventType.PRINTABLE:
+ # We currently only handle printable characters by Open
+ # TTS's KEY command. For other keys, such as Ctrl, Shift
+ # etc. we prefer Orca's verbalization.
+ if event_string.decode("UTF-8").isupper():
+ acss = settings.voices[settings.UPPERCASE_VOICE]
+ else:
+ acss = None
+ key = self.KEY_NAMES.get(event_string, event_string)
+ self._apply_acss(acss)
+ self._send_command(self._client.key, key)
+ else:
+ return super(SpeechServer, self).speakKeyEvent(event_string,
+ eventType)
+
+ def increaseSpeechRate(self, step=5):
+ self._change_default_speech_rate()
+
+ def decreaseSpeechRate(self, step=5):
+ self._change_default_speech_rate(decrease=True)
+
+ def increaseSpeechPitch(self, step=0.5):
+ self._change_default_speech_pitch()
+
+ def decreaseSpeechPitch(self, step=0.5):
+ self._change_default_speech_pitch(decrease=True)
+
+ def stop(self):
+ self._cancel()
+
+ def shutdown(self):
+ self._client.close()
+ del SpeechServer._active_servers[self._id]
+
+ def reset(self, text=None, acss=None):
+ self._client.close()
+ self._init()
+
+ def list_output_modules(self):
+ """Return names of available output modules as a tuple of strings.
+
+ This method is not a part of Orca speech API, but is used internally
+ by the Open TTS backend.
+
+ The returned tuple can be empty if the information can not be
+ obtained (e.g. with an older Open TTS version).
+
+ """
+ try:
+ return self._send_command(self._client.list_output_modules)
+ except AttributeError:
+ return ()
+ except opentts.SSIPCommandError:
+ return ()
+
diff --git a/src/orca/settings.py b/src/orca/settings.py
index 33d1424..94121ae 100644
--- a/src/orca/settings.py
+++ b/src/orca/settings.py
@@ -363,6 +363,7 @@ silenceSpeech = False
#
speechFactoryModules = ["espeechfactory", \
"gnomespeechfactory", \
+ "openttsfactory", \
"speechdispatcherfactory"]
speechServerFactory = "gnomespeechfactory"
speechServerInfo = None # None means let the factory decide.
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]