Coverage Report - orca.gnomespeechfactory

ModuleCoverage %
orca.gnomespeechfactory
22%
1
# Orca
2
#
3
# Copyright 2005-2007 Sun Microsystems 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 1
"""Provides a SpeechServer factory for gnome-speech drivers."""
21
22 1
__id__        = "$Id: gnomespeechfactory.py 2126 2007-03-06 21:35:17Z richb $"
23 1
__version__   = "$Revision: 2126 $"
24 1
__date__      = "$Date: 2007-03-06 13:35:17 -0800 (Tue, 06 Mar 2007) $"
25 1
__copyright__ = "Copyright (c) 2005-2006 Sun Microsystems Inc."
26 1
__license__   = "LGPL"
27
28 1
import logging
29 1
log = logging.getLogger("speech")
30
31 1
import os
32
33 1
import gobject
34 1
import Queue
35 1
import string
36 1
import threading
37 1
import time
38
39 1
import bonobo
40
41 1
import atspi
42 1
import chnames
43 1
import debug
44 1
import orca_state
45 1
import punctuation_settings
46 1
import settings
47 1
import speech
48 1
import speechserver
49
50 1
from acss import ACSS
51
52 1
from orca_i18n import _           # for gettext support
53
54 1
atspi.ORBit.load_typelib('GNOME_Speech')
55 1
import GNOME.Speech, GNOME__POA.Speech
56
57 2
class _SayAll:
58 1
    def __init__(self, iterator, context, id, progressCallback):
59 0
        self.utteranceIterator   = iterator
60 0
        self.currentContext      = context
61 0
        self.idForCurrentContext = id
62 0
        self.progressCallback    = progressCallback
63
64 2
class _Speaker(GNOME__POA.Speech.SpeechCallback):
65
    """Implements gnome-speech's SpeechCallback class.  The gnome-speech
66
    server only allows one speech callback to be registered with a speaker
67
    and there's no way to unregister it.  So...we need to handle stuff
68
    like that on our own.  This class handles this for us and also delegates
69
    all calls to the 'real' gnome speech speaker.
70
    """
71
72 1
    def __init__(self, gnome_speaker):
73 0
        self.gnome_speaker = gnome_speaker
74 0
        if settings.enableSpeechCallbacks:
75 0
            gnome_speaker.registerSpeechCallback(self._this())
76 0
        self.__callbacks = []
77
78 1
    def registerCallback(self, callback):
79 0
        self.__callbacks.append(callback)
80
81 1
    def deregisterCallback(self, callback):
82 0
        self.__callbacks.remove(callback)
83
84 1
    def notify(self, type, id, offset):
85
        """Called by GNOME Speech when the GNOME Speech driver generates
86
        a callback.
87
88
        Arguments:
89
        - type:   one of GNOME.Speech.speech_callback_speech_started,
90
                         GNOME.Speech.speech_callback_speech_progress,
91
                         GNOME.Speech.speech_callback_speech_ended
92
        - id:     the id of the utterance (returned by say)
93
        - offset: the character offset into the utterance (for progress)
94
        """
95 0
        for callback in self.__callbacks:
96 0
            callback.notify(type, id, offset)
97
98 1
    def say(self, text):
99 0
        return self.gnome_speaker.say(text)
100
101 1
    def stop(self):
102 0
        self.gnome_speaker.stop()
103
104 1
    def getSupportedParameters(self):
105 0
        return self.gnome_speaker.getSupportedParameters()
106
107 1
    def getParameterValue(self, name):
108 0
        return self.gnome_speaker.getParameterValue(name)
109
110 1
    def setParameterValue(self, name, value):
111 0
        return self.gnome_speaker.setParameterValue(name, value)
112
113 1
    def unref(self):
114 0
        try:
115 0
            self.gnome_speaker.unref()
116 0
        except:
117 0
            pass
118
119 2
class SpeechServer(speechserver.SpeechServer):
120
    """Provides SpeechServer implementation for gnome-speech."""
121
122
    # Dictionary of known running servers.  The key is the iid and the
123
    # value is the SpeechServer instance.  We do this to enforce a
124
    # singleton instance of any given server.
125
    #
126 1
    __activeServers = {}
127
128 1
    def __activateDriver(iid):
129 2
        driver = bonobo.activation.activate_from_id(iid,
130 2
                                                    0,
131 2
                                                    False)
132 2
        driver = driver._narrow(GNOME.Speech.SynthesisDriver)
133 2
        isInitialized = driver.isInitialized()
134 2
        if not isInitialized:
135 2
            isInitialized = driver.driverInit()
136 2
        if not isInitialized:
137 1
            try:
138 1
                driver.unref()
139 0
            except:
140 0
                pass
141 1
            driver = None
142 2
        return driver
143
144 1
    __activateDriver = staticmethod(__activateDriver)
145
146 1
    def __createServer(iid):
