Skip to content

Commit

Permalink
Release 0.6
Browse files Browse the repository at this point in the history
  • Loading branch information
muflone committed Jul 20, 2009
1 parent a0bba54 commit 6150472
Show file tree
Hide file tree
Showing 21 changed files with 1,691 additions and 653 deletions.
7 changes: 5 additions & 2 deletions DEBIAN/control
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
Package: gespeaker
Version: 0.5
Version: 0.6
Section: sound
Priority: optional
Architecture: all
Depends: espeak, espeak-data, python, python-gtk2, python-gobject, alsa-utils | pulseaudio-utils
Installed-Size: 280
Recommends: mbrola
Installed-Size: 320
Maintainer: Muflone Ubuntu Trucchi <[email protected]>
Description: A GTK+ frontend for the espeak system
Gespeaker is a GTK+ frontend for espeak. It allows to play
a text in many languages with settings for voice, pitch,
volume, speed and word gap.
Since version 0.6 it can use mbrola package and voices to
achieve a more realistic text reading experience.
2 changes: 2 additions & 0 deletions DialogFileOpenSave.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@ def __init__(self, useForOpen=True, title=None, initialDir=None, initialFile=Non
self.set_current_name(initialFile)
self.set_do_overwrite_confirmation(askOverwrite)
self.filename = None
self.lastFilter = None

def _response_callback(self, *args):
self.response = args[1]
if args[1] == gtk.RESPONSE_OK:
self.filename = self.get_filename()
self.lastFilter = self.get_filter()
self.destroy()

def show(self):
Expand Down
89 changes: 84 additions & 5 deletions EspeakFrontend.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

import SubprocessWrapper
import os
from gettext import gettext as _
from DialogSimpleMessages import ShowDialogError

class EspeakFrontend(object):
def __init__(self):
Expand Down Expand Up @@ -37,6 +39,7 @@ def play(self, cmdEspeak, cmdPlayer, fileToRecord=None):
"Play the command provided"
# If save to file has been requested add -w else --stdout
cmdEspeak += fileToRecord and ['-w', fileToRecord] or ['--stdout']

# Execute espeak and pipe it with player
print cmdEspeak, cmdPlayer.split()
procEspeak = SubprocessWrapper.Popen(cmdEspeak,
Expand All @@ -47,12 +50,55 @@ def play(self, cmdEspeak, cmdPlayer, fileToRecord=None):
procEspeak.wait()
procEspeak = SubprocessWrapper.Popen(['cat', fileToRecord],
stdout=SubprocessWrapper.PIPE)
procPlay = SubprocessWrapper.Popen(cmdPlayer.split(),
stdin=procEspeak.stdout,
stdout=SubprocessWrapper.PIPE,
stderr=SubprocessWrapper.PIPE)
self.__playAudio(procEspeak, cmdPlayer)

def playMbrola(self, cmdEspeak, cmdPlayer, cmdMbrola, fileToRecord=None):
"Play the command provided"
# If save to file has been requested add filename else -
if not fileToRecord:
fileToRecord = '/tmp/gespeaker.wav'
cmdMbrola += [fileToRecord]

# Execute espeak and pipe it with mbrola and then the player)
print cmdEspeak, cmdMbrola, cmdPlayer.split()
procEspeak = SubprocessWrapper.Popen(cmdEspeak,
stdout=SubprocessWrapper.PIPE)

try:
procMbrola = SubprocessWrapper.Popen(cmdMbrola,
stdin=procEspeak.stdout, stdout=SubprocessWrapper.PIPE)
except OSError, (errno, strerror):
# Error during communicate"
ShowDialogError(title=_('Audio testing'), showOk=True,
text=_('There was an error during the test for the audio player.\n\n'
'Error %s: %s') % (errno, strerror))
procMbrola = None

# Save to file has been requested so we have to wait for espeak end
# and to pipe filename content to the player
if procMbrola:
procMbrola.wait()
procMbrola = SubprocessWrapper.Popen(['cat', fileToRecord],
stdout=SubprocessWrapper.PIPE)
self.__playAudio(procMbrola, cmdPlayer)

def __playAudio(self, procFrom, cmdPlayer):
"Play audio with the player piping from a process"
try:
procPlay = SubprocessWrapper.Popen(cmdPlayer.split(),
stdin=procFrom.stdout,
stdout=SubprocessWrapper.PIPE,
stderr=SubprocessWrapper.PIPE)
except OSError, (errno, strerror):
# Error during communicate"
ShowDialogError(title=_('Audio testing'), showOk=True,
text=_('There was an error during the test for the audio player.\n\n'
'Error %s: %s') % (errno, strerror))
procPlay = None
# Save both processes espeak and player
self.procTalk = (procEspeak, procPlay)
if procPlay:
self.procTalk = (procFrom, procPlay)


def stop(self):
"Stop audio killing espeak and player"
Expand Down Expand Up @@ -104,3 +150,36 @@ def loadVariants(self, cmdEspeak):
else:
variantsM.append((f, varcontent[1][5:]))
return (variantsM, variantsF)

def loadMbrolaVoices(self, pathVoicesmb):
"Load mbrola languages list"
voicesmb = []
pathVoices = '/usr/share/espeak-data/voices/mb/'
if not pathVoicesmb:
pathVoicesmb = '/usr/share/mbrola/voices'
if os.path.isdir(pathVoices) and os.path.isdir(pathVoicesmb):
for voice in os.listdir(pathVoices):
# Only files
if os.path.isfile(os.path.join(pathVoices, voice)):
voicefile = open(os.path.join(pathVoices, voice), mode='r')
voicecontent = voicefile.read().split('\n')
voicefile.close()
# Check if it's a valid voice
for line in voicecontent:
if line[:5] == 'name ':
voicesmb.append((line[5:], voice, os.path.isfile(os.path.join(pathVoicesmb, voice[3:]))))
break
return voicesmb

def mbrolaExists(self, cmdMbrola):
"Return mbrola existance"
try:
# Try to call mbrola executable
mbrola = SubprocessWrapper.Popen(cmdMbrola,
stdout=SubprocessWrapper.PIPE, stderr=SubprocessWrapper.PIPE)
mbrola.communicate()
status = True
except:
# Error during communicate"
status = False
return status
86 changes: 80 additions & 6 deletions PreferencesWindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,20 @@
from DialogSimpleMessages import ShowDialogError
from pygtkutils import *

def showPreferencesWindow(gladeFile, iconLogo):
prefsWindow = PreferencesWindow(gladeFile, iconLogo)
def showPreferencesWindow(gladeFile, espeak):
prefsWindow = PreferencesWindow(gladeFile, espeak)

class PreferencesWindow(object):
def __init__(self, gladeFile, iconLogo):
def __init__(self, gladeFile, espeak):
self.gladeFile = gladeFile
self.espeak = espeak
self.loadControls()
self.dlgPrefs.set_icon_from_file(iconLogo)
self.dlgPrefs.set_icon_from_file(Settings.iconLogo)
signals = {
'on_cboPlayer_changed': self.on_cboPlayer_changed,
'on_btnPlayerTest_clicked': self.on_btnPlayerTest_clicked,
'on_chkCustomWelcome_toggled': self.on_chkCustomWelcome_toggled,
'on_btnRefresh_clicked': self.on_btnRefresh_clicked,
'on_btnOk_clicked': self.on_btnOk_clicked
}
self.gladeFile.signal_autoconnect(signals)
Expand All @@ -32,10 +34,13 @@ def __init__(self, gladeFile, iconLogo):
self.chkSingleRecord.set_active(Settings.get('SingleRecord') == True)
self.chkWordWrap.set_active(Settings.get('WordWrap') == True)
self.chkLoadVariants.set_active(Settings.get('LoadVariants') == True)
self.chooserLanguagePath.set_current_folder(Settings.get('VoicesmbPath'))
# Before to use the window property the realize method must be called
self.dlgPrefs.realize()
# Change WM buttons making the window only movable with the closing button
self.dlgPrefs.window.set_functions(gtk.gdk.FUNC_CLOSE | gtk.gdk.FUNC_MOVE)
# Reload mbrola languages list
self.btnRefresh.clicked()
self.dlgPrefs.run()
self.dlgPrefs.destroy()

Expand All @@ -57,7 +62,13 @@ def separator_filter(model, iter, data=None):
self.chkSingleRecord = gw('chkRecordSingleTrack')
self.chkWordWrap = gw('chkWordWrap')
self.chkLoadVariants = gw('chkLoadVariants')
self.tvwLanguages = gw('tvwLanguages')
self.chooserLanguagePath = gw('chooserLanguagePath')
self.btnRefresh = gw('btnRefresh')
self.btnOk = gw('btnOk')
self.imgExecutableMbrola = gw('imgExecutableMbrola')
self.lblExecutableMbrolaStatus = gw('lblExecutableMbrolaStatus')
self.lblLanguagesDetected = gw('lblLanguagesDetected')
# Prepare model for players combo
listStore = gtk.ListStore(gtk.gdk.Pixbuf, str, bool)
self.cboPlayer.set_model(listStore)
Expand All @@ -81,6 +92,42 @@ def separator_filter(model, iter, data=None):
listStore.append([None, _('Custom sound application'), False])
# Change testing button caption
Button_change_stock_description(self.btnPlayerTest, _('_Test'), True)
# Create model and sorted model for mbrola languages
self.treeModel = gtk.ListStore(gtk.gdk.Pixbuf, str, str, str)
#self.tvwLanguages.set_model(self.treeModel)
sortedModel = gtk.TreeModelSort(self.treeModel)
sortedModel.set_sort_column_id(1, gtk.SORT_ASCENDING)
self.tvwLanguages.set_model(sortedModel)
# Create columns for tvwLanguages
COL_IMG, COL_LANG, COL_RES, COL_STATUS = range(4)
cell = gtk.CellRendererPixbuf()
column = gtk.TreeViewColumn('')
column.pack_start(cell)
column.set_attributes(cell, pixbuf=COL_IMG)
self.tvwLanguages.append_column(column)

cell = gtk.CellRendererText()
column = gtk.TreeViewColumn(_('Language'), cell, text=COL_LANG)
column.set_sort_column_id(COL_LANG)
column.set_resizable(True)
self.tvwLanguages.append_column(column)

cell = gtk.CellRendererText()
column = gtk.TreeViewColumn(_('Resource'), cell, text=COL_RES)
column.set_sort_column_id(COL_RES)
column.set_resizable(True)
self.tvwLanguages.append_column(column)

cell = gtk.CellRendererText()
column = gtk.TreeViewColumn(_('Status'), cell, text=COL_STATUS)
column.set_sort_column_id(COL_STATUS)
column.set_resizable(True)
self.tvwLanguages.append_column(column)
# Order by Language column
#column = self.tvwLanguages.get_column(COL_LANG)
#column.set_sort_column_id(COL_LANG)
#column.set_sort_order(gtk.SORT_ASCENDING)
#column.set_sort_indicator(True)

def on_chkCustomWelcome_toggled(self, widget, data=None):
self.lblCustomWelcome.set_sensitive(self.chkCustomWelcome.get_active())
Expand All @@ -98,6 +145,7 @@ def on_btnOk_clicked(self, widget, data=None):
Settings.set('SingleRecord', self.chkSingleRecord.get_active())
Settings.set('WordWrap', self.chkWordWrap.get_active())
Settings.set('LoadVariants', self.chkLoadVariants.get_active())
Settings.set('VoicesmbPath', self.chooserLanguagePath.get_filename())

def on_cboPlayer_changed(self, widget, data=None):
"Enable and disable controls if custom command is not set"
Expand Down Expand Up @@ -127,10 +175,36 @@ def on_btnPlayerTest_clicked(self, widget, data=None):
except OSError, (errno, strerror):
# Error during communicate"
ShowDialogError(title=_('Audio testing'), showOk=True,
text=_('There was an error during the test for the audio player.\n'
'Error %s: %s' % (errno, strerror)))
text=_('There was an error during the test for the audio player.\n\n'
'Error %s: %s') % (errno, strerror))
# Terminate test if it's still running, follows a broken pipe error
if test.poll() is None:
test.terminate()
# Restore default cursor
Window_change_cursor(self.dlgPrefs.window, None, False)

def on_btnRefresh_clicked(self, widget, data=None):
"Reload mbrola languages from the selected folder"
self.treeModel.clear()
selectedFolder = self.chooserLanguagePath.get_filename()
if not selectedFolder:
# Calling before the dialog is shown results in None path
selectedFolder = Settings.get('VoicesmbPath')
mbrolaVoices = self.espeak.loadMbrolaVoices(selectedFolder)
voicesFound = 0
for voice in mbrolaVoices:
if voice[2]:
voicesFound += 1
self.treeModel.append((
widget.render_icon(voice[2] and gtk.STOCK_YES or gtk.STOCK_NO,
gtk.ICON_SIZE_BUTTON), voice[0], voice[1],
voice[2] and _('Installed') or _('Not installed')))
# lblLanguagesDetected
self.lblLanguagesDetected.set_text(_("%d languages of %d detected") % (
voicesFound, len(mbrolaVoices)))
# Check if mbrola exists
status = self.espeak.mbrolaExists(Settings.cmdMbrola)
self.imgExecutableMbrola.set_from_stock(size=gtk.ICON_SIZE_BUTTON,
stock_id=status and gtk.STOCK_YES or gtk.STOCK_NO)
self.lblExecutableMbrolaStatus.set_label('<b>%s</b>' % (status and
_('Package mbrola installed') or _('Package mbrola not installed')))
10 changes: 9 additions & 1 deletion Settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,19 @@
import os
from gettext import gettext as _

cmdEspeak = '/usr/bin/espeak'
argsEspeak = '-a %v -p %p -s %s -g %d -v %l -f %f'
iconLogo = 'gespeaker.svg'
cmdMbrola = '/usr/bin/mbrola'
argsMbrola = '-e %l -'

config = None
confdir = os.path.join(os.path.expanduser('~/.gespeaker'))
conffile = os.path.join(confdir, 'settings.conf')
__sectionSettings = 'settings'
__sectionWindowSize = 'window size'
__sectionVoiceSetting = 'voice settings'
__sectionMbrola = 'mbrola'
__defSettings = None

if not os.path.exists(confdir):
Expand Down Expand Up @@ -50,7 +57,8 @@ def loadDefaults():
'VoiceSpeed': [int, 170, __sectionVoiceSetting],
'VoiceDelay': [int, 10, __sectionVoiceSetting],
'VoiceTypeMale': [strbool, True, __sectionVoiceSetting],
'VoiceLanguage': [int, -1, __sectionVoiceSetting]
'VoiceLanguage': [str, _('default language'), __sectionVoiceSetting],
'VoicesmbPath': [str, '/usr/share/mbrola/voices', __sectionMbrola]
}

