From ddec556ecbddec904c5b7fdc9062f429b6c4eedf Mon Sep 17 00:00:00 2001 From: Joshua Minor Date: Fri, 07 Mar 2008 08:15:06 +0000 Subject: Speak now tries to pick a default language based on $LANG Added a first attempt at i18n support. Removed experimental speechd support. --- (limited to 'Speak.activity') diff --git a/Speak.activity/activity.py b/Speak.activity/activity.py index d6ad229..fc1275f 100755 --- a/Speak.activity/activity.py +++ b/Speak.activity/activity.py @@ -33,12 +33,13 @@ import logging import gtk import gobject import pango +from gettext import gettext as _ -try: - sys.path.append('/usr/lib/python2.4/site-packages') # for speechd - import speechd.client -except: - print "Speech-dispatcher not found." +# try: +# sys.path.append('/usr/lib/python2.4/site-packages') # for speechd +# import speechd.client +# except: +# print "Speech-dispatcher not found." from sugar.graphics.toolbutton import ToolButton from sugar.graphics.toolcombobox import ToolComboBox @@ -62,24 +63,30 @@ class SpeakActivity(activity.Activity): activity.Activity.__init__(self, handle) bounds = self.get_allocation() - try: - self.synth = speechd.client.SSIPClient("Speak.activity") - except: - self.synth = None - print "Falling back to espeak command line tool." + self.synth = None + # try: + # self.synth = speechd.client.SSIPClient("Speak.activity") + # try: + # # Try some speechd v0.6.6 features + # print "Output modules:", self.synth.list_output_modules() + # print "Voices:", self.synth.list_synthesis_voices() + # except: + # pass + # except: + # self.synth = None + # print "Falling back to espeak command line tool." # pick a voice that espeak supports self.voices = voice.allVoices() + #print self.voices #self.voice = random.choice(self.voices.values()) - self.voice = self.voices["Default"] + self.voice = voice.defaultVoice() # make an audio device for playing back and rendering audio self.active = False self.connect( "notify::active", self._activeCb ) self.audio = audio.AudioGrab(datastore, self._jobject) - #self.proc = None - # make a box to type into self.entrycombo = gtk.combo_box_entry_new_text() self.entrycombo.connect("changed", self._combo_changed_cb) @@ -164,7 +171,7 @@ class SpeakActivity(activity.Activity): self.active = True presenceService = presenceservice.get_instance() xoOwner = presenceService.get_owner() - self.say("Hello %s. Type something." % xoOwner.props.nick) + self.say(_("Hello %s. Type something.") % xoOwner.props.nick) def write_file(self, file_path): f = open(file_path, "w") @@ -324,10 +331,10 @@ class SpeakActivity(activity.Activity): self.say(self.voice.friendlyname) def pitch_adjusted_cb(self, get, data=None): - self.say("pitch adjusted") + self.say(_("pitch adjusted")) def rate_adjusted_cb(self, get, data=None): - self.say("rate adjusted") + self.say(_("rate adjusted")) def make_face_bar(self): facebar = gtk.Toolbar() @@ -336,9 +343,9 @@ class SpeakActivity(activity.Activity): self.mouth_shape_combo = ComboBox() self.mouth_shape_combo.connect('changed', self.mouth_changed_cb) - self.mouth_shape_combo.append_item(mouth.Mouth, "Simple") - self.mouth_shape_combo.append_item(waveform_mouth.WaveformMouth, "Waveform") - self.mouth_shape_combo.append_item(fft_mouth.FFTMouth, "Frequency") + self.mouth_shape_combo.append_item(mouth.Mouth, _("Simple")) + self.mouth_shape_combo.append_item(waveform_mouth.WaveformMouth, _("Waveform")) + self.mouth_shape_combo.append_item(fft_mouth.FFTMouth, _("Frequency")) self.mouth_shape_combo.set_active(0) combotool = ToolComboBox(self.mouth_shape_combo) facebar.insert(combotool, -1) @@ -346,8 +353,8 @@ class SpeakActivity(activity.Activity): self.eye_shape_combo = ComboBox() self.eye_shape_combo.connect('changed', self.eyes_changed_cb) - self.eye_shape_combo.append_item(eye.Eye, "Round") - self.eye_shape_combo.append_item(glasses.Glasses, "Glasses") + self.eye_shape_combo.append_item(eye.Eye, _("Round")) + self.eye_shape_combo.append_item(glasses.Glasses, _("Glasses")) combotool = ToolComboBox(self.eye_shape_combo) facebar.insert(combotool, -1) combotool.show() @@ -378,7 +385,7 @@ class SpeakActivity(activity.Activity): # enable mouse move events so we can track the eyes while the mouse is over the mouth self.mouth.add_events(gtk.gdk.POINTER_MOTION_MASK) # this SegFaults: self.say(combo.get_active_text()) - self.say("mouth changed") + self.say(_("mouth changed")) def eyes_changed_cb(self, ignored, ignored2=None): if self.numeyesadj is None: @@ -399,7 +406,7 @@ class SpeakActivity(activity.Activity): eye.show() # this SegFaults: self.say(self.eye_shape_combo.get_active_text()) - self.say("eyes changed") + self.say(_("eyes changed")) def _combo_changed_cb(self, combo): # when a new item is chosen, make sure the text is selected @@ -448,8 +455,8 @@ class SpeakActivity(activity.Activity): # select the whole text entry.select_region(0,-1) - def _synth_cb(self, callback_type): - print "synth callback type:", callback_type + def _synth_cb(self, callback_type, index_mark=None): + print "synth callback:", callback_type, index_mark def say(self, something): if self.audio is None or not self.active: diff --git a/Speak.activity/voice.py b/Speak.activity/voice.py index 674ee1b..ac364e0 100644 --- a/Speak.activity/voice.py +++ b/Speak.activity/voice.py @@ -22,7 +22,49 @@ import subprocess -import re +import re, os +from gettext import gettext as _ + +# Lets trick gettext into generating entries for the voice names we expect espeak to have +# If espeak actually has new or different names then they won't get translated, but they +# should still show up in the interface. +expectedVoiceNames = [ + _("Brazil"), + _("Swedish"), + _("Icelandic"), + _("Romanian"), + _("Swahili"), + _("Hindi"), + _("Dutch"), + _("Latin"), + _("Hungarian"), + _("Macedonian"), + _("Welsh"), + _("French"), + _("Norwegian"), + _("Russian"), + _("Afrikaans"), + _("Finnish"), + _("Default"), + _("Cantonese"), + _("Scottish"), + _("Greek"), + _("Vietnam"), + _("English"), + _("Lancashire"), + _("Italian"), + _("Portugal"), + _("German"), + _("Whisper"), + _("Croatian"), + _("Czech"), + _("Slovak"), + _("Spanish"), + _("Polish"), + _("Esperanto") +] + +_allVoices = {} class Voice: def __init__(self, language, gender, name): @@ -36,20 +78,46 @@ class Voice: friendlyname = friendlyname.replace('en-','') friendlyname = friendlyname.replace('english-wisper','whisper') friendlyname = friendlyname.capitalize() - self.friendlyname = friendlyname - + self.friendlyname = _(friendlyname) def allVoices(): - voices = {} - result = subprocess.Popen(["espeak", "--voices"], stdout=subprocess.PIPE).communicate()[0] - for line in result.split('\n'): - m = re.match(r'\s*\d+\s+([\w-]+)\s+([MF])\s+([\w_-]+)\s+(.+)', line) - if m: - language, gender, name, stuff = m.groups() - if stuff.startswith('mb/') or name in ('en-rhotic','english_rp','english_wmids'): - # these voices don't produce sound - continue - voice = Voice(language, gender, name) - voices[voice.friendlyname] = voice - return voices + if len(_allVoices) == 0: + result = subprocess.Popen(["espeak", "--voices"], stdout=subprocess.PIPE).communicate()[0] + for line in result.split('\n'): + m = re.match(r'\s*\d+\s+([\w-]+)\s+([MF])\s+([\w_-]+)\s+(.+)', line) + if m: + language, gender, name, stuff = m.groups() + if stuff.startswith('mb/') or name in ('en-rhotic','english_rp','english_wmids'): + # these voices don't produce sound + continue + voice = Voice(language, gender, name) + _allVoices[voice.friendlyname] = voice + return _allVoices + +def defaultVoice(): + """Try to figure out the default voice, from the current locale ($LANG). + Fall back to espeak's voice called Default.""" + + def fit(a,b): + "Compare two language ids to see if they are similar." + as = re.split(r'[^a-z]+', a.lower()) + bs = re.split(r'[^a-z]+', b.lower()) + for count in range(0, min(len(as),len(bs))): + if as[count] != bs[count]: + count -= 1 + break + return count + try: + lang = os.environ["LANG"] + except: + lang = "" + + best = _allVoices[_("Default")] + for voice in _allVoices.values(): + voiceMetric = fit(voice.language, lang) + bestMetric = fit(best.language, lang) + if voiceMetric > bestMetric: + best = voice + print "Best voice for LANG %s seems to be %s %s" % (lang, best.language, best.friendlyname) + return best -- cgit v0.9.1