147 2
        server = None
148
149 2
        if SpeechServer.__activeServers.has_key(iid):
150 0
            server = SpeechServer.__activeServers[iid]
151
        else:
152 2
            driver = SpeechServer.__activateDriver(iid)
153 2
            if driver:
154 1
                server = SpeechServer(driver, iid)
155 1
                SpeechServer.__activeServers[iid] = server
156
157 2
        return server
158
159 1
    __createServer = staticmethod(__createServer)
160
161 1
    def getFactoryName():
162
        """Returns a localized name describing this factory."""
163 1
        return _("GNOME Speech Services")
164
165 1
    getFactoryName = staticmethod(getFactoryName)
166
167 1
    def getSpeechServers():
168
        """Gets available speech servers as a list.  The caller
169
        is responsible for calling the shutdown() method of each
170
        speech server returned.
171
        """
172
173
        # Get a list of all the drivers on the system and find out how many
174
        # of them work.
175
        #
176 1
        knownServers = bonobo.activation.query(
177
            "repo_ids.has('IDL:GNOME/Speech/SynthesisDriver:0.3')")
178
179 3
        for server in knownServers:
180 2
            if not SpeechServer.__activeServers.has_key(server.iid):
181 2
                try:
182 2
                    SpeechServer.__createServer(server.iid)
183 0
                except:
184 0
                    debug.printException(debug.LEVEL_WARNING)
185
186 1
        return SpeechServer.__activeServers.values()
187
188 1
    getSpeechServers = staticmethod(getSpeechServers)
189
190 1
    def getSpeechServer(info=None):
191
        """Gets a given SpeechServer based upon the info.
192
        See SpeechServer.getInfo() for more info.
193
        """
194
195 0
        if info and SpeechServer.__activeServers.has_key(info[1]):
196 0
            return SpeechServer.__activeServers[info[1]]
197
198 0
        server = None
199
200 0
        gservers = bonobo.activation.query(
201
            "repo_ids.has('IDL:GNOME/Speech/SynthesisDriver:0.3')")
202
203 0
        if len(gservers) == 0:
204 0
            return None
205
206 0
        gserver = None
207
208
        # All this logic is to attempt to fall back to a working
209
        # driver if the desired one cannot be found or is not
210
        # not working.
211
        #
212 0
        if not info:
213 0
            gserver = gservers[0]
214
        else:
215 0
            for s in gservers:
216 0
                if s.iid == info[1]:
217 0
                    gserver = s
218 0
                    break
219
220 0
        if not gserver:
221 0
            return None
222
223 0
        server = None
224 0
        try:
225 0
            server = SpeechServer.__createServer(gserver.iid)
226 0
        except:
227 0
            debug.printException(debug.LEVEL_WARNING)
228
229 0
        if not server:
230 0
            for s in gservers:
231 0
                try:
232 0
                    server = SpeechServer.__createServer(s.iid)
233 0
                    if server:
234 0
                        break
235 0
                except:
236 0
                    debug.printException(debug.LEVEL_WARNING)
237
238 0
        return server
239
240 1
    getSpeechServer = staticmethod(getSpeechServer)
241
242 1
    def shutdownActiveServers():
243
        """Cleans up and shuts down this factory.
244
        """
245 0
        for key in SpeechServer.__activeServers.keys():
246 0
            server = SpeechServer.__activeServers[key]
247 0
            server.shutdown()
248
249 1
    shutdownActiveServers = staticmethod(shutdownActiveServers)
250
251 1
    def __init__(self, driver, iid):
252 1
        speechserver.SpeechServer.__init__(self)
253 1
        self.__speakers      = {}
254 1
        self.__pitchInfo     = {}
255 1
        self.__rateInfo      = {}
256 1
        self.__volumeInfo    = {}
257 1
        self.__driver        = driver
258 1
        self.__driverName    = driver.driverName
259 1
        self.__iid           = iid
260 1
        self.__sayAll        = None
261 1
        self.__isSpeaking    = False
262 1
        self.__eventQueue    = Queue.Queue(0)
263 1
        self.__gidleId       = 0
264 1
        self.__gidleLock     = threading.Lock()
265 1
        self.__lastResetTime = 0
266
267 1
    def __getRate(self, speaker):
268
        """Gets the voice-independent ACSS rate value of a voice."""
269
270 0
        if not self.__rateInfo.has_key(speaker):
271 0
            return 50.0
272
273 0
        [minRate, averageRate, maxRate] = self.__rateInfo[speaker]
274 0
        rate = speaker.getParameterValue("rate")
275 0
        if rate < averageRate:
276 0
            return 50.0 * (rate - minRate) / (averageRate - minRate)
277 0
        elif rate > averageRate:
278 0
            return 50.0 \
279
                   + (50.0 * (rate - averageRate) / (maxRate - averageRate))
280
        else:
281 0
            return 50.0
282
283 1
    def __setRate(self, speaker, acssRate):
284
        """Determines the voice-specific rate setting for the
285
        voice-independent ACSS rate value.
286
        """
287
288 0
        if not self.__rateInfo.has_key(speaker):
289 0
            return
290
291 0
        [minRate, averageRate, maxRate] = self.__rateInfo[speaker]
292 0
        if acssRate < 50.0:
293 0
            rate = minRate + acssRate * (averageRate - minRate) / 50.0
294 0
        elif acssRate > 50.0:
295 0
            rate = averageRate \
296
                   + (acssRate - 50.0) * (maxRate - averageRate) / 50.0
297
        else:
298 0
            rate = averageRate
299
300 0
        speaker.setParameterValue("rate", rate)
301
302 1
    def __getPitch(self, speaker):
303
        """Gets the voice-specific pitch setting for the
304
           voice-independent ACSS pitch value.
305
306
        Returns the voice-specific pitch setting.
307
        """
308
309 0
        if not self.__pitchInfo.has_key(speaker):
310 0
            return 5.0
311
312 0
        [minPitch, averagePitch, maxPitch] = self.__pitchInfo[speaker]
313 0
        pitch = speaker.getParameterValue("pitch")
314 0
        if pitch < averagePitch:
315 0
            return 5.0 * (pitch - minPitch) / (averagePitch - minPitch)
316 0
        elif pitch > averagePitch:
317 0
            return 5.0 \
318
                   + (5.0 * (pitch - averagePitch) / (maxPitch - averagePitch))
319
        else:
320 0
            return 5.0
321
322 1
    def __setPitch(self, speaker, acssPitch):
323
        """Determines the voice-specific pitch setting for the
324
        voice-independent ACSS pitch value.
325
        """
326
327 0
        if not self.__pitchInfo.has_key(speaker):
328 0
            return
329
330 0
        [minPitch, averagePitch, maxPitch] = self.__pitchInfo[speaker]
331 0
        if acssPitch < 5.0:
332 0
            pitch = minPitch + acssPitch * (averagePitch - minPitch) / 5.0
333 0
        elif acssPitch > 5.0:
334 0
            pitch = averagePitch \
335
                    + (acssPitch - 5.0) * (maxPitch - averagePitch) / 5.0
336
        else:
337 0
            pitch = averagePitch
338
339 0
        speaker.setParameterValue("pitch", pitch)
340
341 1
    def __setVolume(self, speaker, acssGain):
342
        """Determines the voice-specific rate setting for the
343
        voice-independent ACSS rate value.
344
        """
345
346 0
        if not self.__volumeInfo.has_key(speaker):
347 0
            return
348
349 0
        [minVolume, averageVolume, maxVolume] = self.__volumeInfo[speaker]
350 0
        volume = minVolume + acssGain * (maxVolume - minVolume) / 10.0
351
352 0
        speaker.setParameterValue("volume", volume)
353
354 1
    def __getSpeaker(self, acss=None):
355
356 0
        voices = settings.voices
357 0
        defaultACSS = voices[settings.DEFAULT_VOICE]
358
359 0
        if not acss:
360 0
            acss = defaultACSS
361
362 0
        if self.__speakers.has_key(acss.name()):
363 0
            return self.__speakers[acss.name()]
364
365
        # Search for matching languages first, as that is the most
366
        # important thing.  We also try to look for the desired language
367
        # first, but then fall back to the default language.
368
        #
369 0
        languages = []
370 0
        try:
371 0
            if acss.has_key(ACSS.FAMILY):
372 0
                family = acss[ACSS.FAMILY]
373 0
                languages = [family[speechserver.VoiceFamily.LOCALE]]
374 0
            elif defaultACSS.has_key(ACSS.FAMILY):
375 0
                family = defaultACSS[ACSS.FAMILY]
376 0
                languages = [family[speechserver.VoiceFamily.LOCALE]]
377 0
        except:
378 0
            pass
379
380
        # [[[TODO: WDW - I'm using an array of languages here because
381
        # it will give us the opportunity to provide a match for
382
        # gnome-speech driver implementations that do not designate
383
        # languages using the combination of ISO 639-1 and 3166-1
384
        # language_region strings (e.g., they use 'english' instead of
385
        # "en_US").  Thus, we could detect the default language is
386
        # "en_US" and just automatically append "english" and such to
387
        # the list to allow us to accomodate these kind of
388
        # variants.]]]
389
        #
390 0
        if len(languages) == 0:
391 0
            import locale
392 0
            language, encoding = locale.getdefaultlocale()
393 0
            languages = [language]
394
395 0
        voices = self.__driver.getAllVoices()
396 0
        foundVoices = []
397 0
        for voice in voices:
398 0
            if voice.language in languages:
399 0
                foundVoices.append(voice)