def save(filename=conffile, clearDefaults=False):
Expand Down
49 changes: 33 additions & 16 deletions doc/changelog
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
gespeaker (0.6) all; urgency=low

* Fixed audio testing localized string.
* An error message is now shown if the audio player is not found instead of
quietly ignore the error.
* New spanish translation provided by David Prieto.
* New mbrola support with more realistic voices.
* Added mbrola voices to languages list.
* Tabbed preferences dialog for new mbrola support.
* Moved language, voice type and variants to base settings and pitch, volume,
speed and gap sliders to advanced settings upon suggestion of frandavid100.
* Added automatic txt extension on saving text file.
* Added automatic wav extension on saving WAVE file. This was causing weird
noises on playing the recorded track if it wasn't a .wav filename.

-- Muflone <[email protected]> Sat, 18 Jul 2009 11:42:00 +0100

gespeaker (0.5) all; urgency=low

* Added an exteder separator for settings to allow maximum usage of the
Expand All @@ -8,26 +25,26 @@ gespeaker (0.5) all; urgency=low
* Added preferences dialog.
* Added preferences save and reload for welcome message, window size,
voice settings and expander status.
* Added support for audio frontend: ALSA (aplay), PulseAudio (paplay)
and user customized player command, with audio command test.
* Added voice variants by scanning /usr/share/espeak-data/voices/!v
folder for extra voice variants.
* Added support for audio frontend: ALSA (aplay), PulseAudio (paplay) and
user customized player command, with audio command test.
* Added voice variants by scanning /usr/share/espeak-data/voices/!v folder
for extra voice variants.
* Fixed stock icon for DialogFileOpenSave.

