diff --git a/README b/README index bc4e83d3bc2f388362806d9afc39213392a1e656..a0a66bf20e7448aaf0a4607b1fc6f6b97a6be183 100644 --- a/README +++ b/README @@ -12,33 +12,41 @@ copy libgstkaldionline2.so to ./asr/ or change GST_PLUGIN_PATH in main.py to poi Run: -python2 main.py +- The main program: +./start.sh -Doc: +- The osc server: +python2 osc.py +Doc: developing with pygtk3: http://lazka.github.io/pgi-docs/, https://python-gtk-3-tutorial.readthedocs.org/en/latest/ Todo: +DONE configuration for osc +DONE non intrusive animated scrolling +DONE make configuration box options persistant +DONE push words through osc +DONE global action send which deals with +DONE - show a warning (optional) +DONE - send action through osc +DONE - show an action performed message (message log with timing?) + events = click action or words to resynchronize ? click line = synchronize to that line click section = select that section click action = perform action and synchronize to the next line -non intrusive animated scrolling -global action send which deals with - - show a warning (optional) - - sned action through osc - - show an action performed message (message log with timing?) add global timer which shows elapsed time change xml view to reflect already performed actions, already recognized text -configuration for osc +move slu to asr make selector a proper window -make configuration box options persistant add thread for slu allow sequence advance in slu, add UI for that +remove section changer UI +add global keybindings (1-9 for sections, y/n)... diff --git a/action.py b/action.py deleted file mode 100644 index 777b865e20855037f53d37813add8237f168f489..0000000000000000000000000000000000000000 --- a/action.py +++ /dev/null @@ -1,35 +0,0 @@ -from gi.repository import Gtk - -class ActionView(Gtk.HBox): - def __init__(self): - super(ActionView, self).__init__() - self.actions = set(['next', 'previous', 'light-on', 'light-off']) - - self.pack_start(Gtk.Label('Actions:'), False, False, 5) - for action in self.actions: - button = Gtk.Button(action) - button.connect('clicked', self._perform) - self.pack_start(button, False, False, 5) - - self.confiermer = None - - def _perform(self, button): - action = button.get_label() - if self.confirmer: - self.confirmer.confirm('Perform action "%s"?' % action, 4, lambda: self.perform(action)) - else: - self.perform(action) - - def perform(self, action): - if action in self.actions: - print 'PERFORM', action - return True - return False - - def actions(self): - return self.actions - - def set_confirmer(self, confirmer): - self.confirmer = confirmer - - diff --git a/actions.py b/actions.py new file mode 100644 index 0000000000000000000000000000000000000000..89e47bce122578f773cb008908efe2732beed683 --- /dev/null +++ b/actions.py @@ -0,0 +1,38 @@ +import osc, log + +class Action: + def __init__(self, text, **kwargs): + self.text = text + for key, value in kwargs: + setattr(self, key, value) + +class ActionManager: + def __init__(self, host, port, confirmer, highlighter, logger=log.ConsoleLogger()): + #self.client = osc.Client(host, port) + self.confirmer = confirmer + self.highlighter = highlighter + self.logger = logger + + def confirmed_perform(self, action): + osc.client.send_action(action) + self.highlighter.highlight(action) + self.logger.log(action) + + def perform(self, action, confirm=True, timeout=3): + if confirm: + self.confirmer.confirm('Perform action "%s"?' % action.text, time, lambda: self.confirmed_perform(action)) + else: + self.confirmed_perform(action) + +manager = None + +def setup(confirmer, highlighter, logger): + global manager + manager = ActionManager(confirmer, highlighter, logger) + +def perform_action(action, confirm=True, timeout=3): + global manager + manager.perform(action, confirm, timeout) + + + diff --git a/animate.py b/animate.py index 2e76748b6e5faf5333de1b916b3560bd7e390c83..bb71dab024eb2d5f658da5733cf4adadc74c5bae 100644 --- a/animate.py +++ b/animate.py @@ -9,13 +9,13 @@ def cancel(): LINEAR=1 DECELERATE=2 -def animate_value(callback, current, target, policy=DECELERATE): +def animate_value(callback, current, target, policy=DECELERATE, speed=32): global timer if current != target: if policy == DECELERATE: delta = abs(target - current) / 2 else: - delta = 32 + delta = speed if current > target: current -= delta else: diff --git a/asr.py b/asr.py index 2eca0f81b9a2037e31d6a236894256d32bd8ec1d..1299008dafb17f416260fb7d45289db354e24f9b 100644 --- a/asr.py +++ b/asr.py @@ -8,6 +8,8 @@ GObject.threads_init() Gdk.threads_init() Gst.init(None) +import osc + class ASR(Gtk.HBox): def __init__(self, asr_config, hyp_callback = None, partial_hyp_callback = None): super(ASR, self).__init__() @@ -110,6 +112,10 @@ class ASR(Gtk.HBox): Gdk.threads_enter() if len(self.hyp) == 0: self.hyp = [''] + + if hyp != self.hyp[-1]: + osc.client.send_words(len(self.hyp), hyp) + self.hyp[-1] = hyp if self.partial_hyp_callback: self.partial_hyp_callback(self.hyp) diff --git a/confirm.py b/confirm.py index 26381b0498a22184d7d344b17ca8bff4796419e2..3a022991de3811c8c7753c0d4441a25872a1f598 100644 --- a/confirm.py +++ b/confirm.py @@ -31,6 +31,7 @@ class ConfirmationBox(Gtk.HBox): self.yes_button.get_child().set_text("YES (%d)" % self.counter) self.timer = GObject.timeout_add_seconds(1, self.countdown) + self.yes_button.grab_focus() self.show() def click_yes(self, button = None): diff --git a/log.py b/log.py new file mode 100644 index 0000000000000000000000000000000000000000..5a462e59d5c62da4ba56a8b8bdbde82d7b6138b8 --- /dev/null +++ b/log.py @@ -0,0 +1,37 @@ +from gi.repository import Gtk +import actions + +class ConsoleLogger: + def __init__(self): + pass + + def log(self, action): + print 'Performed:', action.text + +class GtkLogger(Gtk.HBox): + def __init__(self): + super(Log, self).__init__() + self.text = Gtk.TextView() + self.text.set_editable(False) + self.text.set_cursor_visible(False) + self.buffer = self.text.get_buffer() + self.scrolled = Gtk.ScrolledWindow() + self.scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.ALWAYS) + self.scrolled.add_with_viewport(self.text) + #self.scrolled.set_size_request(-1, 100) + self.pack_start(self.scrolled, True, True, 5) + + def log(self, action): + self.buffer.insert_at_cursor('Performed: %s\n' % action.text) + adj = self.scrolled.get_vadjustment() + adj.set_value(adj.get_upper() - adj.get_page_size()) + +if __name__ == '__main__': + window = Gtk.Window() + window.connect("destroy", Gtk.main_quit) + logger = GtkLogger() + window.add(logger) + window.show_all() + logger.log(actions.Action('coucou')) + Gtk.main() + diff --git a/main.py b/main.py index 24aaed9447c8762a33ad040d1f50342742eaa188..8417cfc2900a6ad71bc2170b46103c36e9c399b2 100644 --- a/main.py +++ b/main.py @@ -5,8 +5,10 @@ import os import glob, re # set to location of libgstkaldionline2.so -os.environ['GST_PLUGIN_PATH'] = './asr/' +directory = os.path.dirname(__file__) or '.' +os.environ['GST_PLUGIN_PATH'] = directory + '/asr/' os.environ['GTK_THEME'] = 'light' +print 'gst plugin path =', os.environ['GST_PLUGIN_PATH'] # import gtk stuff from threading import Thread @@ -22,21 +24,23 @@ import signal signal.signal(signal.SIGINT, signal.SIG_DFL) # import local stuff -import confirm, asr, action, xmlview_widgets -import levenstein, SLU +import confirm, asr, actions, xmlview_widgets +import sections +import levenstein, SLU, osc class ScriptedASR(Gtk.Window): - def __init__(self, xml_filename, asr_config_file): + def __init__(self, xml_filename, asr_config_file, osc_host, osc_port): super(ScriptedASR, self).__init__() + osc.setup(osc_host, osc_port) + self.connect("destroy", self.quit) - self.set_default_size(800,600) + self.set_default_size(1024, 768) self.set_border_width(10) - self.set_title('ScriptedASR [%s]' % xml_filename) + self.set_title('ASR Transcript [xml=%s asr=%s osc=%s:%s]' % (xml_filename, asr_config_file, osc_host, osc_port)) vbox = Gtk.VBox() - import section - self.sections = section.SectionManager() + self.sections = sections.SectionManager() vbox.pack_start(self.sections, False, True, 5) self.xmlview = xmlview_widgets.XmlView(xml_filename) @@ -140,8 +144,8 @@ if __name__ == '__main__': xml_filename = sys.argv[1] if len(sys.argv) > 2: asr_config_file = sys.argv[2] - xml_filename, asr_config_file = selector.ModelSelector(xml_filename, asr_config_file).run() - if xml_filename == None or asr_config_file == None: + xml_filename, asr_config_file, osc_host, osc_port = selector.ModelSelector(xml_filename, asr_config_file).run() + if xml_filename == None or asr_config_file == None or osc_host == None or osc_port == None: sys.exit(0) - app = ScriptedASR(xml_filename, asr_config_file) + app = ScriptedASR(xml_filename, asr_config_file, osc_host, osc_port) Gtk.main() diff --git a/osc.py b/osc.py index 9ffce770476cb51f955bee9488fe0fc30268ba6e..212f3e5e7b1e2892c7fa7c33e010ad25593952b8 100644 --- a/osc.py +++ b/osc.py @@ -1,6 +1,6 @@ import liblo, sys -class ActionSender: +class Client: def __init__(self, host = '127.0.0.1', port = 1234): try: self.target = liblo.Address(host, port) @@ -13,24 +13,35 @@ class ActionSender: except: print >>sys.stderr, 'OSC: failed to send message [%s]' % str(message) -class ActionReceiver: + def send_action(self, action): + self.send('ACTION: %s' % action.text) + + def send_words(self, start, words): + self.send('WORDS(%d): %s' % (start, words)) + +class Server: def __init__(self, host = '127.0.0.1', port = 1234): print 'OSC: Creating server at %s:%d' % (host, port) self.server = liblo.Server(port) self.server.add_method(None, None, self.callback) def callback(self, message): - print 'OSC: Recieved [%s]' % message + print 'OSC: Received [%s]' % message def run(self): print 'OSC: Waiting for messages' while True: self.server.recv(100) +client = None +def setup(host, port): + global client + client = Client(host, port) + if __name__ == '__main__': if len(sys.argv) > 1: - client = ActionSender() + client = Client() client.send(' '.join(sys.argv[1:])) else: - server = ActionReceiver() + server = Server() server.run() diff --git a/section.py b/sections.py similarity index 100% rename from section.py rename to sections.py diff --git a/selector.py b/selector.py index c9bdca656b99b538b4a3621d2b74d41b7582ddc4..c995378c055e89057875183cc2036fb871d5d8c2 100644 --- a/selector.py +++ b/selector.py @@ -1,54 +1,60 @@ from gi.repository import GObject, Gtk, Gdk -import os, sys, glob +import os, sys, glob, re import config class ModelSelector(Gtk.Dialog): def __init__(self, xml_filename = '', asr_model = ''): super(ModelSelector, self).__init__() + self.options = {'xml_filename': xml_filename, 'asr_model': asr_model, 'osc_host': '127.0.0.1', 'osc_port': '1234'} + self.load_options() + self.set_title('Configuration') + self.set_border_width(10) self.add_button("Cancel", Gtk.ResponseType.CANCEL) self.add_button("OK", Gtk.ResponseType.OK) box = self.get_content_area() xml_box = Gtk.HBox() - xml_box.pack_start(Gtk.Label('XML file:'), False, False, 10) + xml_box.pack_start(Gtk.Label('XML file:'), False, False, 5) xml_entry = Gtk.Entry() - xml_entry.set_text(xml_filename) - xml_entry.set_width_chars(len(xml_filename)) + xml_entry.set_text(self.options['xml_filename']) + xml_entry.set_width_chars(len(self.options['xml_filename'])) self.xml_entry = xml_entry - xml_box.pack_start(xml_entry, True, True, 10) + xml_box.pack_start(xml_entry, True, True, 5) xml_button = Gtk.Button("Choose...") xml_button.connect('clicked', self.show_filechooser) - xml_box.pack_start(xml_button, False, False, 10) + xml_box.pack_start(xml_button, False, False, 5) box.pack_start(xml_box, False, False, 5) model_box = Gtk.HBox() - model_box.pack_start(Gtk.Label('ASR model:'), False, False, 10) + model_box.pack_start(Gtk.Label('ASR model:'), False, False, 5) model_chooser = Gtk.ComboBoxText() model_chooser.set_entry_text_column(0) target_index = 0 for i, model in enumerate(self.list_models()): model_chooser.append_text(model) - if asr_model == self.models[i]: + if self.options['asr_model'] == self.models[i]: target_index = i model_chooser.set_active(target_index) self.model_chooser = model_chooser - model_box.pack_start(model_chooser, True, True, 10) + model_box.pack_start(model_chooser, True, True, 5) box.pack_start(model_box, False, False, 5) osc_box = Gtk.HBox() - osc_box.pack_start(Gtk.Label('OSC host:'), False, False, 10) + osc_box.pack_start(Gtk.Label('OSC host:'), False, False, 5) osc_host = Gtk.Entry() - osc_host.set_text('127.0.0.1') + osc_host.set_text(self.options['osc_host']) osc_host.set_width_chars(len(osc_host.get_text())) - osc_box.pack_start(osc_host, True, True, 10) - osc_box.pack_start(Gtk.Label('Port:'), False, False, 10) + self.osc_host = osc_host + osc_box.pack_start(osc_host, True, True, 5) + osc_box.pack_start(Gtk.Label('Port:'), False, False, 5) osc_port = Gtk.Entry() - osc_port.set_text('1234') + osc_port.set_text(self.options['osc_port']) osc_port.set_width_chars(len(osc_port.get_text())) - osc_box.pack_start(osc_port, True, True, 10) + self.osc_port = osc_port + osc_box.pack_start(osc_port, True, True, 5) box.pack_start(osc_box, False, False, 5) @@ -56,16 +62,38 @@ class ModelSelector(Gtk.Dialog): okButton = self.get_widget_for_response(response_id=Gtk.ResponseType.OK) okButton.set_can_default(True) okButton.grab_default() + okButton.grab_focus() self.show_all() + def save_options(self): + try: + with open('.options.txt', 'w') as fp: + for key, value in self.options.items(): + print >>fp, '%s: %s' % (key, str(value)) + except: + print 'Warning: could not save options' + + def load_options(self): + try: + with open('.options.txt') as fp: + for line in fp: + line = line.strip() + found = re.search('^([^:]*):(.*)', line) + if found: + key = found.group(1).strip() + value = found.group(2).strip() + self.options[key] = value + except: + print 'Warning: could not load options' + def show_filechooser(self, button): dialog = Gtk.FileChooserDialog("Please choose a file", self, Gtk.FileChooserAction.OPEN, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK)) filter_text = Gtk.FileFilter() filter_text.set_name("XML files") filter_text.add_mime_type("text/xml") dialog.add_filter(filter_text) - dialog.set_current_folder('%s/data' % os.path.dirname(__file__)) + dialog.set_current_folder('%s/data' % (os.path.dirname(__file__) or '.')) response = dialog.run() if response == Gtk.ResponseType.OK: @@ -90,9 +118,12 @@ class ModelSelector(Gtk.Dialog): def run(self): response = super(ModelSelector, self).run() if response != Gtk.ResponseType.OK: - return None, None - asr_model = self.models[self.model_chooser.get_active()] - xml_filename = self.xml_entry.get_text() + return None, None, None, None + self.options['asr_model'] = self.models[self.model_chooser.get_active()] + self.options['xml_filename'] = self.xml_entry.get_text() + self.options['osc_host'] = self.osc_host.get_text() + self.options['osc_port'] = self.osc_port.get_text() + self.save_options() self.destroy() - return xml_filename, asr_model + return self.options['xml_filename'], self.options['asr_model'], self.options['osc_host'], self.options['osc_port']