400
401
        # If we didn't find any matches, well...punt.
402
        #
403 0
        if len(foundVoices):
404 0
            voices = foundVoices
405
        else:
406 0
            voices = self.__driver.getAllVoices()
407
408
        # Now search for a matching family name.
409
        #
410 0
        familyName = None
411 0
        if acss.has_key(ACSS.FAMILY):
412 0
            family = acss[ACSS.FAMILY]
413 0
            familyName = family[speechserver.VoiceFamily.NAME]
414 0
        elif defaultACSS.has_key(ACSS.FAMILY):
415 0
            family = defaultACSS[ACSS.FAMILY]
416 0
            familyName = family[speechserver.VoiceFamily.NAME]
417
418 0
        found = False
419 0
        for voice in voices:
420 0
            if (not familyName) or (voice.name == familyName):
421 0
                found = True
422 0
                break
423
424 0
        if not found:
425 0
            if len(voices) == 0:
426 0
                return None
427
            else:
428 0
                voice = voices[0]
429
430 0
        s = self.__driver.createSpeaker(voice)
431 0
        speaker = _Speaker(s._narrow(GNOME.Speech.Speaker))
432
433
        # Turn off punctuation if the speaker allows us to do so.  We
434
        # do this because we want to handle spoken punctuation on our
435
        # own.
436
        #
437 0
        try:
438 0
            speaker.setParameterValue("punctuation mode", 0.0)
439 0
        except:
440 0
            pass
441
442 0
        speaker.registerCallback(self)
443
444 0
        parameters = speaker.getSupportedParameters()
445 0
        for parameter in parameters:
446 0
            if parameter.name == "rate":
447 0
                self.__rateInfo[speaker] = \
448
                    [parameter.min, parameter.current, parameter.max]
449 0
            elif parameter.name == "pitch":
450 0
                self.__pitchInfo[speaker] = \
451
                    [parameter.min, parameter.current, parameter.max]
452 0
            elif parameter.name == "volume":
453 0
                self.__volumeInfo[speaker] = \
454
                    [parameter.min, parameter.current, parameter.max]
455
456 0
        if acss.has_key(ACSS.RATE):
457 0
            self.__setRate(speaker, acss[ACSS.RATE])
458
459 0
        if acss.has_key(ACSS.AVERAGE_PITCH):
460 0
            self.__setPitch(speaker, acss[ACSS.AVERAGE_PITCH])
461
462 0
        if acss.has_key(ACSS.GAIN):
463 0
            self.__setVolume(speaker, acss[ACSS.GAIN])
464
465 0
        self.__speakers[acss.name()] = speaker
466
467 0
        return speaker
468
469 1
    def __idleHandler(self):
470
        """Called by the gidle thread when there is something to do.
471
        The goal is to try to do all AT-SPI interactions on the gidle
472
        thread as a means to help prevent hangs."""
473
474
        # Added in the notify method below.
475
        #
476
        # id     = the id of the utterance we sent to gnome-speech
477
        # type   = the type of progress we're getting
478
        # offset = character offset into the utterance
479
        #
480 0
        (id, type, offset) = self.__eventQueue.get()
481
482 0
        if self.__sayAll:
483 0
            if self.__sayAll.idForCurrentContext == id:
484 0
                context = self.__sayAll.currentContext
485 0
                if type == GNOME.Speech.speech_callback_speech_started:
486 0
                    context.currentOffset = context.startOffset
487 0
                    self.__sayAll.progressCallback(
488
                        self.__sayAll.currentContext,
489 0
                        speechserver.SayAllContext.PROGRESS)
490 0
                elif type == GNOME.Speech.speech_callback_speech_progress:
491 0
                    context.currentOffset = context.startOffset + offset
492 0
                    self.__sayAll.progressCallback(
493
                        self.__sayAll.currentContext,
494 0
                        speechserver.SayAllContext.PROGRESS)
495 0
                elif type == GNOME.Speech.speech_callback_speech_ended:
496 0
                    try:
497 0
                        [self.__sayAll.currentContext, acss] = \
498
                            self.__sayAll.utteranceIterator.next()
499 0
                        debug.println(debug.LEVEL_INFO,
500 0
                                      "SPEECH OUTPUT: '" \
501
                                      + self.__sayAll.currentContext.utterance\
502
                                      + "'")
503 0
                        log.info("'%s'" \
504
                                 % self.__sayAll.currentContext.utterance)
505 0
                        self.__sayAll.idForCurrentContext = self.__speak(
506
                            self.__sayAll.currentContext.utterance,
507 0
                            acss)
508 0
                    except StopIteration:
509 0
                        self.__isSpeaking = False
510 0
                        context.currentOffset = context.endOffset
511 0
                        self.__sayAll.progressCallback(
512
                            self.__sayAll.currentContext,
513 0
                            speechserver.SayAllContext.COMPLETED)