-- Muflone <[email protected]> Fri, 30 Jun 2009 18:48:50 +0100

gespeaker (0.4) all; urgency=medium

* Added SubprocessWrapper.Popen to wrap subprocess.Popen in order to
support python versions prior to 2.6 which don't have the delete
argument on object creation.
* Added TempfileWrapper.NamedTemporaryFile to wrap tempfile's Popen
object in order to support python versions prior to 2.6 which don't
have terminate and send_signal methods.
* Added SubprocessWrapper.Popen to wrap subprocess.Popen in order to support
python versions prior to 2.6 which don't have the delete argument on object
creation.
* Added TempfileWrapper.NamedTemporaryFile to wrap tempfile's Popen object in
order to support python versions prior to 2.6 which don't have terminate
and send_signal methods.
Actually no more used, left for future usage.
* Now gespeaker works with python version 2.4 and higher.
* Temporary file for output to speech is created at program start so
new temporary files are no longer created after each play.
* Temporary file for output to speech is created at program start so new
temporary files are no longer created after each play.
* Included pause and resume features.
* New icon and logo, kindly provided by MIX.
* New french translation provided by Emmanuel.
Expand All @@ -37,11 +54,11 @@ gespeaker (0.4) all; urgency=medium
gespeaker (0.3) all; urgency=low

* Added support for voice type (male/female) via +12 for female voice.
* Removed escaped text substitution with a more secure temporary file
with the text to play.
* Removed escaped text substitution with a more secure temporary file with
the text to play.
* Substituted direct shell piping with more secure subprocess' piping.
* Better control of external calls, now both espeak and player execution
are polled for exitcode and terminated if requested.
* Better control of external calls, now both espeak and player execution are
polled for exitcode and terminated if requested.
* Added documentation and artists parameters to DialogAbout.

-- Muflone <[email protected]> Thu, 18 Jun 2009 01:21:30 +0100
Expand Down
Loading

0 comments on commit 6150472

Please sign in to comment.