1 |
|
# Orca |
2 |
|
# |
3 |
|
# Copyright 2005-2006 Google Inc. |
4 |
|
# |
5 |
|
# This library is free software; you can redistribute it and/or |
6 |
|
# modify it under the terms of the GNU Library General Public |
7 |
|
# License as published by the Free Software Foundation; either |
8 |
|
# version 2 of the License, or (at your option) any later version. |
9 |
|
# |
10 |
|
# This library is distributed in the hope that it will be useful, |
11 |
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 |
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 |
|
# Library General Public License for more details. |
14 |
|
# |
15 |
|
# You should have received a copy of the GNU Library General Public |
16 |
|
# License along with this library; if not, write to the |
17 |
|
# Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
18 |
|
# Boston, MA 02111-1307, USA. |
19 |
|
# |
20 |
|
"""Python wrapper for Emacspeak speech servers. |
21 |
|
|
22 |
|
The emacspeak TTS server provides a simple but powerful and |
23 |
|
well-tested speech-server abstraction. That server is implemented |
24 |
|
as an external program (typically in TCL). This wrapper class |
25 |
|
provides Python access to available Emacspeak speech servers. |
26 |
|
|
27 |
|
Initially, this class will provide Python access to the TTS |
28 |
|
server commands. Over time, this module may also implement |
29 |
|
functionality present in Emacspeak's Lisp layer ---specifically, |
30 |
|
higher level TTS functionality provided by the following |
31 |
|
emacspeak modules: |
32 |
|
|
33 |
|
0) dtk-speak.el |
34 |
|
|
35 |
|
1) emacspeak-pronounce.el |
36 |
|
|
37 |
|
2) accs-structure.el |
38 |
|
|
39 |
1 |
""" |
40 |
|
|
41 |
1 |
__id__ = "$Id: espeechfactory.py 1680 2006-11-09 18:43:48Z wwalker $" |
42 |
1 |
__author__ = "T. V. Raman" |
43 |
1 |
__version__ = "$Revision: 1680 $" |
44 |
1 |
__date__ = "$Date: 2006-11-09 10:43:48 -0800 (Thu, 09 Nov 2006) $" |
45 |
1 |
__copyright__ = "Copyright (c) 2005 Google Inc." |
46 |
1 |
__license__ = "LGPL" |
47 |
1 |
__all__=['Speaker'] |
48 |
|
|
49 |
1 |
import os |
50 |
|
|
51 |
1 |
import debug |
52 |
1 |
import settings |
53 |
1 |
import speechserver |
54 |
|
|
55 |
1 |
from orca_i18n import _ # for gettext support |
56 |
|
|
57 |
2 |
class SpeechServer(speechserver.SpeechServer): |
58 |
|
|
59 |
|
"""Provides speech server abstraction. |
60 |
|
|
61 |
|
Class Variables: |
62 |
|
|
63 |
|
location -- specifies directory where Emacspeak |
64 |
|
speech servers are installed. |
65 |
|
|
66 |
|
config -- dictionary of default settings. |
67 |
|
|
68 |
|
Speaker objects can be initialized with the following parameters: |
69 |
|
|
70 |
|
engine -- TTS server to instantiate. Default: outloud |
71 |
|
host -- Host that runs server. Default: localhost |
72 |
|
settings -- Dictionary of default settings. |
73 |
|
|
74 |
|
""" |
75 |
|
|
76 |
1 |
location="/usr/share/emacs/site-lisp/emacspeak/servers" |
77 |
|
|
78 |
1 |
config = {'splitcaps' : 1, |
79 |
|
'rate' : 70, |
80 |
|
'capitalize' : 0, |
81 |
|
'allcaps' : 0, |
82 |
|
'punctuations' : 'all' |
83 |
|
} |
84 |
|
|
85 |
|
# Dictionary of known running servers. The key is the name and |
86 |
|
# the value is the SpeechServer instance. We do this to enforce a |
87 |
|
# singleton instance of any given server. |
88 |
|
# |
89 |
1 |
__activeServers = {} |
90 |
1 |
__getSpeechServersCalled = False |
91 |
|
|
92 |
1 |
def getFactoryName(): |
93 |
|
"""Returns a localized name describing this factory.""" |
94 |
0 |
return _("Emacspeak Speech Services") |
95 |
|
|
96 |
1 |
getFactoryName = staticmethod(getFactoryName) |
97 |
|
|
98 |
1 |
def getSpeechServers(): |
99 |
|
"""Enumerate available speech servers. |
100 |
|
|
101 |
|
Returns a list of [name, id] values identifying the available |
102 |
|
speech servers. The name is a human consumable string and the |
103 |
|
id is an object that can be used to create a speech server |
104 |
|
via the getSpeechServer method. |
105 |
|
""" |
106 |
|
|
107 |
1 |
if SpeechServer.__getSpeechServersCalled: |
108 |
0 |
return SpeechServer.__activeServers.values() |
109 |
|
else: |
110 |
1 |
SpeechServer.__getSpeechServersCalled = True |
111 |
|
|
112 |
1 |
f = open(os.path.join(SpeechServer.location, '.servers')) |
113 |
0 |
for line in f: |
114 |
0 |
if line[0] == '#' or line.strip() == '': continue |
115 |
0 |
name = line.strip() |
116 |
0 |
if not SpeechServer.__activeServers.has_key(name): |
117 |
0 |
try: |
118 |
0 |
SpeechServer.__activeServers[name] = SpeechServer(name) |
119 |
0 |
except: |
120 |
0 |
pass |
121 |
0 |
f.close() |
122 |
|
|
123 |
0 |
return SpeechServer.__activeServers.values() |
124 |
|
|
125 |
1 |
getSpeechServers = staticmethod(getSpeechServers) |
126 |
|
|
127 |
1 |
def getSpeechServer(info=['outloud','outloud']): |
128 |
|
"""Gets a given SpeechServer based upon the info. |
129 |
|
See SpeechServer.getInfo() for more info. |
130 |
|
""" |
131 |
0 |
if SpeechServer.__activeServers.has_key(info[0]): |
132 |
0 |
return SpeechServer.__activeServers[info[0]] |
133 |
|
else: |
134 |
0 |
try: |
135 |
0 |
return SpeechServer(info[0]) |
136 |
0 |
except: |
137 |
0 |
debug.printException(debug.LEVEL_SEVERE) |
138 |
0 |
return None |
139 |
|
|
140 |
1 |
getSpeechServer = staticmethod(getSpeechServer) |
141 |
|
|
142 |
1 |
def shutdownActiveServers(): |
143 |
|
"""Cleans up and shuts down this factory. |
144 |
|
""" |
145 |
0 |
for key in SpeechServer.__activeServers.keys(): |
146 |
0 |
server = SpeechServer.__activeServers[key] |
147 |
0 |
server.shutdown() |
148 |
|
|
149 |
1 |
shutdownActiveServers = staticmethod(shutdownActiveServers) |
150 |
|
|
151 |
1 |
def __init__ (self, |
152 |
|
engine='outloud', |
153 |
|
host='localhost', |
154 |
|
initial=config): |
155 |
|
"""Launches speech engine.""" |
156 |
|
|
157 |
0 |
speechserver.SpeechServer.__init__(self) |
158 |
|
|
159 |
0 |
self._engine = engine |
160 |
0 |
e = __import__(_getcodes(engine), |
161 |
0 |
globals(), |
162 |
0 |
locals(), |
163 |
0 |
['']) |
164 |
0 |
self.getvoice = e.getvoice |
165 |
0 |
self.getrate = e.getrate |
166 |
0 |
self.getvoicelist = e.getvoicelist |
167 |
0 |
if host == 'localhost': |
168 |
0 |
self._server = os.path.join(SpeechServer.location, self._engine) |
169 |
|
else: |
170 |
0 |
self._server = os.path.join(SpeechServer.location, |
171 |
0 |
"ssh-%s" % self._engine) |
172 |
0 |
cmd = '{ ' + self._server + '; } 2>&1' |
173 |
|
#print "Command = ", cmd |
174 |
|
#self._output = os.popen(cmd, "w", 1) |
175 |
0 |
[self._output, stdout, stderr] = os.popen3(cmd, "w", 1) |
176 |
0 |
self._settings ={} |
177 |
0 |
if initial: |
178 |
0 |
self._settings.update(initial) |
179 |
0 |
self.configure(self._settings) |
180 |
|
|
181 |
1 |
def configure(self, settings): |
182 |
|
"""Configure engine with settings.""" |
183 |
0 |
for k in settings.keys(): |
184 |
0 |
if hasattr(self, k) and callable(getattr(self,k)): |
185 |
0 |
getattr(self,k)(settings[k]) |
186 |
|
|
187 |
1 |
def settings(self): return self._settings |
188 |
|
|
189 |
1 |
def getInfo(self): |
190 |
|
"""Returns [driverName, serverId] |
191 |
|
""" |
192 |
0 |
return [self._engine, self._engine] |
193 |
|
|
194 |
1 |
def getVoiceFamilies(self): |
195 |
|
"""Returns a list of speechserver.VoiceFamily instances |
196 |
|
representing all the voice families known by the speech server. |
197 |
|
""" |
198 |
|
|
199 |
0 |
families = [] |
200 |
0 |
try: |
201 |
0 |
for voice in self.getvoicelist(): |
202 |
0 |
props = { |
203 |
|
speechserver.VoiceFamily.NAME : voice |
204 |
|
} |
205 |
0 |
families.append(speechserver.VoiceFamily(props)) |
206 |
0 |
except: |
207 |
0 |
debug.printException(debug.LEVEL_SEVERE) |
208 |
0 |
pass |
209 |
|
|
210 |
0 |
return families |
211 |
|
|
212 |
1 |
def queueText(self, text="", acss=None): |
213 |
|
"""Queue text to be spoken. |
214 |
|
Output is produced by next call to say() or speak().""" |
215 |
0 |
if acss: |
216 |
0 |
code =self.getvoice(acss) |
217 |
0 |
self._output.write("q {%s %s %s}\n" %(code[0], text, |
218 |
|
code[1])) |
219 |
|
else: |
220 |
0 |
self._output.write("q {%s}\n" %text) |
221 |
|
|
222 |
1 |
def queueTone(self, pitch=440, duration=50): |
223 |
|
"""Queue specified tone.""" |
224 |
0 |
self._output.write("t %s %s\n " % (pitch, duration)) |
225 |
|
|
226 |
1 |
def queueSilence( self, duration=50): |
227 |
|
"""Queue specified silence.""" |
228 |
0 |
self._output.write("sh %s" % duration) |
229 |
|
|
230 |
1 |
def speakCharacter(self, character, acss=None): |
231 |
|
"""Speak single character.""" |
232 |
0 |
self._output.write("l {%s}\n" % character) |
233 |
|
|
234 |
1 |
def speakUtterances(self, list, acss=None, interrupt=True): |
235 |
|
"""Speak list of utterances.""" |
236 |
0 |
if acss: |
237 |
0 |
code =self.getvoice(acss) |
238 |
0 |
for t in list: |
239 |
0 |
self._output.write("q { %s %s %s }\n" %(code[0], str(t), code[1])) |
240 |
|
else: |
241 |
0 |
for t in list: |
242 |
0 |
self._output.write("q { %s }\n" % str(t)) |
243 |
0 |
self._output.write("d\n") |
244 |
|
|
245 |
1 |
def speak(self, text="", acss=None, interrupt=True): |
246 |
|
"""Speaks specified text. All queued text is spoken immediately.""" |
247 |
|
|
248 |
|
# If the user has speech turned off, just return. |
249 |
|
# |
250 |
0 |
if not settings.enableSpeech: |
251 |
0 |
return |
252 |
|
|
253 |
0 |
if acss: |
254 |
0 |
code =self.getvoice(acss) |
255 |
0 |
self._output.write("q {%s %s %s}\nd\n" %(code[0], text, code[1])) |
256 |
|
else: |
257 |
0 |
self._output.write("q {%s}\nd\n" %text) |
258 |
|
|
259 |
1 |
def increaseSpeechPitch(self, step=0.5): |
260 |
|
"""Increase speech pitch.""" |
261 |
0 |
self._settings['average-pitch'] += step |
262 |
|
|
263 |
1 |
def decreaseSpeechPitch(self, step=0.5): |
264 |
|
"""Decrease speech pitch.""" |
265 |
0 |
self._settings['average-pitch'] -= step |
266 |
|
|
267 |
1 |
def increaseSpeechRate(self, step=5): |
268 |
|
"""Set speech rate.""" |
269 |
0 |
self._settings['rate'] += step |
270 |
0 |
self._output.write("tts_set_speech_rate %s\n" \ |
271 |
|
% self.getrate(self._settings['rate'])) |
272 |
|
|
273 |
1 |
def decreaseSpeechRate(self, step=5): |
274 |
|
"""Set speech rate.""" |
275 |
0 |
self._settings['rate'] -= step |
276 |
0 |
self._output.write("tts_set_speech_rate %s\n" \ |
277 |
|
% self.getrate(self._settings['rate'])) |
278 |
|
|
279 |
1 |
def stop(self): |
280 |
|
"""Silence ongoing speech.""" |
281 |
0 |
self._output.write("s\n") |
282 |
|
|
283 |
1 |
def shutdown(self): |
284 |
|
"""Shutdown speech engine.""" |
285 |
0 |
if SpeechServer.__activeServers.has_key(self._engine): |
286 |
0 |
self._output.close() |
287 |
0 |
del SpeechServer.__activeServers[self._engine] |
288 |
|
|
289 |
1 |
def reset(self, text=None, acss=None): |
290 |
|
"""Reset TTS engine.""" |
291 |
0 |
self._output.write("tts_reset\n") |
292 |
|
|
293 |
1 |
def version(self): |
294 |
|
"""Speak TTS version info.""" |
295 |
0 |
self._output.write("version\n") |
296 |
|
|
297 |
1 |
def punctuations(self, mode): |
298 |
|
"""Set punctuation mode.""" |
299 |
0 |
if mode in ['all', 'some', 'none']: |
300 |
0 |
self._settings['punctuations'] = mode |
301 |
0 |
self._output.write("tts_set_punctuations %s\n" % mode) |
302 |
|
|
303 |
1 |
def rate(self, r): |
304 |
|
"""Set speech rate.""" |
305 |
0 |
self._settings['rate'] = r |
306 |
0 |
self._output.write("tts_set_speech_rate %s\n" % self.getrate(r)) |
307 |
|
|
308 |
1 |
def splitcaps(self, flag): |
309 |
|
"""Set splitcaps mode. 1 turns on, 0 turns off""" |
310 |
0 |
flag = bool(flag) and 1 or 0 |
311 |
0 |
self._settings['splitcaps'] = flag |
312 |
0 |
self._output.write("tts_split_caps %s\n" % flag) |
313 |
|
|
314 |
1 |
def capitalize(self, flag): |
315 |
|
"""Set capitalization mode. 1 turns on, 0 turns off""" |
316 |
0 |
flag = bool(flag) and 1 or 0 |
317 |
0 |
self._settings['capitalize'] = flag |
318 |
0 |
self._output.write("tts_capitalize %s\n" % flag) |
319 |
|
|
320 |
1 |
def allcaps(self, flag): |
321 |
|
"""Set allcaps mode. 1 turns on, 0 turns off""" |
322 |
0 |
flag = bool(flag) and 1 or 0 |
323 |
0 |
self._settings['allcaps'] = flag |
324 |
0 |
self._output.write("tts_allcaps_beep %s\n" % flag) |
325 |
|
|
326 |
1 |
def __del__(self): |
327 |
|
"Shutdown speech engine." |
328 |
0 |
if hasattr(self, "_output") \ |
329 |
|
and not self._output.closed: |
330 |
0 |
self.shutdown() |
331 |
|
|
332 |
1 |
def _getcodes(engine): |
333 |
|
"""Helper function that fetches synthesizer codes for a |
334 |
|
specified engine.""" |
335 |
0 |
if engine not in _codeTable: |
336 |
0 |
raise Exception("No code table for %s" % engine) |
337 |
0 |
return _codeTable[engine] |
338 |
|
|
339 |
1 |
_codeTable = { |
340 |
|
'dtk-exp' : 'dectalk', |
341 |
|
'dtk-mv' : 'dectalk', |
342 |
|
'dtk-soft' : 'dectalk', |
343 |
|
'outloud' : 'outloud', |
344 |
|
} |
345 |
|
|
346 |
1 |
def _test(): |
347 |
|
"""Self test.""" |
348 |
0 |
import time |
349 |
0 |
import acss |
350 |
0 |
s=SpeechServer() |
351 |
0 |
a=acss.ACSS() |
352 |
0 |
s.punctuations('some') |
353 |
0 |
s.queueText("This is an initial test."); |
354 |
0 |
s.queueText("Next, we'll test audio formatted output.") |
355 |
0 |
for d in ['average-pitch', 'pitch-range', |
356 |
|
'richness', 'stress']: |
357 |
0 |
for i in range(0,10,2): |
358 |
0 |
a[d] = i |
359 |
0 |
s.queueText("Set %s to %i. " % (d, i), a) |
360 |
0 |
del a[d] |
361 |
0 |
s.queueText("Reset %s." % d, a) |
362 |
0 |
s.speak() |
363 |
0 |
print "sleeping while waiting for speech to complete." |
364 |
0 |
time.sleep(40) |
365 |
0 |
s.shutdown() |
366 |
|
|
367 |
|
|
368 |
1 |
if __name__=="__main__": _test() |