514 0
                        self.__sayAll = None
515
516 0
        rerun = True
517
518 0
        self.__gidleLock.acquire()
519 0
        if self.__eventQueue.empty():
520 0
            self.__gidleId = 0
521 0
            rerun = False # destroy and don't call again
522 0
        self.__gidleLock.release()
523
524 0
        return rerun
525
526 1
    def notify(self, type, id, offset):
527
        """Called by GNOME Speech when the GNOME Speech driver generates
528
        a callback.  This is for internal use only.
529
530
        Arguments:
531
        - type:   one of GNOME.Speech.speech_callback_speech_started,
532
                         GNOME.Speech.speech_callback_speech_progress,
533
                         GNOME.Speech.speech_callback_speech_ended
534
        - id:     the id of the utterance (returned by say)
535
        - offset: the character offset into the utterance (for progress)
536
        """
537
538 0
        if type == GNOME.Speech.speech_callback_speech_started:
539 0
            self.__isSpeaking = True
540 0
        elif type == GNOME.Speech.speech_callback_speech_progress:
541 0
            self.__isSpeaking = True
542 0
        elif (type == GNOME.Speech.speech_callback_speech_ended) \
543
            and (not self.__sayAll):
544 0
            self.__isSpeaking = False
545
546 0
        if self.__sayAll:
547 0
            self.__gidleLock.acquire()
548 0
            self.__eventQueue.put((id, type, offset))
549 0
            if not self.__gidleId:
550 0
                if settings.gilSleepTime:
551 0
                    time.sleep(settings.gilSleepTime)
552 0
                self.__gidleId = gobject.idle_add(self.__idleHandler)
553 0
            self.__gidleLock.release()
554
555 1
    def getInfo(self):
556
        """Returns [driverName, serverId]
557
        """
558 0
        return [self.__driverName, self.__iid]
559
560 1
    def getVoiceFamilies(self):
561
        """Returns a list of speechserver.VoiceFamily instances
562
        representing all the voice families known by the speech server.
563
        """
564
565 0
        families = []
566 0
        try:
567 0
            for voice in self.__driver.getAllVoices():
568 0
                props = {
569
                    speechserver.VoiceFamily.NAME   : voice.name,
570
                    speechserver.VoiceFamily.LOCALE : voice.language
571
                }
572 0
                families.append(speechserver.VoiceFamily(props))
573 0
        except:
574 0
            debug.printException(debug.LEVEL_SEVERE)
575 0
            pass
576
577 0
        return families
578
579 1
    def queueText(self, text="", acss=None):
580
        """Adds the text to the queue.
581
582
        Arguments:
583
        - text: text to be spoken
584
        - acss: acss.ACSS instance; if None,
585
                the default voice settings will be used.
586
                Otherwise, the acss settings will be
587
                used to augment/override the default
588
                voice settings.
589
590
        Output is produced by the next call to speak.
591
        """
592 0
        self.speak(text, acss)
593
594 1
    def queueTone(self, pitch=440, duration=50):
595
        """Adds a tone to the queue.
596
597
        Output is produced by the next call to speak.
598
        """
599
        pass
600
601 1
    def queueSilence(self, duration=50):
602
        """Adds silence to the queue.
603
604
        Output is produced by the next call to speak.
605
        """
606
        pass
607
608 1
    def speakCharacter(self, character, acss=None):
609
        """Speaks a single character immediately.
610
611
        Arguments:
612
        - character: text to be spoken
613
        - acss:      acss.ACSS instance; if None,
614
                     the default voice settings will be used.
615
                     Otherwise, the acss settings will be
616
                     used to augment/override the default
617
                     voice settings.
618
        """
619 0
        self.speak(character, acss)
620
621 1
    def speakUtterances(self, list, acss=None, interrupt=True):
622
        """Speaks the given list of utterances immediately.
623
624
        Arguments:
625
        - list:      list of strings to be spoken
626
        - acss:      acss.ACSS instance; if None,
627
                     the default voice settings will be used.
628
                     Otherwise, the acss settings will be
629
                     used to augment/override the default
630
                     voice settings.
631
        - interrupt: if True, stop any speech currently in progress.
632
        """
633 0
        i = 0
634 0
        for text in list:
635 0
            if len(text):
636 0
                self.speak(text, acss, interrupt and (i == 0))
637 0
            i += 1
638
639 1
    def __addVerbalizedPunctuation(self, oldText):
640
        """Depending upon the users verbalized punctuation setting,
641
        adjust punctuation symbols in the given text to their pronounced
642
        equivalents. The pronounced text will either replace the
643
        punctuation symbol or be inserted before it. In the latter case,
644
        this is to retain spoken prosity.
645
646
        If we are moving around by single characters, then always speak
647
        the punctuation. We try to detect this by looking for just a
648
        single character being spoken.
649
650
        Arguments:
651
        - oldText:      text to be parsed for punctuation.
652
653
        Returns a text string with the punctuation symbols adjusted accordingly.
654
        """
