Port to sugar3.speech
James Cameron authored 5 years ago
Rahul Bothra committed 5 years ago
52 | 52 | from sugar3.activity.widgets import ActivityToolbarButton |
53 | 53 | from sugar3.activity.widgets import StopButton |
54 | 54 | from sugar3.graphics.tray import HTray |
55 | from sugar3.graphics.menuitem import MenuItem | |
55 | 56 | from sugar3 import network |
56 | 57 | from sugar3 import mime |
57 | 58 | from sugar3 import profile |
70 | 71 | from readtoolbar import ViewToolbar |
71 | 72 | from bookmarkview import BookmarkView |
72 | 73 | from readdb import BookmarkManager |
73 | from sugar3.graphics.menuitem import MenuItem | |
74 | 74 | from linkbutton import LinkButton |
75 | from speechtoolbar import SpeechToolbar | |
75 | 76 | |
76 | 77 | _HARDWARE_MANAGER_INTERFACE = 'org.laptop.HardwareManager' |
77 | 78 | _HARDWARE_MANAGER_SERVICE = 'org.laptop.HardwareManager' |
1065 | 1066 | if self._view.can_highlight(): |
1066 | 1067 | self._highlight.show() |
1067 | 1068 | 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() | |
1074 | 1072 | |
1075 | 1073 | def _share_document(self): |
1076 | 1074 | """Share the document.""" |
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 | # 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 | # 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) |
23 | 23 | from sugar3.graphics.toggletoolbutton import ToggleToolButton |
24 | 24 | from sugar3.graphics.combobox import ComboBox |
25 | 25 | from sugar3.graphics.toolcombobox import ToolComboBox |
26 | ||
27 | import speech | |
26 | from sugar3.speech import SpeechManager | |
28 | 27 | |
29 | 28 | |
30 | 29 | class SpeechToolbar(Gtk.Toolbar): |
32 | 31 | def __init__(self, activity): |
33 | 32 | Gtk.Toolbar.__init__(self) |
34 | 33 | self._activity = activity |
35 | if not speech.supported: | |
36 | return | |
37 | self.is_paused = False | |
34 | self._speech = SpeechManager() | |
35 | self._is_paused = False | |
38 | 36 | |
39 | 37 | self.load_speech_parameters() |
40 | 38 | |
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) | |
48 | 58 | |
49 | 59 | # 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')) | |
55 | 65 | |
56 | 66 | # 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')) | |
63 | 73 | |
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 | |
68 | 83 | |
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) | |
71 | 86 | self.insert(combotool, -1) |
72 | 87 | combotool.show() |
73 | speech.reset_buttons_cb = self.reset_buttons_cb | |
74 | 88 | |
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(): | |
77 | 93 | return 0 |
78 | if a[0] .lower() < b[0].lower(): | |
94 | if a[1].lower() < b[1].lower(): | |
79 | 95 | return -1 |
80 | if a[0] .lower() > b[0].lower(): | |
96 | if a[1].lower() > b[1].lower(): | |
81 | 97 | return 1 |
82 | 98 | |
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]) | |
86 | 102 | self.save_speech_parameters() |
87 | 103 | |
88 | 104 | def load_speech_parameters(self): |
93 | 109 | f = open(data_file_name, 'r') |
94 | 110 | try: |
95 | 111 | speech_parameters = json.load(f) |
96 | speech.voice = speech_parameters['voice'] | |
112 | self._voice = speech_parameters['voice'] | |
97 | 113 | finally: |
98 | 114 | f.close() |
99 | 115 | |
100 | 116 | def save_speech_parameters(self): |
101 | 117 | speech_parameters = {} |
102 | speech_parameters['voice'] = speech.voice | |
118 | speech_parameters['voice'] = self._voice | |
103 | 119 | data_path = os.path.join(self._activity.get_activity_root(), 'data') |
104 | 120 | data_file_name = os.path.join(data_path, 'speech_params.json') |
105 | 121 | f = open(data_file_name, 'w') |
108 | 124 | finally: |
109 | 125 | f.close() |
110 | 126 | |
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 | |
115 | 131 | |
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) | |
118 | 134 | 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) | |
122 | 140 | else: |
123 | speech.continue_play() | |
141 | self._speech.restart() | |
124 | 142 | 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() | |
128 | 146 | |
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() |
8 | 8 | |
9 | 9 | from sugar3 import mime |
10 | 10 | from sugar3.graphics import style |
11 | ||
12 | import speech | |
13 | 11 | |
14 | 12 | PAGE_SIZE = 38 |
15 | 13 | |
135 | 133 | self._scrollbar.set_range(1.0, self._pagecount - 1.0) |
136 | 134 | self._scrollbar.set_increments(1.0, 1.0) |
137 | 135 | |
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 | |
140 | 139 | |
141 | 140 | def _show_page(self, page_number): |
142 | 141 | position = self.page_index[page_number] |
280 | 279 | marked_up_text = '<speak> ' |
281 | 280 | while i < len(self.word_tuples): |
282 | 281 | 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] | |
285 | 284 | i = i + 1 |
286 | 285 | print marked_up_text |
287 | 286 | return marked_up_text + '</speak>' |