Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAneesh Dogra <lionaneesh@gmail.com>2012-12-12 11:58:44 (GMT)
committer Aneesh Dogra <lionaneesh@gmail.com>2012-12-12 11:58:44 (GMT)
commit9c349009cb02451ef6f1dbd00eebf57f8725df59 (patch)
tree9f84ac4765e58b30f186aa7ac29cce9240e98e2b
parente4e91d4bb19444297722e2953dfcb967dd2663c6 (diff)
Add gtk3 version of Adding_TTS.
-rwxr-xr-xAdding_TTS_gtk3/ReadEtextsTTS.py272
-rwxr-xr-xAdding_TTS_gtk3/espeak.py63
-rwxr-xr-xAdding_TTS_gtk3/gst_choir_example.py51
-rwxr-xr-xAdding_TTS_gtk3/gst_simple_example.py36
-rwxr-xr-xAdding_TTS_gtk3/gst_simple_tts.py93
-rwxr-xr-xAdding_TTS_gtk3/gst_track_marks.py54
-rwxr-xr-xAdding_TTS_gtk3/gst_track_words.py50
-rw-r--r--Adding_TTS_gtk3/speech.py136
-rw-r--r--Adding_TTS_gtk3/testtts.txt21
9 files changed, 776 insertions, 0 deletions
diff --git a/Adding_TTS_gtk3/ReadEtextsTTS.py b/Adding_TTS_gtk3/ReadEtextsTTS.py
new file mode 100755
index 0000000..161f8c1
--- /dev/null
+++ b/Adding_TTS_gtk3/ReadEtextsTTS.py
@@ -0,0 +1,272 @@
+#! /usr/bin/env python
+#
+# ReadEtextsTTS.py A standalone ebook reader program that
+# demonstrates text to speech with word highlighting.
+# Copyright (C) 2010 James D. Simmons
+#
+# This program 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 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+import sys
+import os
+import zipfile
+from gi.repository import Gtk
+from gi.repository import Gdk
+import getopt
+from gi.repository import Pango
+from gi.repository import GObject
+import time
+import speech
+
+speech_supported = True
+
+try:
+ import gst
+ gst.element_factory_make('espeak')
+ print 'speech supported!'
+except Exception, e:
+ speech_supported = False
+ print 'speech not supported!'
+
+page=0
+PAGE_SIZE = 45
+
+class ReadEtextsActivity():
+ def __init__(self):
+ "The entry point to the Activity"
+ speech.highlight_cb = self.highlight_next_word
+ # print speech.voices()
+
+ def highlight_next_word(self, word_count):
+ if word_count < len(self.word_tuples):
+ word_tuple = self.word_tuples[word_count]
+ textbuffer = self.textview.get_buffer()
+ tag = textbuffer.create_tag()
+ tag.set_property('weight', Pango.Weight.BOLD)
+ tag.set_property('foreground', "white")
+ tag.set_property('background', "black")
+ iterStart = textbuffer.get_iter_at_offset(word_tuple[0])
+ iterEnd = textbuffer.get_iter_at_offset(word_tuple[1])
+ bounds = textbuffer.get_bounds()
+ textbuffer.remove_all_tags(bounds[0], bounds[1])
+ textbuffer.apply_tag(tag, iterStart, iterEnd)
+ v_adjustment = self.scrolled_window.get_vadjustment()
+ max = v_adjustment.get_upper() - v_adjustment.get_page_size()
+ max = max * word_count
+ max = max / len(self.word_tuples)
+ v_adjustment.set_value(max)
+ return True
+
+ def keypress_cb(self, widget, event):
+ "Respond when the user presses one of the arrow keys"
+ global done
+ global speech_supported
+ keyname = Gdk.keyval_name(event.keyval)
+ if keyname == 'KP_End' and speech_supported:
+ if speech.is_paused() or speech.is_stopped():
+ speech.play(self.words_on_page)
+ else:
+ speech.pause()
+ return True
+ if keyname == 'plus':
+ self.font_increase()
+ return True
+ if keyname == 'minus':
+ self.font_decrease()
+ return True
+ if speech_supported and speech.is_stopped() == False and speech.is_paused == False:
+ # If speech is in progress, ignore other keys.
+ return True
+ if keyname == '7':
+ speech.pitch_down()
+ speech.say('Pitch Adjusted')
+ return True
+ if keyname == '8':
+ speech.pitch_up()
+ speech.say('Pitch Adjusted')
+ return True
+ if keyname == '9':
+ speech.rate_down()
+ speech.say('Rate Adjusted')
+ return True
+ if keyname == '0':
+ speech.rate_up()
+ speech.say('Rate Adjusted')
+ return True
+ if keyname == 'KP_Right':
+ self.page_next()
+ return True
+ if keyname == 'Page_Up' or keyname == 'KP_Up':
+ self.page_previous()
+ return True
+ if keyname == 'KP_Left':
+ self.page_previous()
+ return True
+ if keyname == 'Page_Down' or keyname == 'KP_Down':
+ self.page_next()
+ return True
+ if keyname == 'Up':
+ self.scroll_up()
+ return True
+ if keyname == 'Down':
+ self.scroll_down()
+ return True
+ return False
+
+ def page_previous(self):
+ global page
+ page=page-1
+ if page < 0: page=0
+ self.show_page(page)
+ v_adjustment = self.scrolled_window.get_vadjustment()
+ v_adjustment.set_value(v_adjustment.get_upper() - \
+ v_adjustment.get_page_size())
+
+ def page_next(self):
+ global page
+ page=page+1
+ if page >= len(self.page_index): page=0
+ self.show_page(page)
+ v_adjustment = self.scrolled_window.get_vadjustment()
+ v_adjustment.set_value(v_adjustment.get_lower())
+
+ def font_decrease(self):
+ font_size = self.font_desc.get_size() / 1024
+ font_size = font_size - 1
+ if font_size < 1:
+ font_size = 1
+ self.font_desc.set_size(font_size * 1024)
+ self.textview.modify_font(self.font_desc)
+
+ def font_increase(self):
+ font_size = self.font_desc.get_size() / 1024
+ font_size = font_size + 1
+ self.font_desc.set_size(font_size * 1024)
+ self.textview.modify_font(self.font_desc)
+
+ def scroll_down(self):
+ v_adjustment = self.scrolled_window.get_vadjustment()
+ if v_adjustment.get_value() == v_adjustment.get_upper() - v_adjustment.get_page_size():
+ self.page_next()
+ return
+ if v_adjustment.get_value() < v_adjustment.get_upper() - v_adjustment.get_page_size():
+ new_value = v_adjustment.get_value() + v_adjustment.step_increment
+ if new_value > v_adjustment.get_upper() - v_adjustment.get_page_size():
+ new_value = v_adjustment.get_upper() - v_adjustment.get_page_size()
+ v_adjustment.set_value(new_value)
+
+ def scroll_up(self):
+ v_adjustment = self.scrolled_window.get_vadjustment()
+ if v_adjustment.get_value() == v_adjustment.get_lower():
+ self.page_previous()
+ return
+ if v_adjustment.get_value() > v_adjustment.get_lower():
+ new_value = v_adjustment.get_value() - v_adjustment.step_increment
+ if new_value < v_adjustment.get_lower():
+ new_value = v_adjustment.get_lower()
+ v_adjustment.set_value(new_value)
+
+ def show_page(self, page_number):
+ global PAGE_SIZE, current_word
+ position = self.page_index[page_number]
+ self.etext_file.seek(position)
+ linecount = 0
+ label_text = ''
+ textbuffer = self.textview.get_buffer()
+ while linecount < PAGE_SIZE:
+ line = self.etext_file.readline()
+ label_text = label_text + unicode(line, 'iso-8859-1')
+ linecount = linecount + 1
+ textbuffer.set_text(label_text)
+ self.textview.set_buffer(textbuffer)
+ self.word_tuples = speech.prepare_highlighting(label_text)
+ self.words_on_page = speech.add_word_marks(self.word_tuples)
+
+ def save_extracted_file(self, zipfile, filename):
+ "Extract the file to a temp directory for viewing"
+ filebytes = zipfile.read(filename)
+ f = open("/tmp/" + filename, 'w')
+ try:
+ f.write(filebytes)
+ finally:
+ f.close()
+
+ def read_file(self, filename):
+ "Read the Etext file"
+ global PAGE_SIZE
+
+ if zipfile.is_zipfile(filename):
+ self.zf = zipfile.ZipFile(filename, 'r')
+ self.book_files = self.zf.namelist()
+ self.save_extracted_file(self.zf, self.book_files[0])
+ currentFileName = "/tmp/" + self.book_files[0]
+ else:
+ currentFileName = filename
+
+ self.etext_file = open(currentFileName,"r")
+ self.page_index = [ 0 ]
+ linecount = 0
+ while self.etext_file:
+ line = self.etext_file.readline()
+ if not line:
+ break
+ linecount = linecount + 1
+ if linecount >= PAGE_SIZE:
+ position = self.etext_file.tell()
+ self.page_index.append(position)
+ linecount = 0
+ if filename.endswith(".zip"):
+ os.remove(currentFileName)
+
+ def delete_cb(self, widget, event, data=None):
+ speech.stop()
+ return False
+
+ def destroy_cb(self, widget, data=None):
+ speech.stop()
+ Gtk.main_quit()
+
+ def main(self, file_path):
+ self.window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
+ self.window.connect("delete_event", self.delete_cb)
+ self.window.connect("destroy", self.destroy_cb)
+ self.window.set_title("Read Etexts Activity")
+ self.window.set_size_request(800, 600)
+ self.window.set_border_width(0)
+ self.read_file(file_path)
+ self.scrolled_window = Gtk.ScrolledWindow(hadjustment=None, vadjustment=None)
+ self.textview = Gtk.TextView()
+ self.textview.set_editable(False)
+ self.textview.set_left_margin(50)
+ self.textview.set_cursor_visible(False)
+ self.textview.connect("key_press_event", self.keypress_cb)
+ self.font_desc = Pango.FontDescription("sans 12")
+ self.textview.modify_font(self.font_desc)
+ self.show_page(0)
+ self.scrolled_window.add(self.textview)
+ self.window.add(self.scrolled_window)
+ self.textview.show()
+ self.scrolled_window.show()
+ self.window.show()
+ Gtk.main()
+
+if __name__ == "__main__":
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], "")
+ ReadEtextsActivity().main(args[0])
+ except getopt.error, msg:
+ print msg
+ print "This program has no options"
+ sys.exit(2)
diff --git a/Adding_TTS_gtk3/espeak.py b/Adding_TTS_gtk3/espeak.py
new file mode 100755
index 0000000..fbb1b22
--- /dev/null
+++ b/Adding_TTS_gtk3/espeak.py
@@ -0,0 +1,63 @@
+#! /usr/bin/env python
+#
+# espeak.py
+# Use espeak, part of Make Your Own Sugar Activities Book Examples.
+# Copyright (C) 2010 James D. Simmons
+#
+# This program 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 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+import re
+import subprocess
+
+PITCH_MAX = 99
+RATE_MAX = 99
+PITCH_DEFAULT = PITCH_MAX/2
+RATE_DEFAULT = RATE_MAX/3
+
+def speak(text, rate=RATE_DEFAULT, pitch=PITCH_DEFAULT, voice="default"):
+
+ # espeak uses 80 to 370
+ rate = 80 + (370-80) * int(rate) / 100
+
+ subprocess.call(["espeak", "-p", str(pitch),
+ "-s", str(rate), "-v", voice, text],
+ stdout=subprocess.PIPE)
+
+def voices():
+ out = []
+ 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 not m:
+ continue
+ 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
+ out.append((language, name))
+
+ return out
+
+def main():
+ print voices()
+ speak("I'm afraid I can't do that, Dave.")
+ speak("Your mother was a hamster, and your father smelled of elderberries!", 30, 60, "fr")
+
+if __name__ == "__main__":
+ main()
diff --git a/Adding_TTS_gtk3/gst_choir_example.py b/Adding_TTS_gtk3/gst_choir_example.py
new file mode 100755
index 0000000..58a2534
--- /dev/null
+++ b/Adding_TTS_gtk3/gst_choir_example.py
@@ -0,0 +1,51 @@
+#
+# <one line to give the program's name and a brief idea of what it does.>
+# Copyright (C) <YEAR> <NAME>
+#
+# This program 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 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+from gi.repository import Gtk
+import gst
+import random
+from gettext import gettext as _
+
+def gstmessage_cb(bus, message, pipe):
+ if message.type in (gst.MESSAGE_EOS, gst.MESSAGE_ERROR):
+ pipe.set_state(gst.STATE_NULL)
+
+def make_pipe():
+ pipeline = 'espeak name=src ! autoaudiosink'
+ pipe = gst.parse_launch(pipeline)
+
+ src = pipe.get_by_name('src')
+ src.props.text = _('Hello, World!')
+ src.props.pitch = random.randint(-100, 100)
+ src.props.rate = random.randint(-100, 100)
+
+ voices = src.props.voices
+ voice = voices[random.randint(0, len(voices)-1)]
+ src.props.voice = voice[0]
+
+ bus = pipe.get_bus()
+ bus.add_signal_watch()
+ bus.connect('message', gstmessage_cb, pipe)
+
+ pipe.set_state(gst.STATE_PLAYING)
+
+for i in range(10):
+ make_pipe()
+
+Gtk.main()
diff --git a/Adding_TTS_gtk3/gst_simple_example.py b/Adding_TTS_gtk3/gst_simple_example.py
new file mode 100755
index 0000000..3962777
--- /dev/null
+++ b/Adding_TTS_gtk3/gst_simple_example.py
@@ -0,0 +1,36 @@
+#
+# <one line to give the program's name and a brief idea of what it does.>
+# Copyright (C) <YEAR> <NAME>
+#
+# This program 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 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+from gi.repository import Gtk
+import gst
+
+def gstmessage_cb(bus, message, pipe):
+ if message.type in (gst.MESSAGE_EOS, gst.MESSAGE_ERROR):
+ pipe.set_state(gst.STATE_NULL)
+
+pipeline = 'espeak text="Hello, World!" ! autoaudiosink'
+pipe = gst.parse_launch(pipeline)
+
+bus = pipe.get_bus()
+bus.add_signal_watch()
+bus.connect('message', gstmessage_cb, pipe)
+
+pipe.set_state(gst.STATE_PLAYING)
+
+Gtk.main()
diff --git a/Adding_TTS_gtk3/gst_simple_tts.py b/Adding_TTS_gtk3/gst_simple_tts.py
new file mode 100755
index 0000000..2148ffe
--- /dev/null
+++ b/Adding_TTS_gtk3/gst_simple_tts.py
@@ -0,0 +1,93 @@
+# gst_simple_tts.py
+# Copyright (C) 2010 Aleksey Lim
+#
+# This program 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 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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 this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gi.repository import Gtk
+import gst
+from gi.repository import Pango
+
+window = Gtk.Window()
+window.connect('destroy',
+ lambda sender: Gtk.main_quit())
+
+workspace = Gtk.VBox()
+window.add(workspace)
+
+# text widget
+
+scrolled = Gtk.ScrolledWindow()
+workspace.pack_start(scrolled, True, True, 0)
+
+text = Gtk.TextView()
+text.set_left_margin(50)
+text.set_right_margin(50)
+text.set_wrap_mode(Gtk.WrapMode.WORD)
+scrolled.add(text)
+
+buffer = text.props.buffer
+buffer.props.text = file("testtts.txt").read()
+
+tag = buffer.create_tag()
+tag.props.weight = Pango.Weight.BOLD
+
+# play controls
+
+toolbar = Gtk.HBox()
+workspace.pack_end(toolbar, False, True, 0)
+
+play = Gtk.Button('Play/Resume')
+play.connect('clicked',
+ lambda sender: pipe.set_state(gst.STATE_PLAYING))
+toolbar.add(play)
+
+pause = Gtk.Button('Pause')
+pause.connect('clicked',
+ lambda sender: pipe.set_state(gst.STATE_PAUSED))
+toolbar.add(pause)
+
+stop = Gtk.Button('Stop')
+stop.connect('clicked',
+ lambda sender: pipe.set_state(gst.STATE_NULL))
+toolbar.add(stop)
+
+# gst code
+
+pipe = gst.parse_launch('espeak name=src ! autoaudiosink')
+
+src = pipe.get_by_name('src')
+src.props.text = buffer.props.text
+src.props.track = 1 # track for words
+
+def tts_cb(bus, message):
+ if message.structure.get_name() != 'espeak-word':
+ return
+
+ offset = message.structure['offset']
+ len = message.structure['len']
+
+ buffer.remove_tag(tag, buffer.get_start_iter(), buffer.get_end_iter())
+ start = buffer.get_iter_at_offset(offset)
+ end = buffer.get_iter_at_offset(offset + len)
+ buffer.apply_tag(tag, start, end)
+
+bus = pipe.get_bus()
+bus.add_signal_watch()
+bus.connect('message::element', tts_cb)
+
+# gtk start
+
+window.show_all()
+Gtk.main()
diff --git a/Adding_TTS_gtk3/gst_track_marks.py b/Adding_TTS_gtk3/gst_track_marks.py
new file mode 100755
index 0000000..a1e9f8e
--- /dev/null
+++ b/Adding_TTS_gtk3/gst_track_marks.py
@@ -0,0 +1,54 @@
+#
+# gst_track_marks.py
+# Sample code for using the gstreamer espeak plugin.
+
+# Copyright (C) 2010 Aleksey Lim
+#
+# This program 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 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+from gi.repository import Gtk
+import gst
+
+text = '<mark name="mark to Hello"/>Hello, <mark name="mark for World"/>World!'
+
+def gstmessage_cb(bus, message, pipe):
+ if message.type in (gst.MESSAGE_EOS, gst.MESSAGE_ERROR):
+ pipe.set_state(gst.STATE_NULL)
+ elif message.type == gst.MESSAGE_ELEMENT and \
+ message.structure.get_name() == 'espeak-mark':
+ offset = message.structure['offset']
+ mark = message.structure['mark']
+ print '%d:%s' % (offset, mark)
+
+pipe = gst.Pipeline('pipeline')
+
+src = gst.element_factory_make('espeak', 'src')
+src.props.text = text
+src.props.track = 2
+src.props.gap = 100
+pipe.add(src)
+
+sink = gst.element_factory_make('autoaudiosink', 'sink')
+pipe.add(sink)
+src.link(sink)
+
+bus = pipe.get_bus()
+bus.add_signal_watch()
+bus.connect('message', gstmessage_cb, pipe)
+
+pipe.set_state(gst.STATE_PLAYING)
+
+Gtk.main()
diff --git a/Adding_TTS_gtk3/gst_track_words.py b/Adding_TTS_gtk3/gst_track_words.py
new file mode 100755
index 0000000..86b99d2
--- /dev/null
+++ b/Adding_TTS_gtk3/gst_track_words.py
@@ -0,0 +1,50 @@
+# gst_track_words.py
+# Sample code for using the gstreamer espeak plugin.
+# Copyright (C) 2010 Aleksey Lim
+# This program 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 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+from gi.repository import Gtk
+import gst
+
+text = file(__file__, 'r').read()
+
+def gstmessage_cb(bus, message, pipe):
+ if message.type in (gst.MESSAGE_EOS, gst.MESSAGE_ERROR):
+ pipe.set_state(gst.STATE_NULL)
+ elif message.type == gst.MESSAGE_ELEMENT and \
+ message.structure.get_name() == 'espeak-word':
+ offset = message.structure['offset']
+ len = message.structure['len']
+ print text[offset:offset+len]
+
+pipe = gst.Pipeline('pipeline')
+
+src = gst.element_factory_make('espeak', 'src')
+src.props.text = text
+src.props.track = 1
+pipe.add(src)
+
+sink = gst.element_factory_make('autoaudiosink', 'sink')
+pipe.add(sink)
+src.link(sink)
+
+bus = pipe.get_bus()
+bus.add_signal_watch()
+bus.connect('message', gstmessage_cb, pipe)
+
+pipe.set_state(gst.STATE_PLAYING)
+
+Gtk.main()
diff --git a/Adding_TTS_gtk3/speech.py b/Adding_TTS_gtk3/speech.py
new file mode 100644
index 0000000..cdab7d2
--- /dev/null
+++ b/Adding_TTS_gtk3/speech.py
@@ -0,0 +1,136 @@
+# speech.py
+
+# Copyright (C) 2010 Aleksey Lim and James D. Simmons
+#
+# This program 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 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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 this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import gst
+
+voice = 'default'
+pitch = 0
+
+rate = -20
+highlight_cb = None
+
+def _create_pipe():
+ pipeline = 'espeak name=source ! autoaudiosink'
+ pipe = gst.parse_launch(pipeline)
+
+ def stop_cb(bus, message):
+ pipe.set_state(gst.STATE_NULL)
+
+ def mark_cb(bus, message):
+ if message.structure.get_name() == 'espeak-mark':
+ mark = message.structure['mark']
+ highlight_cb(int(mark))
+
+ bus = pipe.get_bus()
+ bus.add_signal_watch()
+ bus.connect('message::eos', stop_cb)
+ bus.connect('message::error', stop_cb)
+ bus.connect('message::element', mark_cb)
+
+ return (pipe.get_by_name('source'), pipe)
+
+def _speech(source, pipe, words):
+ source.props.pitch = pitch
+ source.props.rate = rate
+ source.props.voice = voice
+ source.props.text = words;
+ pipe.set_state(gst.STATE_PLAYING)
+
+info_source, info_pipe = _create_pipe()
+play_source, play_pipe = _create_pipe()
+
+# track for marks
+play_source.props.track = 2
+
+def voices():
+ return info_source.props.voices
+
+def say(words):
+ _speech(info_source, info_pipe, words)
+ print words
+
+def play(words):
+ _speech(play_source, play_pipe, words)
+
+def is_stopped():
+ for i in play_pipe.get_state():
+ if isinstance(i, gst.State) and i == gst.STATE_NULL:
+ return True
+ return False
+
+def stop():
+ play_pipe.set_state(gst.STATE_NULL)
+
+def is_paused():
+ for i in play_pipe.get_state():
+ if isinstance(i, gst.State) and i == gst.STATE_PAUSED:
+ return True
+ return False
+
+def pause():
+ play_pipe.set_state(gst.STATE_PAUSED)
+
+def rate_up():
+ global rate
+ rate = min(99, rate + 10)
+
+def rate_down():
+ global rate
+ rate = max(-99, rate - 10)
+
+def pitch_up():
+ global pitch
+ pitch = min(99, pitch + 10)
+
+def pitch_down():
+ global pitch
+ pitch = max(-99, pitch - 10)
+
+def prepare_highlighting(label_text):
+ i = 0
+ j = 0
+ word_begin = 0
+ word_end = 0
+ current_word = 0
+ word_tuples = []
+ omitted = [' ', '\n', u'\r', '_', '[', '{', ']', '}', '|', '<',\
+ '>', '*', '+', '/', '\\' ]
+ omitted_chars = set(omitted)
+ while i < len(label_text):
+ if label_text[i] not in omitted_chars:
+ word_begin = i
+ j = i
+ while j < len(label_text) and label_text[j] not in omitted_chars:
+ j = j + 1
+ word_end = j
+ i = j
+ word_t = (word_begin, word_end, label_text[word_begin: word_end].strip())
+ if word_t[2] != u'\r':
+ word_tuples.append(word_t)
+ i = i + 1
+ return word_tuples
+
+def add_word_marks(word_tuples):
+ "Adds a mark between each word of text."
+ i = 0
+ marked_up_text = '<speak> '
+ while i < len(word_tuples):
+ word_t = word_tuples[i]
+ marked_up_text = marked_up_text + '<mark name="' + str(i) + '"/>' + word_t[2]
+ i = i + 1
+ return marked_up_text + '</speak>'
diff --git a/Adding_TTS_gtk3/testtts.txt b/Adding_TTS_gtk3/testtts.txt
new file mode 100644
index 0000000..4057792
--- /dev/null
+++ b/Adding_TTS_gtk3/testtts.txt
@@ -0,0 +1,21 @@
+Jane Austen? Why I go so far as to say that any library is a good library that does not contain a volume by Jane
+Austen. Even if it contains no other book.
+
+- quoted in Remembered Yesterdays, Robert Underwood Johnson
+
+To me his prose is unreadable -- like Jane Austin's [sic]. No there is a difference. I could read his prose on salary, but
+not Jane's. Jane is entirely impossible. It seems a great pity that they allowed her to die a natural death.
+
+- Letter to W. D. Howells, 18 January 1909
+
+Jane Austen's books, too, are absent from this library. Just that one omission alone would make a
+fairly good library out of a library that hadn't a book in it.
+
+- Following the Equator
+
+I haven't any right to criticise books, and I don't do it except when I hate them. I often want to criticise
+Jane Austen, but her books madden me so that I can't conceal my frenzy from the reader; and therefore I have
+to stop every time I begin. Everytime I read 'Pride and Prejudice' I want to dig her up and beat her over
+the skull with her own shin-bone.
+
+- Letter to Joseph Twichell, 13 September 1898