Codebase list sugar-read-activity / ef211f3
Port to sugar3.speech James Cameron authored 5 years ago Rahul Bothra committed 5 years ago
6 changed file(s) with 86 addition(s) and 344 deletion(s). Raw diff Collapse all Expand all
5252 from sugar3.activity.widgets import ActivityToolbarButton
5353 from sugar3.activity.widgets import StopButton
5454 from sugar3.graphics.tray import HTray
55 from sugar3.graphics.menuitem import MenuItem
5556 from sugar3 import network
5657 from sugar3 import mime
5758 from sugar3 import profile
7071 from readtoolbar import ViewToolbar
7172 from bookmarkview import BookmarkView
7273 from readdb import BookmarkManager
73 from sugar3.graphics.menuitem import MenuItem
7474 from linkbutton import LinkButton
75 from speechtoolbar import SpeechToolbar
7576
7677 _HARDWARE_MANAGER_INTERFACE = 'org.laptop.HardwareManager'
7778 _HARDWARE_MANAGER_SERVICE = 'org.laptop.HardwareManager'
10651066 if self._view.can_highlight():
10661067 self._highlight.show()
10671068 if self._view.can_do_text_to_speech():
1068 import speech
1069 from speechtoolbar import SpeechToolbar
1070 if speech.supported:
1071 self.speech_toolbar = SpeechToolbar(self)
1072 self.speech_toolbar_button.set_page(self.speech_toolbar)
1073 self.speech_toolbar_button.show()
1069 self.speech_toolbar = SpeechToolbar(self)
1070 self.speech_toolbar_button.set_page(self.speech_toolbar)
1071 self.speech_toolbar_button.show()
10741072
10751073 def _share_document(self):
10761074 """Share the document."""
+0
-45
speech.py less more
0 # Copyright (C) 2008, 2009 James D. Simmons
1 # Copyright (C) 2009 Aleksey S. Lim
2 #
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or
6 # (at your option) any later version.
7 #
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
12 #
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software
15 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
16
17 import logging
18
19 _logger = logging.getLogger('read-activity')
20
21 supported = True
22
23 try:
24 from gi.repository import Gst
25 Gst.init(None)
26 Gst.ElementFactory.make('espeak', None)
27 from speech_gst import *
28 _logger.debug('use gst-plugins-espeak')
29 except Exception, e:
30 _logger.debug('disable gst-plugins-espeak: %s' % e)
31 try:
32 from speech_dispatcher import *
33 _logger.debug('use speech-dispatcher')
34 except Exception, e:
35 supported = False
36 _logger.debug('disable speech: %s' % e)
37
38 voice = 'default'
39 pitch = 0
40 rate = 0
41
42 highlight_cb = None
43 end_text_cb = None
44 reset_cb = None
+0
-124
speech_dispatcher.py less more
0 # Copyright (C) 2008 James D. Simmons
1 #
2 # This program is free software; you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation; either version 2 of the License, or
5 # (at your option) any later version.
6 #
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
11 #
12 # You should have received a copy of the GNU General Public License
13 # along with this program; if not, write to the Free Software
14 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
15
16 from gi.repository import Gdk
17 import time
18 import threading
19 import speechd
20 import logging
21
22 import speech
23
24 _logger = logging.getLogger('read-etexts-activity')
25
26 done = True
27
28
29 def voices():
30 try:
31 client = speechd.SSIPClient('readetextstest')
32 voices = client.list_synthesis_voices()
33 client.close()
34 return voices
35 except Exception, e:
36 _logger.warning('speech dispatcher not started: %s' % e)
37 return []
38
39
40 def say(words):
41 try:
42 client = speechd.SSIPClient('readetextstest')
43 client.set_rate(int(speech.rate))
44 client.set_pitch(int(speech.pitch))
45 client.set_language(speech.voice[1])
46 client.speak(words)
47 client.close()
48 except Exception, e:
49 _logger.warning('speech dispatcher not running: %s' % e)
50
51
52 def is_stopped():
53 return done
54
55
56 def pause():
57 pass
58
59
60 def stop():
61 global done
62 done = True
63
64
65 def play(words):
66 global thread
67 thread = EspeakThread(words)
68 thread.start()
69
70
71 class EspeakThread(threading.Thread):
72
73 def __init__(self, words):
74 threading.Thread.__init__(self)
75 self.words = words
76
77 def run(self):
78 "This is the code that is executed when the start() method is called"
79 self.client = None
80 try:
81 self.client = speechd.SSIPClient('readetexts')
82 self.client._conn.send_command('SET', speechd.Scope.SELF,
83 'SSML_MODE', "ON")
84 if speech.voice:
85 self.client.set_language(speech.voice[1])
86 self.client.set_rate(speech.rate)
87 self.client.set_pitch(speech.pitch)
88 self.client.speak(self.words, self.next_word_cb,
89 (speechd.CallbackType.INDEX_MARK,
90 speechd.CallbackType.END))
91 global done
92 done = False
93 while not done:
94 time.sleep(0.1)
95 self.cancel()
96 self.client.close()
97 except Exception, e:
98 _logger.warning('speech-dispatcher client not created: %s' % e)
99
100 def cancel(self):
101 if self.client:
102 try:
103 self.client.cancel()
104 except Exception, e:
105 _logger.warning('speech dispatcher cancel failed: %s' % e)
106
107 def next_word_cb(self, type, **kargs):
108 if type == speechd.CallbackType.INDEX_MARK:
109 mark = kargs['index_mark']
110 word_count = int(mark)
111 Gdk.threads_enter()
112 speech.highlight_cb(word_count)
113 Gdk.threads_leave()
114 elif type == speechd.CallbackType.END:
115 Gdk.threads_enter()
116 if speech.reset_cb is not None:
117 speech.reset_cb()
118 if speech.reset_buttons_cb is not None:
119 speech.reset_buttons_cb()
120
121 Gdk.threads_leave()
122 global done
123 done = True
+0
-104
speech_gst.py less more
0 # Copyright (C) 2009 Aleksey S. Lim
1 #
2 # This program is free software; you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation; either version 2 of the License, or
5 # (at your option) any later version.
6 #
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
11 #
12 # You should have received a copy of the GNU General Public License
13 # along with this program; if not, write to the Free Software
14 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
15
16 from gi.repository import Gst
17 import logging
18
19 import speech
20
21 _logger = logging.getLogger('read-activity')
22
23
24 def get_all_voices():
25 all_voices = {}
26 for voice in Gst.ElementFactory.make('espeak', None).props.voices:
27 name, language, dialect = voice
28 if dialect != 'none':
29 all_voices[language + '_' + dialect] = name
30 else:
31 all_voices[language] = name
32 return all_voices
33
34
35 def _message_cb(bus, message, pipe):
36 if message.type == Gst.MessageType.EOS:
37 pipe.set_state(Gst.State.NULL)
38 if speech.end_text_cb is not None:
39 speech.end_text_cb()
40 if message.type == Gst.MessageType.ERROR:
41 pipe.set_state(Gst.State.NULL)
42 if pipe is play_speaker[1]:
43 speech.reset_cb()
44 elif message.type == Gst.MessageType.ELEMENT and \
45 message.get_structure().get_name() == 'espeak-mark':
46 mark = message.get_structure()['mark']
47 speech.highlight_cb(int(mark))
48
49
50 def _create_pipe():
51 pipe = Gst.parse_launch('espeak name=espeak ! autoaudiosink')
52 source = pipe.get_by_name('espeak')
53
54 bus = pipe.get_bus()
55 bus.add_signal_watch()
56 bus.connect('message', _message_cb, pipe)
57
58 return (source, pipe)
59
60
61 def _speech(speaker, words):
62 speaker[0].props.pitch = speech.pitch
63 speaker[0].props.rate = speech.rate
64 speaker[0].props.voice = speech.voice[1]
65 speaker[0].props.text = words
66 speaker[1].set_state(Gst.State.NULL)
67 speaker[1].set_state(Gst.State.PLAYING)
68
69
70 info_speaker = _create_pipe()
71 play_speaker = _create_pipe()
72 play_speaker[0].props.track = 2
73
74
75 def voices():
76 return info_speaker[0].props.voices
77
78
79 def say(words):
80 _speech(info_speaker, words)
81
82
83 def play(words):
84 _speech(play_speaker, words)
85
86
87 def pause():
88 play_speaker[1].set_state(Gst.State.PAUSED)
89
90
91 def continue_play():
92 play_speaker[1].set_state(Gst.State.PLAYING)
93
94
95 def is_stopped():
96 for i in play_speaker[1].get_state():
97 if isinstance(i, Gst.State) and i == Gst.State.NULL:
98 return True
99 return False
100
101
102 def stop():
103 play_speaker[1].set_state(Gst.State.NULL)
2323 from sugar3.graphics.toggletoolbutton import ToggleToolButton
2424 from sugar3.graphics.combobox import ComboBox
2525 from sugar3.graphics.toolcombobox import ToolComboBox
26
27 import speech
26 from sugar3.speech import SpeechManager
2827
2928
3029 class SpeechToolbar(Gtk.Toolbar):
3231 def __init__(self, activity):
3332 Gtk.Toolbar.__init__(self)
3433 self._activity = activity
35 if not speech.supported:
36 return
37 self.is_paused = False
34 self._speech = SpeechManager()
35 self._is_paused = False
3836
3937 self.load_speech_parameters()
4038
41 self.sorted_voices = [i for i in speech.voices()]
42 self.sorted_voices.sort(self.compare_voices)
43 default = 0
44 for voice in self.sorted_voices:
45 if voice[0] == speech.voice[0]:
46 break
47 default = default + 1
39 self._voices = self._speech.get_all_voices() # a dictionary
40
41 locale = os.environ.get('LANG', '')
42 language_location = locale.split('.', 1)[0].lower()
43 language = language_location.split('_')[0]
44 # if the language is es but not es_es default to es_la (latin voice)
45 if language == 'es' and language_location != 'es_es':
46 language_location = 'es_la'
47
48 self._voice = 'en_us'
49 if language_location in self._voices:
50 self._voice = language_location
51 elif language in self._voices:
52 self._voice = language
53
54 voice_names = []
55 for language, name in self._voices.iteritems():
56 voice_names.append((language, name))
57 voice_names.sort(self._compare_voice)
4858
4959 # Play button
50 self.play_btn = ToggleToolButton('media-playback-start')
51 self.play_btn.show()
52 self.play_btn.connect('toggled', self.play_cb)
53 self.insert(self.play_btn, -1)
54 self.play_btn.set_tooltip(_('Play / Pause'))
60 self._play_button = ToggleToolButton('media-playback-start')
61 self._play_button.show()
62 self._play_button.connect('toggled', self._play_toggled_cb)
63 self.insert(self._play_button, -1)
64 self._play_button.set_tooltip(_('Play / Pause'))
5565
5666 # Stop button
57 self.stop_btn = ToolButton('media-playback-stop')
58 self.stop_btn.show()
59 self.stop_btn.connect('clicked', self.stop_cb)
60 self.stop_btn.set_sensitive(False)
61 self.insert(self.stop_btn, -1)
62 self.stop_btn.set_tooltip(_('Stop'))
67 self._stop_button = ToolButton('media-playback-stop')
68 self._stop_button.show()
69 self._stop_button.connect('clicked', self._stop_clicked_cb)
70 self._stop_button.set_sensitive(False)
71 self.insert(self._stop_button, -1)
72 self._stop_button.set_tooltip(_('Stop'))
6373
64 self.voice_combo = ComboBox()
65 for voice in self.sorted_voices:
66 self.voice_combo.append_item(voice, voice[0])
67 self.voice_combo.set_active(default)
74 # Language list
75 combo = ComboBox()
76 which = 0
77 for pair in voice_names:
78 language, name = pair
79 combo.append_item(language, name)
80 if language == self._voice:
81 combo.set_active(which)
82 which += 1
6883
69 self.voice_combo.connect('changed', self.voice_changed_cb)
70 combotool = ToolComboBox(self.voice_combo)
84 combo.connect('changed', self._voice_changed_cb)
85 combotool = ToolComboBox(combo)
7186 self.insert(combotool, -1)
7287 combotool.show()
73 speech.reset_buttons_cb = self.reset_buttons_cb
7488
75 def compare_voices(self, a, b):
76 if a[0].lower() == b[0].lower():
89 self._speech.connect('stop', self._reset_buttons_cb)
90
91 def _compare_voice(self, a, b):
92 if a[1].lower() == b[1].lower():
7793 return 0
78 if a[0] .lower() < b[0].lower():
94 if a[1].lower() < b[1].lower():
7995 return -1
80 if a[0] .lower() > b[0].lower():
96 if a[1].lower() > b[1].lower():
8197 return 1
8298
83 def voice_changed_cb(self, combo):
84 speech.voice = combo.props.value
85 speech.say(speech.voice[0])
99 def _voice_changed_cb(self, combo):
100 self._voice = combo.props.value
101 self._speech.say_text(self._voices[self._voice])
86102 self.save_speech_parameters()
87103
88104 def load_speech_parameters(self):
93109 f = open(data_file_name, 'r')
94110 try:
95111 speech_parameters = json.load(f)
96 speech.voice = speech_parameters['voice']
112 self._voice = speech_parameters['voice']
97113 finally:
98114 f.close()
99115
100116 def save_speech_parameters(self):
101117 speech_parameters = {}
102 speech_parameters['voice'] = speech.voice
118 speech_parameters['voice'] = self._voice
103119 data_path = os.path.join(self._activity.get_activity_root(), 'data')
104120 data_file_name = os.path.join(data_path, 'speech_params.json')
105121 f = open(data_file_name, 'w')
108124 finally:
109125 f.close()
110126
111 def reset_buttons_cb(self):
112 self.play_btn.set_icon_name('media-playback-start')
113 self.stop_btn.set_sensitive(False)
114 self.is_paused = False
127 def _reset_buttons_cb(self, widget=None):
128 self._play_button.set_icon_name('media-playback-start')
129 self._stop_button.set_sensitive(False)
130 self._is_paused = False
115131
116 def play_cb(self, widget):
117 self.stop_btn.set_sensitive(True)
132 def _play_toggled_cb(self, widget):
133 self._stop_button.set_sensitive(True)
118134 if widget.get_active():
119 self.play_btn.set_icon_name('media-playback-pause')
120 if not self.is_paused:
121 speech.play(self._activity._view.get_marked_words())
135 self._play_button.set_icon_name('media-playback-pause')
136 if not self._is_paused:
137 self._speech.say_text(
138 self._activity._view.get_marked_words(),
139 lang_code=self._voice)
122140 else:
123 speech.continue_play()
141 self._speech.restart()
124142 else:
125 self.play_btn.set_icon_name('media-playback-start')
126 self.is_paused = True
127 speech.pause()
143 self._play_button.set_icon_name('media-playback-start')
144 self._is_paused = True
145 self._speech.pause()
128146
129 def stop_cb(self, widget):
130 self.stop_btn.set_sensitive(False)
131 self.play_btn.set_icon_name('media-playback-start')
132 self.play_btn.set_active(False)
133 self.is_paused = False
134 speech.stop()
147 def _stop_clicked_cb(self, widget):
148 self._stop_button.set_sensitive(False)
149 self._play_button.set_icon_name('media-playback-start')
150 self._play_button.set_active(False)
151 self._is_paused = False
152 self._speech.stop()
88
99 from sugar3 import mime
1010 from sugar3.graphics import style
11
12 import speech
1311
1412 PAGE_SIZE = 38
1513
135133 self._scrollbar.set_range(1.0, self._pagecount - 1.0)
136134 self._scrollbar.set_increments(1.0, 1.0)
137135
138 speech.highlight_cb = self.highlight_next_word
139 speech.reset_cb = self.reset_text_to_speech
136 # TODO: if ever sugar3.speech has word signals
137 # call self.highlight_next_word on each word
138 # call self.reset_text_to_speech at end
140139
141140 def _show_page(self, page_number):
142141 position = self.page_index[page_number]
280279 marked_up_text = '<speak> '
281280 while i < len(self.word_tuples):
282281 word_tuple = self.word_tuples[i]
283 marked_up_text = marked_up_text + '<mark name="' + str(i) + '"/>' \
284 + word_tuple[2]
282 marked_up_text = marked_up_text + \
283 ' <mark name="' + str(i) + '"/>' + word_tuple[2]
285284 i = i + 1
286285 print marked_up_text
287286 return marked_up_text + '</speak>'