655
656
        ## Replace ellipses (both manual and UTF-8) with "dot dot dot"
657
        ##
658 0
        oldText = oldText.replace("...", _(" dot dot dot"), 1)
659 0
        oldText = oldText.replace("\342\200\246",  _(" dot dot dot"), 1)
660 0
        oldText = oldText.decode("UTF-8")
661
662
        ## Don't speak newlines unless the user is moving through text
663
        ## using a right or left arrow key.
664
        ##
665 0
        removeNewLines = True
666 0
        if orca_state.lastInputEvent and \
667
               orca_state.lastInputEvent.__dict__.has_key("event_string"):
668 0
            lastKey = orca_state.lastInputEvent.event_string
669 0
            if lastKey == "Left" or lastKey == "Right":
670 0
                removeNewLines = False
671
672 0
        if removeNewLines:
673 0
            oldText = oldText.replace("\n", "", 1)
674
675 0
        style = settings.verbalizePunctuationStyle
676 0
        newText = ''
677 0
        for i in range(0, len(oldText)):
678 0
            try:
679 0
                level, action = \
680
                    punctuation_settings.getPunctuationInfo(oldText[i])
681
682
                # Special case for punctuation in text like filenames or URL's:
683
                #
684 0
                isPrev = isNext = isSpecial = False
685 0
                if i > 0:
686 0
                    isPrev = not (oldText[i - 1] in string.whitespace)
687 0
                if i < (len(oldText) - 1):
688 0
                    isNext = not (oldText[i + 1] in string.whitespace)
689
690
                # If this is a period and there is a non-space character
691
                # on either side of it, then always speak it.
692
                #
693 0
                isSpecial = isPrev and isNext and (oldText[i] == ".")
694
695
                # If this is a dash and the users punctuation level is not
696
                # NONE and the previous character is a white space character,
697
                # and the next character is a dollar sign or a digit, then
698
                # always speak it. See bug #392939.
699
                #
700 0
                prevCharMatches = nextCharMatches = False
701 0
                if orca_state.activeScript:
702 0
                    currencySymbols = \
703
                        orca_state.activeScript.getUnicodeCurrencySymbols()
704 0
                if i > 0:
705 0
                    prevCharMatches = (oldText[i - 1] in string.whitespace)
706 0
                if i < (len(oldText) - 1):
707 0
                    nextCharMatches = (oldText[i + 1] in string.digits or \
708
                                       oldText[i + 1] in currencySymbols)
709
710 0
                if oldText[i] == "-" and \
711
                   style != settings.PUNCTUATION_STYLE_NONE and \
712
                   prevCharMatches and nextCharMatches:
713 0
                    if isPrev:
714 0
                        newText += " "
715 0
                    newText += _("minus")
716 0
                elif (len(oldText) == 1) or isSpecial or (style <= level):
717 0
                    if isPrev:
718 0
                        newText += " "
719 0
                    newText += chnames.getCharacterName(oldText[i])
720 0
                    if (action == punctuation_settings.PUNCTUATION_INSERT) \
721
                        and not isNext:
722 0
                        newText += oldText[i].encode("UTF-8")
723 0
                    if isNext:
724 0
                        newText += " "
725
                else:
726 0
                    newText += oldText[i].encode("UTF-8")
727 0
            except:
728 0
                if (len(oldText) == 1):
729 0
                    newText += chnames.getCharacterName(oldText[i])
730
                else:
731 0
                    newText += oldText[i].encode("UTF-8")
732
733 0
        return newText
734
735 1
    def __speak(self, text=None, acss=None, interrupt=True):
736
        """Speaks all queued text immediately.  If text is not None,
737
        it is added to the queue before speaking.
738
739
        Arguments:
740
        - text:      optional text to add to the queue before speaking
741
        - acss:      acss.ACSS instance; if None,
742
                     the default voice settings will be used.
743
                     Otherwise, the acss settings will be
744
                     used to augment/override the default
745
                     voice settings.
746
        - interrupt: if True, stops any speech in progress before
747
                     speaking the text
748
749
        Returns an id of the thing being spoken or -1 if nothing is to
750
        be spoken.
751
        """
752
753
        # If the user has speech turned off, just return.
754
        #
755 0
        if not settings.enableSpeech:
756 0
            return -1
757
758 0
        speaker = self.__getSpeaker(acss)
759 0
        if acss and not acss.has_key(ACSS.RATE):
760 0
            voices = settings.voices
761 0
            defaultACSS = voices[settings.DEFAULT_VOICE]
762 0
            if defaultACSS.has_key(ACSS.RATE):
763 0
                self.__setRate(speaker, defaultACSS[ACSS.RATE])
764
765 0
        if not text:
766 0
            if interrupt:
