From 6e82c5964e31f3cc937911069709af9830071338 Mon Sep 17 00:00:00 2001 From: Aleksey Lim Date: Fri, 25 Dec 2009 15:34:56 +0000 Subject: Use only one directory level for sources --- (limited to 'face.py') diff --git a/face.py b/face.py new file mode 100644 index 0000000..a3b5d7b --- /dev/null +++ b/face.py @@ -0,0 +1,191 @@ +# Speak.activity +# A simple front end to the espeak text-to-speech engine on the XO laptop +# http://wiki.laptop.org/go/Speak +# +# Copyright (C) 2008 Joshua Minor +# This file is part of Speak.activity +# +# Parts of Speak.activity are based on code from Measure.activity +# Copyright (C) 2007 Arjun Sarwal - arjun@laptop.org +# +# Speak.activity is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Speak.activity 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Speak.activity. If not, see . + + +import logging +import gtk +import cjson +from gettext import gettext as _ + +import sugar.graphics.style as style + +import espeak +import eye +import glasses +import mouth +import voice +import fft_mouth +import waveform_mouth + +logger = logging.getLogger('speak') + +FACE_PAD = 2 + +class Status: + def __init__(self): + self.voice = voice.defaultVoice() + self.pitch = espeak.PITCH_DEFAULT + self.rate = espeak.RATE_DEFAULT + self.eyes = [eye.Eye] * 2 + self.mouth = mouth.Mouth + + def serialize(self): + eyes = { eye.Eye : 1, + glasses.Glasses : 2 } + mouths = { mouth.Mouth : 1, + fft_mouth.FFTMouth : 2, + waveform_mouth.WaveformMouth : 3 } + + return cjson.encode({ + 'voice' : { 'language' : self.voice.language, + 'name' : self.voice.name }, + 'pitch' : self.pitch, + 'rate' : self.rate, + 'eyes' : [eyes[i] for i in self.eyes], + 'mouth' : mouths[self.mouth] }) + + def deserialize(self, buf): + eyes = { 1: eye.Eye, + 2: glasses.Glasses } + mouths = { 1: mouth.Mouth, + 2: fft_mouth.FFTMouth, + 3: waveform_mouth.WaveformMouth } + + data = cjson.decode(buf) + self.voice = voice.Voice(data['voice']['language'], + data['voice']['name']) + self.pitch = data['pitch'] + self.rate = data['rate'] + self.eyes = [eyes[i] for i in data['eyes']] + self.mouth = mouths[data['mouth']] + + return self + + def clone(self): + new = Status() + new.voice = self.voice + new.pitch = self.pitch + new.rate = self.rate + new.eyes = self.eyes + new.mouth = self.mouth + return new + +class View(gtk.EventBox): + def __init__(self, fill_color=style.COLOR_BUTTON_GREY): + gtk.EventBox.__init__(self) + + self.status = Status() + self.fill_color = fill_color + + self.connect('size-allocate', self._size_allocate_cb) + + self._audio = espeak.AudioGrab() + + # make an empty box for some eyes + self._eyes = None + self._eyebox = gtk.HBox() + self._eyebox.show() + + # make an empty box to put the mouth in + self._mouth = None + self._mouthbox = gtk.HBox() + self._mouthbox.show() + + # layout the screen + box = gtk.VBox(homogeneous=False) + box.pack_start(self._eyebox) + box.pack_start(self._mouthbox, False) + box.set_border_width(FACE_PAD) + self.modify_bg(gtk.STATE_NORMAL, self.fill_color.get_gdk_color()) + self.add(box) + + self._peding = None + self.connect('map', self.__map_cb) + + self.update() + + def __map_cb(self, widget): + if self._peding: + self.update(self._peding) + self._peding = None + + def look_ahead(self): + if self._eyes: + map(lambda e: e.look_ahead(), self._eyes) + + def look_at(self, pos=None): + if self._eyes: + if pos is None: + display = gtk.gdk.display_get_default() + screen_, x, y, modifiers_ = display.get_pointer() + else: + x, y = pos + map(lambda e, x=x, y=y: e.look_at(x, y), self._eyes) + + def update(self, status = None): + if not status: + status = self.status + else: + if not self.flags() & gtk.MAPPED: + self._peding = status + return + self.status = status + + if self._eyes: + for eye in self._eyes: + self._eyebox.remove(eye) + if self._mouth: + self._mouthbox.remove(self._mouth) + + self._eyes = [] + + for i in status.eyes: + eye = i(self.fill_color) + self._eyes.append(eye) + self._eyebox.pack_start(eye, padding=FACE_PAD) + eye.show() + + self._mouth = status.mouth(self._audio, self.fill_color) + self._mouth.show() + self._mouthbox.add(self._mouth) + + # 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) + + def set_voice(self, voice): + self.status.voice = voice + self.say_notification(voice.friendlyname) + + def say(self, something): + self._audio.speak(self._peding or self.status, something) + + def say_notification(self, something): + status = (self._peding or self.status).clone() + status.voice = voice.defaultVoice() + self._audio.speak(status, something) + + def shut_up(self): + self._audio.stop_sound_device() + + def _size_allocate_cb(self, widget, allocation): + self._mouthbox.set_size_request(-1, int(allocation.height/2.5)) -- cgit v0.9.1