diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rwxr-xr-x | Blather.py | 448 | ||||
-rw-r--r-- | GtkTrayUI.py | 194 | ||||
-rw-r--r-- | GtkUI.py | 214 | ||||
-rw-r--r-- | README.md | 59 | ||||
-rwxr-xr-x | Recognizer.py | 102 |
6 files changed, 513 insertions, 505 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/Blather.py b/Blather.py index ed5e6ed..1f59ee0 100755 --- a/Blather.py +++ b/Blather.py @@ -5,16 +5,16 @@ import sys import signal -import gobject +from gi.repository import GObject import os.path import subprocess from optparse import OptionParser try: - import yaml + import yaml except: - print "YAML is not supported. ~/.config/blather/options.yaml will not function" + print "YAML is not supported. ~/.config/blather/options.yaml will not function" -#where are the files? +# Where are the files? conf_dir = os.path.expanduser("~/.config/blather") lang_dir = os.path.join(conf_dir, "language") command_file = os.path.join(conf_dir, "commands.conf") @@ -23,230 +23,230 @@ history_file = os.path.join(conf_dir, "blather.history") opt_file = os.path.join(conf_dir, "options.yaml") lang_file = os.path.join(lang_dir,'lm') dic_file = os.path.join(lang_dir,'dic') -#make the lang_dir if it doesn't exist +# Make the lang_dir if it doesn't exist if not os.path.exists(lang_dir): - os.makedirs(lang_dir) + os.makedirs(lang_dir) class Blather: - def __init__(self, opts): - - #import the recognizer so Gst doesn't clobber our -h - from Recognizer import Recognizer - self.ui = None - self.options = {} - ui_continuous_listen = False - self.continuous_listen = False - - self.commands = {} - - #read the commands - self.read_commands() - - #load the options file - self.load_options() - - #merge the opts - for k,v in opts.__dict__.items(): - if (not k in self.options) or opts.override: - self.options[k] = v - - if self.options['interface'] != None: - if self.options['interface'] == "q": - from QtUI import UI - elif self.options['interface'] == "g": - from GtkUI import UI - elif self.options['interface'] == "gt": - from GtkTrayUI import UI - else: - print "no GUI defined" - sys.exit() - - self.ui = UI(args, self.options['continuous']) - self.ui.connect("command", self.process_command) - #can we load the icon resource? - icon = self.load_resource("icon.png") - if icon: - self.ui.set_icon_active_asset(icon) - #can we load the icon_inactive resource? - icon_inactive = self.load_resource("icon_inactive.png") - if icon_inactive: - self.ui.set_icon_inactive_asset(icon_inactive) - - if self.options['history']: - self.history = [] - - #create the recognizer - try: - self.recognizer = Recognizer(lang_file, dic_file, self.options['microphone'] ) - except Exception, e: - #no recognizer? bummer - sys.exit() - - self.recognizer.connect('finished',self.recognizer_finished) - - print "Using Options: ", self.options - - def read_commands(self): - #read the.commands file - file_lines = open(command_file) - strings = open(strings_file, "w") - for line in file_lines: - print line - #trim the white spaces - line = line.strip() - #if the line has length and the first char isn't a hash - if len(line) and line[0]!="#": - #this is a parsible line - (key,value) = line.split(":",1) - print key, value - self.commands[key.strip().lower()] = value.strip() - strings.write( key.strip()+"\n") - #close the strings file - strings.close() - - def load_options(self): - #is there an opt file? - try: - opt_fh = open(opt_file) - text = opt_fh.read() - self.options = yaml.load(text) - except: - pass - - - def log_history(self,text): - if self.options['history']: - self.history.append(text) - if len(self.history) > self.options['history']: - #pop off the first item - self.history.pop(0) - - #open and truncate the blather history file - hfile = open(history_file, "w") - for line in self.history: - hfile.write( line+"\n") - #close the file - hfile.close() - - # Print the cmd and then run the command - def run_command(self, cmd): - print cmd - subprocess.call(cmd, shell=True) - - def recognizer_finished(self, recognizer, text): - t = text.lower() - #is there a matching command? - if self.commands.has_key( t ): - #run the valid_sentence_command if there is a valid sentence command - if self.options['valid_sentence_command']: - subprocess.call(self.options['valid_sentence_command'], shell=True) - cmd = self.commands[t] - #should we be passing words? - if self.options['pass_words']: - cmd+=" "+t - self.run_command(cmd) - else: - self.run_command(cmd) - self.log_history(text) - else: - #run the invalid_sentence_command if there is a valid sentence command - if self.options['invalid_sentence_command']: - subprocess.call(self.options['invalid_sentence_command'], shell=True) - print "no matching command %s" %(t) - #if there is a UI and we are not continuous listen - if self.ui: - if not self.continuous_listen: - #stop listening - self.recognizer.pause() - #let the UI know that there is a finish - self.ui.finished(t) - - def run(self): - if self.ui: - self.ui.run() - else: - blather.recognizer.listen() - - def quit(self): - sys.exit() - - def process_command(self, UI, command): - print command - if command == "listen": - self.recognizer.listen() - elif command == "stop": - self.recognizer.pause() - elif command == "continuous_listen": - self.continuous_listen = True - self.recognizer.listen() - elif command == "continuous_stop": - self.continuous_listen = False - self.recognizer.pause() - elif command == "quit": - self.quit() - - def load_resource(self,string): - local_data = os.path.join(os.path.dirname(__file__), 'data') - paths = ["/usr/share/blather/","/usr/local/share/blather", local_data] - for path in paths: - resource = os.path.join(path, string) - if os.path.exists( resource ): - return resource - #if we get this far, no resource was found - return False + + def __init__(self, opts): + # Import the recognizer so Gst doesn't clobber our -h + from Recognizer import Recognizer + self.ui = None + self.options = {} + ui_continuous_listen = False + self.continuous_listen = False + + self.commands = {} + + # Read the commands + self.read_commands() + + # Load the options file + self.load_options() + + # Merge the opts + for k,v in opts.__dict__.items(): + if (not k in self.options) or opts.override: + self.options[k] = v + + if self.options['interface'] != None: + if self.options['interface'] == "q": + from QtUI import UI + elif self.options['interface'] == "g": + from GtkUI import UI + elif self.options['interface'] == "gt": + from GtkTrayUI import UI + else: + print "no GUI defined" + sys.exit() + + self.ui = UI(args, self.options['continuous']) + self.ui.connect("command", self.process_command) + #can we load the icon resource? + icon = self.load_resource("icon.png") + if icon: + self.ui.set_icon_active_asset(icon) + #can we load the icon_inactive resource? + icon_inactive = self.load_resource("icon_inactive.png") + if icon_inactive: + self.ui.set_icon_inactive_asset(icon_inactive) + + if self.options['history']: + self.history = [] + + # Create the recognizer + try: + self.recognizer = Recognizer(lang_file, dic_file, self.options['microphone']) + except Exception, e: + #no recognizer? bummer + print 'error making recognizer' + sys.exit() + + self.recognizer.connect('finished', self.recognizer_finished) + + print "Using Options: ", self.options + + def read_commands(self): + # Read the commands file + file_lines = open(command_file) + strings = open(strings_file, "w") + for line in file_lines: + print line + # Trim the white spaces + line = line.strip() + # If the line has length and the first char isn't a hash + if len(line) and line[0]!="#": + # This is a parsible line + (key,value) = line.split(":",1) + print key, value + self.commands[key.strip().lower()] = value.strip() + strings.write( key.strip()+"\n") + # Close the strings file + strings.close() + + def load_options(self): + # Is there an opt file? + try: + opt_fh = open(opt_file) + text = opt_fh.read() + self.options = yaml.load(text) + except: + pass + + + def log_history(self,text): + if self.options['history']: + self.history.append(text) + if len(self.history) > self.options['history']: + # Pop off the first item + self.history.pop(0) + + # Open and truncate the blather history file + hfile = open(history_file, "w") + for line in self.history: + hfile.write( line+"\n") + # Close the file + hfile.close() + + def run_command(self, cmd): + '''Print the command, then run it''' + print cmd + subprocess.call(cmd, shell=True) + + def recognizer_finished(self, recognizer, text): + t = text.lower() + # Is there a matching command? + if self.commands.has_key( t ): + # Run the valid_sentence_command if there is a valid sentence command + if self.options['valid_sentence_command']: + subprocess.call(self.options['valid_sentence_command'], shell=True) + cmd = self.commands[t] + # Should we be passing words? + if self.options['pass_words']: + cmd += " " + t + self.run_command(cmd) + else: + self.run_command(cmd) + self.log_history(text) + else: + # Run the invalid_sentence_command if there is an invalid sentence command + if self.options['invalid_sentence_command']: + subprocess.call(self.options['invalid_sentence_command'], shell=True) + print "no matching command %s" % t + # If there is a UI and we are not continuous listen + if self.ui: + if not self.continuous_listen: + # Stop listening + self.recognizer.pause() + # Let the UI know that there is a finish + self.ui.finished(t) + + def run(self): + if self.ui: + self.ui.run() + else: + blather.recognizer.listen() + + def quit(self): + sys.exit() + + def process_command(self, UI, command): + print command + if command == "listen": + self.recognizer.listen() + elif command == "stop": + self.recognizer.pause() + elif command == "continuous_listen": + self.continuous_listen = True + self.recognizer.listen() + elif command == "continuous_stop": + self.continuous_listen = False + self.recognizer.pause() + elif command == "quit": + self.quit() + + def load_resource(self,string): + local_data = os.path.join(os.path.dirname(__file__), 'data') + paths = ["/usr/share/blather/","/usr/local/share/blather", local_data] + for path in paths: + resource = os.path.join(path, string) + if os.path.exists( resource ): + return resource + # If we get this far, no resource was found + return False if __name__ == "__main__": - parser = OptionParser() - parser.add_option("-i", "--interface", type="string", dest="interface", - action='store', - help="Interface to use (if any). 'q' for Qt, 'g' for GTK, 'gt' for GTK system tray icon") - - parser.add_option("-c", "--continuous", - action="store_true", dest="continuous", default=False, - help="starts interface with 'continuous' listen enabled") - - parser.add_option("-p", "--pass-words", - action="store_true", dest="pass_words", default=False, - help="passes the recognized words as arguments to the shell command") - - parser.add_option("-o", "--override", - action="store_true", dest="override", default=False, - help="override config file with command line options") - - parser.add_option("-H", "--history", type="int", - action="store", dest="history", - help="number of commands to store in history file") - - parser.add_option("-m", "--microphone", type="int", - action="store", dest="microphone", default=None, - help="Audio input card to use (if other than system default)") - - parser.add_option("--valid-sentence-command", type="string", dest="valid_sentence_command", - action='store', - help="command to run when a valid sentence is detected") - - parser.add_option( "--invalid-sentence-command", type="string", dest="invalid_sentence_command", - action='store', - help="command to run when an invalid sentence is detected") - - (options, args) = parser.parse_args() - #make our blather object - blather = Blather(options) - #init gobject threads - gobject.threads_init() - #we want a main loop - main_loop = gobject.MainLoop() - #handle sigint - signal.signal(signal.SIGINT, signal.SIG_DFL) - #run the blather - blather.run() - #start the main loop - - try: - main_loop.run() - except: - print "time to quit" - main_loop.quit() - sys.exit() + parser = OptionParser() + parser.add_option("-i", "--interface", type="string", dest="interface", + action='store', + help="Interface to use (if any). 'q' for Qt, 'g' for GTK, 'gt' for GTK system tray icon") + + parser.add_option("-c", "--continuous", + action="store_true", dest="continuous", default=False, + help="starts interface with 'continuous' listen enabled") + + parser.add_option("-p", "--pass-words", + action="store_true", dest="pass_words", default=False, + help="passes the recognized words as arguments to the shell command") + + parser.add_option("-o", "--override", + action="store_true", dest="override", default=False, + help="override config file with command line options") + + parser.add_option("-H", "--history", type="int", + action="store", dest="history", + help="number of commands to store in history file") + + parser.add_option("-m", "--microphone", type="int", + action="store", dest="microphone", default=None, + help="Audio input card to use (if other than system default)") + + parser.add_option("--valid-sentence-command", type="string", dest="valid_sentence_command", + action='store', + help="command to run when a valid sentence is detected") + + parser.add_option( "--invalid-sentence-command", type="string", dest="invalid_sentence_command", + action='store', + help="command to run when an invalid sentence is detected") + + (options, args) = parser.parse_args() + # Make our blather object + blather = Blather(options) + # Init gobject threads + GObject.threads_init() + # We want a main loop + main_loop = GObject.MainLoop() + # Handle sigint + signal.signal(signal.SIGINT, signal.SIG_DFL) + # Run the blather + blather.run() + # Start the main loop + try: + main_loop.run() + except: + print "time to quit" + main_loop.quit() + sys.exit() diff --git a/GtkTrayUI.py b/GtkTrayUI.py index f0d1c2e..dda153d 100644 --- a/GtkTrayUI.py +++ b/GtkTrayUI.py @@ -1,99 +1,97 @@ import sys -import gobject - -import pygtk -import gtk - -class UI(gobject.GObject): - __gsignals__ = { - 'command' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING,)) - } - - def __init__(self,args, continuous): - gobject.GObject.__init__(self) - self.continuous = continuous - - self.statusicon = gtk.StatusIcon() - self.statusicon.set_title("Blather") - self.statusicon.set_name("Blather") - self.statusicon.set_tooltip_text("Blather - Idle") - self.statusicon.set_has_tooltip(True) - self.statusicon.connect("activate", self.continuous_toggle) - self.statusicon.connect("popup-menu", self.popup_menu) - - self.menu = gtk.Menu() - self.menu_listen = gtk.MenuItem('Listen') - self.menu_continuous = gtk.CheckMenuItem('Continuous') - self.menu_quit = gtk.MenuItem('Quit') - self.menu.append(self.menu_listen) - self.menu.append(self.menu_continuous) - self.menu.append(self.menu_quit) - self.menu_listen.connect("activate", self.toggle_listen) - self.menu_continuous.connect("toggled", self.toggle_continuous) - self.menu_quit.connect("activate", self.quit) - self.menu.show_all() - - def continuous_toggle(self, item): - checked = self.menu_continuous.get_active() - self.menu_continuous.set_active(not checked) - - def toggle_continuous(self, item): - checked = self.menu_continuous.get_active() - self.menu_listen.set_sensitive(not checked) - if checked: - self.menu_listen.set_label("Listen") - self.emit('command', "continuous_listen") - self.statusicon.set_tooltip_text("Blather - Listening") - self.set_icon_active() - else: - self.set_icon_inactive() - self.statusicon.set_tooltip_text("Blather - Idle") - self.emit('command', "continuous_stop") - - def toggle_listen(self, item): - val = self.menu_listen.get_label() - if val == "Listen": - self.emit("command", "listen") - self.menu_listen.set_label("Stop") - self.statusicon.set_tooltip_text("Blather - Listening") - else: - self.icon_inactive() - self.menu_listen.set_label("Listen") - self.emit("command", "stop") - self.statusicon.set_tooltip_text("Blather - Idle") - - def popup_menu(self, item, button, time): - self.menu.popup(None, None, gtk.status_icon_position_menu, button, time, item) - - def run(self): - #set the icon - self.set_icon_inactive() - if self.continuous: - self.menu_continuous.set_active(True) - self.set_icon_active() - else: - self.menu_continuous.set_active(False) - self.statusicon.set_visible(True) - - def quit(self, item): - self.statusicon.set_visible(False) - self.emit("command", "quit") - - def finished(self, text): - if not self.menu_continuous.get_active(): - self.menu_listen.set_label("Listen") - self.statusicon.set_from_icon_name("blather_stopped") - self.statusicon.set_tooltip_text("Blather - Idle") - - def set_icon_active_asset(self, i): - self.icon_active = i - - def set_icon_inactive_asset(self, i): - self.icon_inactive = i - - def set_icon_active(self): - self.statusicon.set_from_file( self.icon_active ) - - def set_icon_inactive(self): - self.statusicon.set_from_file( self.icon_inactive ) - +from gi.repository import GObject +# Gtk +from gi.repository import Gtk, Gdk + +class UI(GObject.GObject): + __gsignals__ = { + 'command' : (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, (GObject.TYPE_STRING,)) + } + + def __init__(self, args, continuous): + GObject.GObject.__init__(self) + self.continuous = continuous + + self.statusicon = Gtk.StatusIcon() + self.statusicon.set_title("Blather") + self.statusicon.set_name("Blather") + self.statusicon.set_tooltip_text("Blather - Idle") + self.statusicon.set_has_tooltip(True) + self.statusicon.connect("activate", self.continuous_toggle) + self.statusicon.connect("popup-menu", self.popup_menu) + + self.menu = Gtk.Menu() + self.menu_listen = Gtk.MenuItem('Listen') + self.menu_continuous = Gtk.CheckMenuItem('Continuous') + self.menu_quit = Gtk.MenuItem('Quit') + self.menu.append(self.menu_listen) + self.menu.append(self.menu_continuous) + self.menu.append(self.menu_quit) + self.menu_listen.connect("activate", self.toggle_listen) + self.menu_continuous.connect("toggled", self.toggle_continuous) + self.menu_quit.connect("activate", self.quit) + self.menu.show_all() + + def continuous_toggle(self, item): + checked = self.menu_continuous.get_active() + self.menu_continuous.set_active(not checked) + + def toggle_continuous(self, item): + checked = self.menu_continuous.get_active() + self.menu_listen.set_sensitive(not checked) + if checked: + self.menu_listen.set_label("Listen") + self.emit('command', "continuous_listen") + self.statusicon.set_tooltip_text("Blather - Listening") + self.set_icon_active() + else: + self.set_icon_inactive() + self.statusicon.set_tooltip_text("Blather - Idle") + self.emit('command', "continuous_stop") + + def toggle_listen(self, item): + val = self.menu_listen.get_label() + if val == "Listen": + self.emit("command", "listen") + self.menu_listen.set_label("Stop") + self.statusicon.set_tooltip_text("Blather - Listening") + else: + self.icon_inactive() + self.menu_listen.set_label("Listen") + self.emit("command", "stop") + self.statusicon.set_tooltip_text("Blather - Idle") + + def popup_menu(self, item, button, time): + self.menu.popup(None, None, Gtk.StatusIcon.position_menu, item, button, time) + + def run(self): + # Set the icon + self.set_icon_inactive() + if self.continuous: + self.menu_continuous.set_active(True) + self.set_icon_active() + else: + self.menu_continuous.set_active(False) + self.statusicon.set_visible(True) + + def quit(self, item): + self.statusicon.set_visible(False) + self.emit("command", "quit") + + def finished(self, text): + if not self.menu_continuous.get_active(): + self.menu_listen.set_label("Listen") + self.statusicon.set_from_icon_name("blather_stopped") + self.statusicon.set_tooltip_text("Blather - Idle") + + def set_icon_active_asset(self, i): + self.icon_active = i + + def set_icon_inactive_asset(self, i): + self.icon_inactive = i + + def set_icon_active(self): + self.statusicon.set_from_file(self.icon_active) + + def set_icon_inactive(self): + self.statusicon.set_from_file(self.icon_inactive) diff --git a/GtkUI.py b/GtkUI.py index 17c7b50..2e0fc17 100644 --- a/GtkUI.py +++ b/GtkUI.py @@ -1,111 +1,109 @@ -#This is part of Blather +# This is part of Blather # -- this code is licensed GPLv3 # Copyright 2013 Jezra import sys -import gobject -#Gtk -import pygtk -import gtk - -class UI(gobject.GObject): - __gsignals__ = { - 'command' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING,)) - } - - def __init__(self,args, continuous): - gobject.GObject.__init__(self) - self.continuous = continuous - #make a window - self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) - self.window.connect("delete_event", self.delete_event) - #give the window a name - self.window.set_title("BlatherGtk") - self.window.set_resizable(False) - - layout = gtk.VBox() - self.window.add(layout) - #make a listen/stop button - self.lsbutton = gtk.Button("Listen") - layout.add(self.lsbutton) - #make a continuous button - self.ccheckbox = gtk.CheckButton("Continuous Listen") - layout.add(self.ccheckbox) - - #connect the buttons - self.lsbutton.connect("clicked",self.lsbutton_clicked) - self.ccheckbox.connect("clicked",self.ccheckbox_clicked) - - #add a label to the UI to display the last command - self.label = gtk.Label() - layout.add(self.label) - - #create an accellerator group for this window - accel = gtk.AccelGroup() - #add the ctrl+q to quit - accel.connect_group(gtk.keysyms.q, gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE, self.accel_quit ) - #lock the group - accel.lock() - #add the group to the window - self.window.add_accel_group(accel) - - def ccheckbox_clicked(self, widget): - checked = self.ccheckbox.get_active() - self.lsbutton.set_sensitive(not checked) - if checked: - self.lsbutton_stopped() - self.emit('command', "continuous_listen") - self.set_icon_active() - else: - self.emit('command', "continuous_stop") - self.set_icon_inactive() - - def lsbutton_stopped(self): - self.lsbutton.set_label("Listen") - - def lsbutton_clicked(self, button): - val = self.lsbutton.get_label() - if val == "Listen": - self.emit("command", "listen") - self.lsbutton.set_label("Stop") - #clear the label - self.label.set_text("") - self.set_icon_active() - else: - self.lsbutton_stopped() - self.emit("command", "stop") - self.set_icon_inactive() - - def run(self): - #set the default icon - self.set_icon_inactive() - self.window.show_all() - if self.continuous: - self.set_icon_active() - self.ccheckbox.set_active(True) - - def accel_quit(self, accel_group, acceleratable, keyval, modifier): - self.emit("command", "quit") - - def delete_event(self, x, y ): - self.emit("command", "quit") - - def finished(self, text): - #if the continuous isn't pressed - if not self.ccheckbox.get_active(): - self.lsbutton_stopped() - self.set_icon_inactive() - self.label.set_text(text) - - def set_icon_active_asset(self, i): - self.icon_active = i - - def set_icon_inactive_asset(self, i): - self.icon_inactive = i - - def set_icon_active(self): - gtk.window_set_default_icon_from_file(self.icon_active) - - def set_icon_inactive(self): - gtk.window_set_default_icon_from_file(self.icon_inactive) - - +from gi.repository import GObject +# Gtk +from gi.repository import Gtk, Gdk + +class UI(GObject.GObject): + __gsignals__ = { + 'command' : (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, (GObject.TYPE_STRING,)) + } + + def __init__(self,args, continuous): + GObject.GObject.__init__(self) + self.continuous = continuous + # Make a window + self.window = Gtk.Window(Gtk.WindowType.TOPLEVEL) + self.window.connect("delete_event", self.delete_event) + # Give the window a name + self.window.set_title("BlatherGtk") + self.window.set_resizable(False) + + layout = Gtk.VBox() + self.window.add(layout) + # Make a listen/stop button + self.lsbutton = Gtk.Button("Listen") + layout.add(self.lsbutton) + # Make a continuous button + self.ccheckbox = Gtk.CheckButton("Continuous Listen") + layout.add(self.ccheckbox) + + # Connect the buttons + self.lsbutton.connect("clicked",self.lsbutton_clicked) + self.ccheckbox.connect("clicked",self.ccheckbox_clicked) + + # Add a label to the UI to display the last command + self.label = Gtk.Label() + layout.add(self.label) + + # Create an accellerator group for this window + accel = Gtk.AccelGroup() + # Add the ctrl+q to quit + accel.connect(Gdk.keyval_from_name('q'), Gdk.ModifierType.CONTROL_MASK, + Gtk.AccelFlags.VISIBLE, self.accel_quit) + # Lock the group + accel.lock() + # Add the group to the window + self.window.add_accel_group(accel) + + def ccheckbox_clicked(self, widget): + checked = self.ccheckbox.get_active() + self.lsbutton.set_sensitive(not checked) + if checked: + self.lsbutton_stopped() + self.emit('command', "continuous_listen") + self.set_icon_active() + else: + self.emit('command', "continuous_stop") + self.set_icon_inactive() + + def lsbutton_stopped(self): + self.lsbutton.set_label("Listen") + + def lsbutton_clicked(self, button): + val = self.lsbutton.get_label() + if val == "Listen": + self.emit("command", "listen") + self.lsbutton.set_label("Stop") + # Clear the label + self.label.set_text("") + self.set_icon_active() + else: + self.lsbutton_stopped() + self.emit("command", "stop") + self.set_icon_inactive() + + def run(self): + # Set the default icon + self.set_icon_inactive() + self.window.show_all() + if self.continuous: + self.set_icon_active() + self.ccheckbox.set_active(True) + + def accel_quit(self, accel_group, acceleratable, keyval, modifier): + self.emit("command", "quit") + + def delete_event(self, x, y): + self.emit("command", "quit") + + def finished(self, text): + # If the continuous isn't pressed + if not self.ccheckbox.get_active(): + self.lsbutton_stopped() + self.set_icon_inactive() + self.label.set_text(text) + + def set_icon_active_asset(self, i): + self.icon_active = i + + def set_icon_inactive_asset(self, i): + self.icon_inactive = i + + def set_icon_active(self): + Gtk.Window.set_default_icon_from_file(self.icon_active) + + def set_icon_inactive(self): + Gtk.Window.set_default_icon_from_file(self.icon_inactive) diff --git a/README.md b/README.md index 768f38a..feb0b46 100644 --- a/README.md +++ b/README.md @@ -1,61 +1,64 @@ -# Blather +# Kaylee -Blather is a speech recognizer that will run commands when a user speaks preset sentences. +Kaylee is a somewhat fancy speech recognizer that will run commands and perform +other functions when a user speaks loosely preset sentences. It is based on +[Blather](https://gitlab.com/jezra/blather) by [Jezra](http://www.jezra.net/), +but adds a lot of features that go beyond the original purpose of Blather. ## Requirements 1. pocketsphinx -2. gstreamer-0.10 (and what ever plugin has pocket sphinx support) -3. gstreamer-0.10 base plugins (required for alsa) +2. gstreamer-1.0 (and what ever plugin has pocket sphinx support) +3. gstreamer-1.0 base plugins (required for alsa) 4. pyside (only required for the Qt based UI) 5. pygtk (only required for the Gtk based UI) -6. pyyaml (only required for reading the options file) +6. pyyaml (only required for reading the options file) **Note:** it may also be required to install `pocketsphinx-hmm-en-hub4wsj` ## Usage -0. move commands.tmp to ~/.config/blather/commands.conf and fill the file with sentences and command to run +0. Move commands.tmp to ~/.config/blather/commands.conf and fill the file with sentences and command to run 1. Run Blather.py, this will generate ~/.config/blather/sentences.corpus based on sentences in the 'commands' file -2. quit blather (there is a good chance it will just segfault) -3. go to <http://www.speech.cs.cmu.edu/tools/lmtool.html> and upload the sentences.corpus file -4. download the resulting XXXX.lm file to the ~/.config/blather/language directory and rename to file to 'lm' -5. download the resulting XXXX.dic file to the ~/.config/blather/language directory and rename to file to 'dic' -6. run Blather.py - * for Qt GUI, run Blather.py -i q - * for Gtk GUI, run Blather.py -i g - * to start a UI in 'continuous' listen mode, use the -c flag - * to use a microphone other than the system default, use the -m flag -7. start talking +2. Quit blather (there is a good chance it will just segfault) +3. Go to <http://www.speech.cs.cmu.edu/tools/lmtool.html> and upload the sentences.corpus file +4. Download the resulting XXXX.lm file to the ~/.config/blather/language directory and rename to file to 'lm' +5. Download the resulting XXXX.dic file to the ~/.config/blather/language directory and rename to file to 'dic' +6. Run Blather.py + * For Qt GUI, run Blather.py -i q + * For Gtk GUI, run Blather.py -i g + * To start a UI in 'continuous' listen mode, use the -c flag + * To use a microphone other than the system default, use the -m flag +7. Start talking **Note:** to start Blather without needing to enter command line options all the time, copy options.yaml.tmp to ~/.config/blather/options.yaml and edit accordingly. ### Bonus -once the sentences.corpus file has been created, run the language_updater.sh script to automate the process of creating and downloading language files. +Once the sentences.corpus file has been created, run the language_updater.sh script to automate the process of creating and downloading language files. ### Examples -* To run blather with the GTK UI and start in continuous listen mode: +* To run blather with the GTK UI and start in continuous listen mode: `./Blather.py -i g -c` -* To run blather with no UI and using a USB microphone recognized and device 2: +* To run blather with no UI and using a USB microphone recognized and device 2: `./Blather.py -m 2` -* To have blather pass the matched sentence to the executing command: - `./Blather.py -p` +* To have blather pass the matched sentence to the executing command: + `./Blather.py -p` - **explanation:** if the commands.conf contains: - **good morning world : example_command.sh** - then 3 arguments, 'good', 'morning', and 'world' would get passed to example_command.sh as + **explanation:** if the commands.conf contains: + **good morning world : example_command.sh** + then 3 arguments, 'good', 'morning', and 'world' would get passed to example_command.sh as `example_command.sh good morning world` -* To run a command when a valid sentence has been detected: - `./Blather.py --valid-sentence-command=/path/to/command` +* To run a command when a valid sentence has been detected: + `./Blather.py --valid-sentence-command=/path/to/command` **note:** this can be set in the options.yml file -* To run a command when a invalid sentence has been detected: - `./Blather.py --invalid-sentence-command=/path/to/command` +* To run a command when a invalid sentence has been detected: + `./Blather.py --invalid-sentence-command=/path/to/command` **note:** this can be set in the options.yml file ### Finding the Device Number of a USB microphone diff --git a/Recognizer.py b/Recognizer.py index e9cb648..e962ea3 100755 --- a/Recognizer.py +++ b/Recognizer.py @@ -1,57 +1,65 @@ -#This is part of Blather +# This is part of Blather # -- this code is licensed GPLv3 # Copyright 2013 Jezra -import pygst -pygst.require('0.10') -import gst +import gi +gi.require_version('Gst', '1.0') +from gi.repository import GObject, Gst +GObject.threads_init() +Gst.init(None) import os.path -import gobject import sys -#define some global variables +# Define some global variables this_dir = os.path.dirname( os.path.abspath(__file__) ) -class Recognizer(gobject.GObject): - __gsignals__ = { - 'finished' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING,)) - } - def __init__(self, language_file, dictionary_file, src = None): - gobject.GObject.__init__(self) - self.commands = {} - if src: - audio_src = 'alsasrc device="hw:%d,0"' % (src) - else: - audio_src = 'autoaudiosrc' - - #build the pipeline - cmd = audio_src+' ! audioconvert ! audioresample ! vader name=vad ! pocketsphinx name=asr ! appsink sync=false' - try: - self.pipeline=gst.parse_launch( cmd ) - except Exception, e: - print e.message - print "You may need to install gstreamer0.10-pocketsphinx" - raise e - - #get the Auto Speech Recognition piece - asr=self.pipeline.get_by_name('asr') - asr.connect('result', self.result) - asr.set_property('lm', language_file) - asr.set_property('dict', dictionary_file) - asr.set_property('configured', True) - #get the Voice Activity DEtectoR - self.vad = self.pipeline.get_by_name('vad') - self.vad.set_property('auto-threshold',True) - - def listen(self): - self.pipeline.set_state(gst.STATE_PLAYING) - - def pause(self): - self.vad.set_property('silent', True) - self.pipeline.set_state(gst.STATE_PAUSED) - - def result(self, asr, text, uttid): - #emit finished - self.emit("finished", text) +class Recognizer(GObject.GObject): + __gsignals__ = { + 'finished' : (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, (GObject.TYPE_STRING,)) + } + def __init__(self, language_file, dictionary_file, src = None): + GObject.GObject.__init__(self) + self.commands = {} + if src: + audio_src = 'alsasrc device="hw:%d,0"' % (src) + else: + audio_src = 'autoaudiosrc' + + # Build the pipeline + cmd = audio_src+' ! audioconvert ! audioresample ! pocketsphinx name=asr ! appsink sync=false' + try: + self.pipeline=Gst.parse_launch( cmd ) + except Exception, e: + print e.message + print "You may need to install gstreamer1.0-pocketsphinx" + raise e + + bus = self.pipeline.get_bus() + bus.add_signal_watch() + + # Get the Auto Speech Recognition piece + asr=self.pipeline.get_by_name('asr') + bus.connect('message::element', self.result) + asr.set_property('lm', language_file) + asr.set_property('dict', dictionary_file) + asr.set_property('configured', True) + + def listen(self): + self.pipeline.set_state(Gst.State.PLAYING) + + def pause(self): + self.pipeline.set_state(Gst.State.PAUSED) + + def result(self, bus, msg): + msg_struct = msg.get_structure() + # Ignore messages that aren't from pocketsphinx + msgtype = msg_struct.get_name() + if msgtype != 'pocketsphinx': + return + + # If we have a final command, send it for processing + command = msg_struct.get_string('hypothesis') + if command != '' and msg_struct.get_boolean('final')[1]: + self.emit("finished", command) |