767 0
                speech.stop()
768 0
            return -1
769
770 0
        text = self.__addVerbalizedPunctuation(text)
771 0
        if orca_state.activeScript:
772 0
            text = orca_state.activeScript.adjustForPronunciation(text)
773
774 0
        try:
775
            # [[[TODO: WDW - back this stop out for now.  The problem is
776
            # that we end up clipping too much speech, especially in the
777
            # case where we want to speak the contents of a popup before
778
            # speaking the object with focus.]]]
779
            #
780
            #if interrupt:
781
            #    speaker.stop()
782 0
            self.__lastText = [text, acss]
783 0
            self.__isSpeaking = True
784 0
            return speaker.say(text)
785 0
        except:
786
            # On failure, remember what we said, reset our connection to the
787
            # speech synthesis driver, and try to say it again.
788
            #
789 0
            debug.printException(debug.LEVEL_SEVERE)
790 0
            debug.println(debug.LEVEL_SEVERE, "Restarting speech...")
791 0
            self.reset()
792 0
            return -1
793
794 1
    def speak(self, text=None, acss=None, interrupt=True):
795
        """Speaks all queued text immediately.  If text is not None,
796
        it is added to the queue before speaking.
797
798
        Arguments:
799
        - text:      optional text to add to the queue before speaking
800
        - acss:      acss.ACSS instance; if None,
801
                     the default voice settings will be used.
802
                     Otherwise, the acss settings will be
803
                     used to augment/override the default
804
                     voice settings.
805
        - interrupt: if True, stops any speech in progress before
806
                     speaking the text
807
        """
808 0
        if self.__sayAll:
809 0
            self.stop()
810
811 0
        self.__speak(text, acss, interrupt)
812
813 1
    def isSpeaking(self):
814
        """"Returns True if the system is currently speaking."""
815 0
        return self.__isSpeaking
816
817 1
    def sayAll(self, utteranceIterator, progressCallback):
818
        """Iterates through the given utteranceIterator, speaking
819
        each utterance one at a time.
820
821
        Arguments:
822
        - utteranceIterator: iterator/generator whose next() function
823
                             returns a new string to be spoken
824
        - progressCallback:  called as progress is made
825
        """
826 0
        try:
827 0
            [context, acss] = utteranceIterator.next()
828 0
            debug.println(debug.LEVEL_INFO,
829 0
                          "SPEECH OUTPUT: '" + context.utterance + "'")
830 0
            log.info("'%s'" % context.utterance)
831 0
            self.__sayAll = _SayAll(utteranceIterator,
832 0
                                    context,
833 0
                                    self.__speak(context.utterance, acss),
834 0
                                    progressCallback)
835 0
        except StopIteration:
836 0
            pass
837
838 1
    def increaseSpeechPitch(self, step=0.5):
839
        """Increases the speech pitch for the default voice.
840
841
        Arguments:
842
        - step: the pitch step increment.
843
        """
844
845
        # [[[TODO: richb - this is a hack for now.  Need to take min/max
846
        # values in account, plus also need to take into account that
847
        # different engines provide different pitch ranges.]]]
848
849 0
        voices = settings.voices
850 0
        acss = voices[settings.DEFAULT_VOICE]
851 0
        speaker = self.__getSpeaker(acss)
852
853 0
        pitchDelta = settings.speechPitchDelta
854
855 0
        try:
856 0
            pitch = min(10, self.__getPitch(speaker) + pitchDelta)
857 0
            acss[ACSS.AVERAGE_PITCH] = pitch
858 0
            self.__setPitch(speaker, pitch)
859 0
            debug.println(debug.LEVEL_CONFIGURATION,
860 0
                          "speech.increaseSpeechPitch: pitch is now " \
861
                          " %d" % pitch)
862 0
            self.speak(_("higher."))
863 0
        except:
864 0
            debug.printException(debug.LEVEL_SEVERE)
865
866 1
    def decreaseSpeechPitch(self, step=0.5):
867
        """Decreases the speech pitch for the default voice.
868
869
        Arguments:
870
        - step: the pitch step decrement.
871
        """
872
873
        # [[[TODO: WDW - this is a hack for now.  Need to take min/max
874
        # values in account, plus also need to take into account that
875
        # different engines provide different rate ranges.]]]
876
877 0
        voices = settings.voices
878 0
        acss = voices[settings.DEFAULT_VOICE]
879 0
        speaker = self.__getSpeaker(acss)
880
881 0
        pitchDelta = settings.speechPitchDelta
882
883 0
        try:
884 0
            pitch = max(1, self.__getPitch(speaker) - pitchDelta)
885 0
            acss[ACSS.AVERAGE_PITCH] = pitch
886 0
            self.__setPitch(speaker, pitch)
887 0
            debug.println(debug.LEVEL_CONFIGURATION,
888 0
                          "speech.decreaseSpeechPitch: pitch is now " \
889
                          " %d" % pitch)
