orca r4281 - in trunk: . src/orca
- From: wwalker svn gnome org
- To: svn-commits-list gnome org
- Subject: orca r4281 - in trunk: . src/orca
- Date: Wed, 8 Oct 2008 22:05:56 +0000 (UTC)
Author: wwalker
Date: Wed Oct 8 22:05:56 2008
New Revision: 4281
URL: http://svn.gnome.org/viewvc/orca?rev=4281&view=rev
Log:
Fix for bug #552343 - Quoting special characters in espeechfactory.py and other changes
Modified:
trunk/ChangeLog
trunk/src/orca/dectalk.py
trunk/src/orca/espeechfactory.py
trunk/src/orca/outloud.py
Modified: trunk/src/orca/dectalk.py
==============================================================================
--- trunk/src/orca/dectalk.py (original)
+++ trunk/src/orca/dectalk.py Wed Oct 8 22:05:56 2008
@@ -32,6 +32,27 @@
__copyright__ = "Copyright (c) 2005-2008 Google Inc."
__license__ = "LGPL"
+import chnames
+
+# Handling of special characters
+#
+# Emacspeak uses Tcl syntax to communicate with its speech servers. It
+# embraces text in curly braces, so that at least {, }, and \ must be quoted
+# when sending text to speech server. But individual speech engines have
+# their own special characters in addition to those of Tcl. Dectalk
+# perceives speech parameters enclosed in square brackets, and Emacspeak
+# exploits this to transmit speech settings to Dectalk. Thus we must quote
+# [ and ] too.
+
+def makeSpecialCharMap():
+ """Returns list of pairs mapping characters which are special for
+ Dectalk speech server to their replacements.
+ """
+ chars = r'{\}[]'
+ return [(ch, ' '+chnames.getCharacterName(ch)+' ') for ch in chars]
+
+
+# Speech parameters
_defined_voices = {}
Modified: trunk/src/orca/espeechfactory.py
==============================================================================
--- trunk/src/orca/espeechfactory.py (original)
+++ trunk/src/orca/espeechfactory.py Wed Oct 8 22:05:56 2008
@@ -47,6 +47,7 @@
__all__ = ['Speaker']
import os
+import re
import debug
import settings
@@ -87,7 +88,12 @@
# singleton instance of any given server.
#
__activeServers = {}
- __getSpeechServersCalled = False
+
+ # A regexp to match chunks of text which will be sent to speech server
+ # NB: this must always match, the question is how many characters.
+ _speechChunkRegexp = ('.{0,120}?[\n,.;:!?)][]}"]?\\s+'
+ '|.{1,120}(?:\\Z|\\s+)'
+ '|.{1,120}')
def getFactoryName():
"""Returns a localized name describing this factory."""
@@ -100,30 +106,32 @@
getFactoryName = staticmethod(getFactoryName)
def getSpeechServers():
- """Enumerate available speech servers.
-
- Returns a list of [name, id] values identifying the available
- speech servers. The name is a human consumable string and the
- id is an object that can be used to create a speech server
- via the getSpeechServer method.
+ """Gets available speech servers as a list. The caller
+ is responsible for calling the shutdown() method of each
+ speech server returned.
"""
- if SpeechServer.__getSpeechServersCalled:
- return SpeechServer.__activeServers.values()
- else:
- SpeechServer.__getSpeechServersCalled = True
-
- f = open(os.path.join(SpeechServer.location, '.servers'))
- for line in f:
- if line[0] == '#' or line.strip() == '':
+ haveNewServers = False
+ serversConf = file(os.path.join(SpeechServer.location, '.servers'))
+ for name in serversConf:
+ name = name.strip()
+ if name == '' or name[0] == '#':
continue
- name = line.strip()
if name not in SpeechServer.__activeServers:
try:
SpeechServer.__activeServers[name] = SpeechServer(name)
+ haveNewServers = True
except:
- pass
- f.close()
+ debug.printException(debug.LEVEL_WARNING)
+ serversConf.close()
+
+ # Check whether some servers have died and remove those from the list
+ if haveNewServers:
+ from time import sleep
+ sleep(1)
+ for server in SpeechServer.__activeServers.values():
+ if not server._process or server._process.poll() is not None:
+ server.shutdown()
return SpeechServer.__activeServers.values()
@@ -147,10 +155,8 @@
def shutdownActiveServers():
"""Cleans up and shuts down this factory.
"""
- for key in SpeechServer.__activeServers.keys():
- server = SpeechServer.__activeServers[key]
+ for server in SpeechServer.__activeServers.values():
server.shutdown()
- SpeechServer.__getSpeechServersCalled = False
shutdownActiveServers = staticmethod(shutdownActiveServers)
@@ -163,6 +169,9 @@
speechserver.SpeechServer.__init__(self)
self._engine = engine
+ self._process = None
+ self._output = None
+
e = __import__(_getcodes(engine),
globals(),
locals(),
@@ -170,15 +179,25 @@
self.getvoice = e.getvoice
self.getrate = e.getrate
self.getvoicelist = e.getvoicelist
+ self._specialChars = e.makeSpecialCharMap()
if host == 'localhost':
self._server = os.path.join(SpeechServer.location, self._engine)
else:
self._server = os.path.join(SpeechServer.location,
"ssh-%s" % self._engine)
- cmd = '{ ' + self._server + '; } 2>&1'
- #print "Command = ", cmd
- #self._output = os.popen(cmd, "w", 1)
- [self._output, stdout, stderr] = os.popen3(cmd, "w", 1)
+
+ # Start the process and close its output channels since they were
+ # closed implicitly in previous version of espeechfactory.py.
+ from subprocess import (Popen, PIPE)
+ proc = Popen(self._server, close_fds=True,
+ stdin=PIPE, stdout=PIPE, stderr=PIPE)
+ proc.stdout.close()
+ proc.stderr.close()
+ proc.stdout = proc.stderr = None
+ self._output = proc.stdin
+ self._process = proc
+
+ self._speechChunk = re.compile(self._speechChunkRegexp, re.DOTALL)
self._settings = {}
if initial:
self._settings.update(initial)
@@ -215,87 +234,161 @@
return families
+ def _quoteSpecialChars(self, text):
+ """Replaces all special characters in text by their replacements
+ according to self._specialChars.
+ """
+ for char, name in self._specialChars:
+ text = text.replace(char, name)
+ return text
+
def queueText(self, text="", acss=None):
- """Queue text to be spoken.
- Output is produced by next call to say() or speak()."""
+ """Adds the text to the queue.
+
+ Arguments:
+ - text: text to be spoken
+ - acss: acss.ACSS instance; if None,
+ the default voice settings will be used.
+ Otherwise, the acss settings will be
+ used to augment/override the default
+ voice settings.
+
+ Output is produced by the next call to speak.
+ """
+ if not settings.enableSpeech:
+ return
+
+ # A command to send to the speech server queueing text
+ cmd = "q { %s }\n"
if acss:
- code = self.getvoice(acss)
- self._output.write("q {%s %s %s}\n" %(code[0], text,
- code[1]))
- else:
- self._output.write("q {%s}\n" %text)
+ try:
+ code = self.getvoice(acss)
+ cmd = "q {%s %%s %s}\n" % (code[0], code[1])
+ except:
+ debug.printException(debug.LEVEL_WARNING)
+
+ # Split text into chunks and each chunk queue separately
+ for chunk in (t.group() for t in self._speechChunk.finditer(text)):
+ self._output.write(cmd % self._quoteSpecialChars(chunk))
def queueTone(self, pitch=440, duration=50):
- """Queue specified tone."""
+ """Adds a tone to the queue.
+
+ Output is produced by the next call to speak.
+ """
self._output.write("t %s %s\n " % (pitch, duration))
def queueSilence( self, duration=50):
- """Queue specified silence."""
+ """Adds silence to the queue.
+
+ Output is produced by the next call to speak.
+ """
self._output.write("sh %s" % duration)
def speakCharacter(self, character, acss=None):
- """Speak single character."""
+ """Speaks a single character immediately.
+
+ Arguments:
+ - character: text to be spoken
+ - acss: acss.ACSS instance; if None,
+ the default voice settings will be used.
+ Otherwise, the acss settings will be
+ used to augment/override the default
+ voice settings.
+ """
+ if character in '{\\}':
+ character = self._quoteSpecialChars(character)
self._output.write("l {%s}\n" % character)
+ self._output.flush()
def speakUtterances(self, utteranceList, acss=None, interrupt=True):
- """Speak list of utterances."""
- if acss:
- code = self.getvoice(acss)
- for t in utteranceList:
- self._output.write("q { %s %s %s }\n" % \
- (code[0], str(t), code[1]))
- else:
- for t in utteranceList:
- self._output.write("q { %s }\n" % str(t))
+ """Speaks the given list of utterances immediately.
+
+ Arguments:
+ - utteranceList: list of strings to be spoken
+ - acss: acss.ACSS instance; if None,
+ the default voice settings will be used.
+ Otherwise, the acss settings will be
+ used to augment/override the default
+ voice settings.
+ - interrupt: if True, stop any speech currently in progress.
+ """
+ for utt in utteranceList:
+ self.queueText(str(utt), acss)
self._output.write("d\n")
+ self._output.flush()
def speak(self, text="", acss=None, interrupt=True):
- """Speaks specified text. All queued text is spoken immediately."""
+ """Speaks all queued text immediately. If text is not None,
+ it is added to the queue before speaking.
+
+ Arguments:
+ - text: optional text to add to the queue before speaking
+ - acss: acss.ACSS instance; if None,
+ the default voice settings will be used.
+ Otherwise, the acss settings will be
+ used to augment/override the default
+ voice settings.
+ - interrupt: if True, stops any speech in progress before
+ speaking the text
+ """
# If the user has speech turned off, just return.
#
if not settings.enableSpeech:
return
- if acss:
- code = self.getvoice(acss)
- self._output.write("q {%s %s %s}\nd\n" %(code[0], text, code[1]))
- else:
- self._output.write("q {%s}\nd\n" %text)
+ self.queueText(text, acss)
+ self._output.write("d\n")
+ self._output.flush()
def increaseSpeechPitch(self, step=0.5):
- """Increase speech pitch."""
+ """Increases the speech pitch."""
self._settings['average-pitch'] += step
def decreaseSpeechPitch(self, step=0.5):
- """Decrease speech pitch."""
+ """Decreases the speech pitch."""
self._settings['average-pitch'] -= step
def increaseSpeechRate(self, step=5):
- """Set speech rate."""
+ """Increases the speech rate.
+ """
self._settings['rate'] += step
self._output.write("tts_set_speech_rate %s\n" \
% self.getrate(self._settings['rate']))
+ self._output.flush()
def decreaseSpeechRate(self, step=5):
- """Set speech rate."""
+ """Decreases the speech rate.
+ """
self._settings['rate'] -= step
self._output.write("tts_set_speech_rate %s\n" \
% self.getrate(self._settings['rate']))
+ self._output.flush()
def stop(self):
- """Silence ongoing speech."""
+ """Stops ongoing speech and flushes the queue."""
self._output.write("s\n")
+ self._output.flush()
def shutdown(self):
- """Shutdown speech engine."""
- if self._engine in SpeechServer.__activeServers:
- self._output.close()
+ """Shuts down the speech engine."""
+ if self._process is None:
+ return
+
+ if self._process.poll() is None:
+ from signal import SIGKILL
+ os.kill(self._process.pid, SIGKILL)
+ self._process.wait()
+ self._output.close()
+ self._output = self._process = None
+ if self is SpeechServer.__activeServers.get(self._engine, None):
del SpeechServer.__activeServers[self._engine]
def reset(self, text=None, acss=None):
- """Reset TTS engine."""
+ """Resets the speech engine."""
self._output.write("tts_reset\n")
+ self._output.flush()
def version(self):
"""Speak TTS version info."""
@@ -306,35 +399,38 @@
if mode in ['all', 'some', 'none']:
self._settings['punctuations'] = mode
self._output.write("tts_set_punctuations %s\n" % mode)
+ self._output.flush()
def rate(self, r):
"""Set speech rate."""
self._settings['rate'] = r
self._output.write("tts_set_speech_rate %s\n" % self.getrate(r))
+ self._output.flush()
def splitcaps(self, flag):
"""Set splitcaps mode. 1 turns on, 0 turns off"""
flag = bool(flag) and 1 or 0
self._settings['splitcaps'] = flag
self._output.write("tts_split_caps %s\n" % flag)
+ self._output.flush()
def capitalize(self, flag):
"""Set capitalization mode. 1 turns on, 0 turns off"""
flag = bool(flag) and 1 or 0
self._settings['capitalize'] = flag
self._output.write("tts_capitalize %s\n" % flag)
+ self._output.flush()
def allcaps(self, flag):
"""Set allcaps mode. 1 turns on, 0 turns off"""
flag = bool(flag) and 1 or 0
self._settings['allcaps'] = flag
self._output.write("tts_allcaps_beep %s\n" % flag)
+ self._output.flush()
def __del__(self):
- "Shutdown speech engine."
- if hasattr(self, "_output") \
- and not self._output.closed:
- self.shutdown()
+ "Shuts down the speech engine."
+ self.shutdown()
def _getcodes(engine):
"""Helper function that fetches synthesizer codes for a
Modified: trunk/src/orca/outloud.py
==============================================================================
--- trunk/src/orca/outloud.py (original)
+++ trunk/src/orca/outloud.py Wed Oct 8 22:05:56 2008
@@ -32,6 +32,28 @@
__copyright__ = "Copyright (c) 2005-2008 Google Inc."
__license__ = "LGPL"
+import chnames
+
+# Handling of special characters
+#
+# Emacspeak uses Tcl syntax to communicate with its speech servers. It
+# embraces text in curly braces, so that at least {, }, and \ must be quoted
+# when sending text to speech server. But individual speech engines have
+# their own special characters in addition to those of Tcl. Outloud
+# perceives speech commands starting with backquote, and Emacspeak exploits
+# this to transmit speech settings to Outloud. Thus we must quote `
+# (backquote) too.
+
+def makeSpecialCharMap():
+ """Returns list of pairs mapping characters which are special for
+ Outloud speech server to their replacements.
+ """
+ chars = r'{\}`'
+ return [(ch, ' '+chnames.getCharacterName(ch)+' ') for ch in chars]
+
+
+# Speech parameters
+
_defined_voices = {}
# Map from ACSS dimensions to Outloud settings:
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]