890 0
            self.speak(_("lower."))
891 0
        except:
892 0
            debug.printException(debug.LEVEL_SEVERE)
893
894 1
    def increaseSpeechRate(self, step=5):
895
        """Increases the speech rate.
896
897
        [[[TODO: WDW - this is a hack for now.  Need to take min/max
898
        values in account, plus also need to take into account that
899
        different engines provide different rate ranges.]]]
900
        """
901
902 0
        voices = settings.voices
903 0
        acss = voices[settings.DEFAULT_VOICE]
904 0
        speaker = self.__getSpeaker(acss)
905
906 0
        rateDelta = settings.speechRateDelta
907
908 0
        try:
909 0
            rate = min(100, self.__getRate(speaker) + rateDelta)
910 0
            acss[ACSS.RATE] = rate
911 0
            self.__setRate(speaker, rate)
912 0
            debug.println(debug.LEVEL_CONFIGURATION,
913 0
                          "speech.increaseSpeechRate: rate is now " \
914
                          " %d" % rate)
915 0
            self.speak(_("faster."))
916 0
        except:
917 0
            debug.printException(debug.LEVEL_SEVERE)
918
919 1
    def decreaseSpeechRate(self, step=5):
920
        """Decreases the rate of speech for the given ACSS.  If
921
        acssName is None, the rate decrease will be applied to all
922
        known ACSSs.
923
924
        [[[TODO: WDW - this is a hack for now.  Need to take min/max
925
        values in account, plus also need to take into account that
926
        different engines provide different rate ranges.]]]
927
928
        Arguments:
929
        -acssName: the ACSS whose speech rate should be decreased
930
        """
931
932 0
        voices = settings.voices
933 0
        acss = voices[settings.DEFAULT_VOICE]
934 0
        speaker = self.__getSpeaker(acss)
935
936 0
        rateDelta = settings.speechRateDelta
937
938 0
        try:
939 0
            rate = max(1, self.__getRate(speaker) - rateDelta)
940 0
            acss[ACSS.RATE] = rate
941 0
            self.__setRate(speaker, rate)
942 0
            debug.println(debug.LEVEL_CONFIGURATION,
943 0
                          "speech.decreaseSpeechRate: rate is now " \
944
                          " %d" % rate)
945 0
            self.speak(_("slower."))
946 0
        except:
947 0
            debug.printException(debug.LEVEL_SEVERE)
948
949 1
    def stop(self):
950
        """Stops ongoing speech and flushes the queue."""
951 0
        if self.__sayAll:
952 0
            self.__sayAll.progressCallback(
953
                self.__sayAll.currentContext,
954 0
                speechserver.SayAllContext.INTERRUPTED)
955 0
            self.__sayAll = None
956 0
        for name in self.__speakers.keys():
957 0
            try:
958 0
                self.__speakers[name].stop()
959 0
            except:
960 0
                pass
961 0
        self.__isSpeaking = False
962
963 1
    def shutdown(self):
964
        """Shuts down the speech engine."""
965 0
        if SpeechServer.__activeServers.has_key(self.__iid):
966 0
            for speaker in self.__speakers.values():
967 0
                speaker.stop()
968 0
                speaker.unref()
969 0
            self.__speakers = {}
970 0
            try:
971 0
                self.__driver.unref()
972 0
            except:
973 0
                pass
974 0
            self.__driver = None
975 0
            del SpeechServer.__activeServers[self.__iid]
976
977 1
    def reset(self, text=None, acss=None):
978
        """Resets the speech engine."""
979
980
        # We might get into a vicious loop of resetting speech, so
981
        # we will abort if we see this happening.
982
        #
983 0
        if (time.time() - self.__lastResetTime) < 20:
984 0
            debug.println(debug.LEVEL_SEVERE,
985 0
                          "Something looks wrong with speech.  Aborting.")
986 0
            debug.printStack(debug.LEVEL_ALL)
987 0
            os._exit(50)
988
        else:
989 0
            self.__lastResetTime = time.time()
990
991 0
        speakers = self.__speakers
992 0
        self.shutdown()
993
994 0
        servers = bonobo.activation.query(
995
            "repo_ids.has('IDL:GNOME/Speech/SynthesisDriver:0.3')")
996
997 0
        for server in servers:
998 0
            if server.iid == self.__iid:
999 0
                try:
1000 0
                    self.__driver = self.__activateDriver(self.__iid)
1001 0
                    self.__speakers = {}
1002 0
                    for name in speakers.keys():
1003 0
                        self.__getSpeaker(speakers[name])
1004 0
                    if text:
1005 0
                        self.speak(text, acss)
1006 0
                    break
1007 0
                except:
1008 0
                    debug.printException(debug.LEVEL_SEVERE)
1009 0
                    self.__driver = None
1010 0
                    pass