diff --git a/README b/README index a0a66bf20e7448aaf0a4607b1fc6f6b97a6be183..e0d465117f1838bb8e66ac543425fc52b75e0f1c 100644 --- a/README +++ b/README @@ -31,11 +31,12 @@ 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?) +DONE click section = select that section +DONE click action = perform action 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 +click line = synchronize to that line +click action = synchronize to the next line add global timer which shows elapsed time change xml view to reflect already performed actions, already recognized text diff --git a/actions.py b/actions.py index 89e47bce122578f773cb008908efe2732beed683..43a68495172dc17eb318586633459e6a791c3a78 100644 --- a/actions.py +++ b/actions.py @@ -3,11 +3,11 @@ import osc, log class Action: def __init__(self, text, **kwargs): self.text = text - for key, value in kwargs: + for key, value in kwargs.items(): setattr(self, key, value) class ActionManager: - def __init__(self, host, port, confirmer, highlighter, logger=log.ConsoleLogger()): + def __init__(self, confirmer, highlighter, logger=log.ConsoleLogger()): #self.client = osc.Client(host, port) self.confirmer = confirmer self.highlighter = highlighter @@ -20,13 +20,13 @@ class ActionManager: def perform(self, action, confirm=True, timeout=3): if confirm: - self.confirmer.confirm('Perform action "%s"?' % action.text, time, lambda: self.confirmed_perform(action)) + self.confirmer.confirm('Perform action "%s"?' % action.text, timeout, lambda: self.confirmed_perform(action)) else: self.confirmed_perform(action) manager = None -def setup(confirmer, highlighter, logger): +def setup(confirmer, highlighter, logger=log.ConsoleLogger()): global manager manager = ActionManager(confirmer, highlighter, logger) diff --git a/animate.py b/animate.py index bb71dab024eb2d5f658da5733cf4adadc74c5bae..4d4eea10666efb06e51d0c44b422272eb2a626cd 100644 --- a/animate.py +++ b/animate.py @@ -5,12 +5,14 @@ def cancel(): global timer if timer: GObject.source_remove(timer) + timer = None LINEAR=1 DECELERATE=2 def animate_value(callback, current, target, policy=DECELERATE, speed=32): global timer + timer = None if current != target: if policy == DECELERATE: delta = abs(target - current) / 2 diff --git a/asr.py b/asr.py index 1299008dafb17f416260fb7d45289db354e24f9b..dc6a8c7b4a32cd8e37456b28098981abbaa3bed2 100644 --- a/asr.py +++ b/asr.py @@ -26,9 +26,20 @@ class ASR(Gtk.HBox): self.scrolled.set_size_request(-1, 100) self.pack_start(self.scrolled, True, True, 5) - self.button = Gtk.Button("Speak") + vbox = Gtk.VBox() + self.pack_start(vbox, False, False, 5) + + self.button = Gtk.Button("Record") + self.button.set_size_request(100, -1) self.button.set_sensitive(False) - self.pack_start(self.button, False, False, 5) + vbox.pack_start(self.button, True, True, 5) + + #save_state = Gtk.Button("Save state") + #save_state.connect('clicked', self.save_state) + #vbox.pack_start(save_state, False, False, 5) + #load_state = Gtk.Button("Load state") + #load_state.connect('clicked', self.load_state) + #vbox.pack_start(load_state, False, False, 5) self.button.connect('clicked', self.button_clicked) self.text.connect("size-allocate", self.autoscroll) @@ -82,28 +93,30 @@ class ASR(Gtk.HBox): GObject.idle_add(self._finished_loading_asr) - def load_state(self): + def load_state(self, *args): try: with open('state.txt') as fp: self.asr.set_property('adaptation-state', fp.read()) + print "ASR: State loaded" except: - print >> sys.stderr, 'failed to load asr state' + print >> sys.stderr, 'failed to load asr state' - def save_state(self): + def save_state(self, *args): if hasattr(self, 'asr'): state = self.asr.get_property('adaptation-state') try: with open('state.txt', 'w') as fp: fp.write(state) + print "ASR: State saved" except: print >> sys.stderr, 'failed to save asr state' def _started_loading_asr(self): self.button.set_sensitive(False) - self.button.set_label("Loading...") + self.button.set_label("Loading") def _finished_loading_asr(self): - self.button.set_label("Speak") + self.button.set_label("Record") self.button.set_sensitive(True) def _on_partial_result(self, asr, hyp): @@ -149,12 +162,12 @@ class ASR(Gtk.HBox): def button_clicked(self, button): """Handle button presses.""" - if button.get_label() == "Speak": + if button.get_label() == "Record": button.set_label("Stop") self.asr.set_property("silent", False) self.hyp = [] self.buffer.set_text('...') else: - button.set_label("Speak") + button.set_label("Record") self.asr.set_property("silent", True) diff --git a/confirm.py b/confirm.py index 3a022991de3811c8c7753c0d4441a25872a1f598..0bb476fa20f0f1c62e301d2af3e5b6f995735443 100644 --- a/confirm.py +++ b/confirm.py @@ -56,6 +56,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) else: + self.timer = None self.click_yes() def cancel_timer(self): diff --git a/data/style.css b/data/style.css index d8bd2713a665dace833d0905251958c269dd870d..dca8aa2b8882af2ac60c9bcb87b1582a71239d20 100644 --- a/data/style.css +++ b/data/style.css @@ -15,7 +15,7 @@ font: bold; } -.selected-section-title { +.selected-section > .section-title { font: bold 18; color: white; background: #009900; @@ -31,7 +31,7 @@ margin: 20px; } -.selected-section-body { +.selected-section { background: #eeffee; margin: 20px; } diff --git a/main.py b/main.py index 8417cfc2900a6ad71bc2170b46103c36e9c399b2..699bcd6c36e6a281ee8898297b7007698b78d8c5 100644 --- a/main.py +++ b/main.py @@ -24,43 +24,27 @@ import signal signal.signal(signal.SIGINT, signal.SIG_DFL) # import local stuff -import confirm, asr, actions, xmlview_widgets -import sections +import confirm, asr, actions, xmlview import levenstein, SLU, osc class ScriptedASR(Gtk.Window): 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(1024, 768) self.set_border_width(10) self.set_title('ASR Transcript [xml=%s asr=%s osc=%s:%s]' % (xml_filename, asr_config_file, osc_host, osc_port)) vbox = Gtk.VBox() - self.sections = sections.SectionManager() - vbox.pack_start(self.sections, False, True, 5) - - self.xmlview = xmlview_widgets.XmlView(xml_filename) + self.xmlview = xmlview.XmlView(xml_filename) vbox.pack_start(self.xmlview, True, True, 5) self.lines = [x for x in self.xmlview.get_line_iterator()] self.current_line = -1 - self.xmlview.set_action_clicked_handler(self.keyword_clicked) self.confirmer = confirm.ConfirmationBox() vbox.pack_start(self.confirmer, False, True, 5) - #self.actions = action.ActionView() - #vbox.pack_start(self.actions, False, True, 5) - - self.sections.set_confirmer(self.confirmer) - #self.actions.set_confirmer(self.confirmer) - - self.sections.set_highlight(self.xmlview) - self.sections.set_section(1) - # transcript view self.asr = asr.ASR(asr_config_file, self.hyp_changed, self.hyp_changed) vbox.pack_start(self.asr, False, True, 5) @@ -71,7 +55,6 @@ class ScriptedASR(Gtk.Window): for section_fst in glob.glob(prefix % 'section*.fst'): found = re.search('section(\d+)\.fst$', section_fst) if found: - print section_fst section_id = int(found.group(1)) self.slu[section_id] = SLU.SLU(prefix % 'dico_word.txt', prefix % 'dico_action.txt', section_fst, prefix % 'clean_tail.fst') @@ -85,6 +68,10 @@ class ScriptedASR(Gtk.Window): style_provider.load_from_data(open('data/style.css', 'rb').read()) Gtk.StyleContext.add_provider_for_screen( Gdk.Screen.get_default(), style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) + # setup singletons + osc.setup(osc_host, osc_port) + actions.setup(self.confirmer, self.xmlview) + def line_clicked(self, widget, event): if self.current_line >= 0: self.lines[self.current_line].highlight(False) @@ -93,22 +80,17 @@ class ScriptedASR(Gtk.Window): self.current_line = i self.lines[self.current_line].highlight(True) - def keyword_clicked(self, action): - self.confirmer.confirm('Force action \"%s\"?' % action, 3, None) - def hyp_changed(self, hyp): #hyp = ' '.join(hyp).replace('[noise]', ' ').split() words = hyp[-1].strip().replace('_', ' ').split() - section_id = self.sections.get_section() - print section_id, self.slu.keys() + section_id = int(self.xmlview.current_section.name) - 1 if section_id in self.slu: - print 'SLU: ', section_id slu = self.slu[section_id] previous_actions = slu.num_actions() slu.process(words) for action_id in range(previous_actions, slu.num_actions()): action = slu.get_action(action_id) - self.confirmer.confirm('Perform action \"%s\"?' % action, 3, None) + actions.perform_action(actions.Action(action)) #if self.current_line >= len(self.lines) - 1: # print "FINISHED" diff --git a/timer.py b/timer.py new file mode 100644 index 0000000000000000000000000000000000000000..97d9eef92ca3947d28b0828d8ad3ac7f09001628 --- /dev/null +++ b/timer.py @@ -0,0 +1,19 @@ +from gi.repository import Gtk, GObject +import datetime + +class Timer(Gtk.Label): + def __init__(self): + super(Timer, self).__init__() + self.start_time = datetime.datetime.now() + self.timer = GObject.timeout_add(100, self.update) + self.get_style_context().add_class('timer') + + def update(self): + self.current_time = datetime.datetime.now() - self.start_time + self.set_text(self.to_string()) + self.timer = GObject.timeout_add(100, self.update) + + def to_string(self): + time = self.current_time + return '%02d:%02d:%02d.%01d' % (time.seconds / 3600, (time.seconds / 60) % 60, time.seconds % 60, time.microseconds / 100000) + diff --git a/xmlview.py b/xmlview.py index aba9b3cad07ddcba1ac04a20a56406c98f4c3359..d6ceee07a32fd8061a2ba212f85e69f4ea690c4d 100644 --- a/xmlview.py +++ b/xmlview.py @@ -1,17 +1,117 @@ -from gi.repository import GObject, Gtk, Pango +import animate, actions + +from gi.repository import GObject, Gtk, Pango, Gdk from xml.etree import ElementTree as ET -class Section: - def __init__(self, name, start, end): - self.name = name - self.start = start - self.end = end +class Section(Gtk.VBox): + def __init__(self, section): + super(Section, self).__init__() + self.name = section.get('id') + self.get_style_context().add_class('section-body') + self.title = Gtk.Label() + self.title.set_markup('Section [<a href="%s">%s</a>]' % (self.name, self.name)) + self.title.get_style_context().add_class('section-title') + self.listeners = [] + self.title.connect('activate-link', self.link_clicked) + self.pack_start(self.title, True, True, 5) + self.sequences = [] + + num = 1 + for sequence in section.findall('./sequence'): + self.sequences.append(Sequence(sequence, section.get('id') + '.' + str(num))) + self.pack_start(self.sequences[-1], True, True, 5) + num += 1 + + def highlight(self, active=True): + if active: + self.get_style_context().add_class('selected-section') + self.get_style_context().remove_class('section-body') + else: + self.get_style_context().remove_class('selected-section') + self.get_style_context().add_class('section-body') -class Word: - def __init__(self, text, start, end): - self.text = text - self.start = start - self.end = end + def link_clicked(self, widget, uri): + for listener in self.listeners: + listener(self) + return True + + def add_listener(self, listener): + self.listeners.append(listener) + + +class Sequence(Gtk.VBox): + def __init__(self, sequence, name): + super(Sequence, self).__init__() + self.name = name + self.get_style_context().add_class('sequence-body') + self.label = Gtk.Label('Sequence %s' % name) + self.label.get_style_context().add_class('sequence-title') + self.pack_start(self.label, True, True, 5) + + self.lines = [] + elements = [] + for line in sequence.text.split('\n'): + line = line.strip() + if line != '': + elements.append(Text(line)) + if len(elements) > 0: + self.lines.append(Line(elements)) + self.pack_start(self.lines[-1], True, True, 5) + elements = [] + for node in sequence: + if node.tag == 'keyword': + text = str(node.text).strip() + if node.get('action').strip() != '': + elements.append(Keyword(text, node.get('action'), node.get('lang'))) + else: + elements.append(Text(text)) + for line in node.tail.split('\n'): + line = line.strip() + if line != '': + elements.append(Text(line)) + if len(elements) > 0: + self.lines.append(Line(elements)) + self.pack_start(self.lines[-1], True, True, 5) + elements = [] + if len(elements) > 0: + self.lines.append(Line(elements)) + self.pack_start(self.lines[-1], True, True, 5) + +class Line(Gtk.HBox): + def __init__(self, elements): + super(Line, self).__init__() + self.pack_start(Gtk.Label(' '), False, False, 0) + for element in elements: + self.pack_start(element, False, False, 0) + self.elements = elements + self.get_style_context().add_class('text-line') + + def highlight(self, active=True): + if active: + self.label.get_style_context().add_class('highlighted') + else: + self.label.get_style_context().remove_class('highlighted') + +class Keyword(Gtk.Label): + def __init__(self, text, action, lang): + super(Keyword, self).__init__() + self.action = action + self.lang = lang + text = '\n'.join([x.strip() for x in text.split('\n')]) + self.set_markup(text + ' [<a href="%s">%s</a>] ' % (action, action)) + self.get_style_context().add_class('keyword') + self.connect('activate-link', self.link_clicked) + + def link_clicked(self, widget, uri): + actions.perform_action(actions.Action(uri, keyword=widget)) + return True + +class Text(Gtk.Label): + def __init__(self, text): + super(Text, self).__init__() + text = '\n'.join([x.strip() for x in text.split('\n')]) + self.set_text(text + ' ') + self.get_style_context().add_class('text') class XmlView(Gtk.ScrolledWindow): def __init__(self, filename): @@ -19,64 +119,49 @@ class XmlView(Gtk.ScrolledWindow): self.sections = [] self.words = [] - self.view = Gtk.TextView() - self.view.set_editable(False) - self.view.set_cursor_visible(False) - self.buffer = self.view.get_buffer() - self.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.ALWAYS) - self.add_with_viewport(self.view) - #self.inactive_section = self.buffer.create_tag("inactive_section", background="#ffffff") - self.active_section = self.buffer.create_tag("active_section", background="red") - self.section_title = self.buffer.create_tag("section_title", scale=2, weight=Pango.Weight.BOLD, justification=Gtk.Justification.CENTER) - self.subsection_title = self.buffer.create_tag("subsection_title", scale=1.5, weight=Pango.Weight.BOLD, justification=Gtk.Justification.CENTER) + self.vbox = self.parse_xml(filename) + self.add_with_viewport(self.vbox) - self.parse_xml(filename) + self.connect('scroll-event', lambda widget, event: animate.cancel()) - self.last_section = None - self.show_section(0) + for section in self.sections: + section.add_listener(self.section_clicked) + self.set_section(0) def get_view(self): return self - def parse_sequence(self, sequence, name): - self.buffer.insert_with_tags(self.buffer.get_end_iter(), 'Sequence %s\n' % name, self.subsection_title) - - text = str(sequence.text) - for node in sequence: - text += node.text - text += node.tail - for line in text.split('\n'): - line = line.strip() - if line != '': - self.buffer.insert_with_tags(self.buffer.get_end_iter(), ' %s\n' % line) - - def parse_section(self, section): - name = section.get('id') - section_start = self.buffer.create_mark('section-start %s' % name, self.buffer.get_end_iter(), True) - self.buffer.insert_with_tags(self.buffer.get_end_iter(), 'Section %s\n' % section.get('id'), self.section_title) - - num = 1 - for sequence in section.findall('./sequence'): - self.parse_sequence(sequence, section.get('id') + '.' + str(num)) - num += 1 - self.sections.append(Section(name, section_start, self.buffer.create_mark('section-end %s' % name, self.buffer.get_end_iter(), True))) - def parse_xml(self, filename): + self.sections = [] root = ET.parse(filename) - treestore = Gtk.TreeStore(str) + vbox = Gtk.VBox() + vbox.get_style_context().add_class('xmlview') for section in root.findall(".//section"): - self.parse_section(section) - return treestore - - def show_section(self, section): - if self.last_section != None: - self.buffer.remove_tag(self.active_section, - self.buffer.get_iter_at_mark(self.sections[self.last_section].start), - self.buffer.get_iter_at_mark(self.sections[self.last_section].end)) - self.last_section = section - self.buffer.apply_tag(self.active_section, - self.buffer.get_iter_at_mark(self.sections[section].start), - self.buffer.get_iter_at_mark(self.sections[section].end)) + self.sections.append(Section(section)) + vbox.pack_start(self.sections[-1], True, True, 5) + return vbox + + def get_line_iterator(self): + for section in self.sections: + for sequence in section.sequences: + for line in sequence.lines: + yield line + + def get_section(self): + return int(self.current_section.name) - 1 + + def set_section(self, section_id, scroll=True): + self.current_section = self.sections[section_id] + for section in self.sections: + section.highlight(self.current_section is section) + if scroll: + animate.scroll_to(self, self.current_section) + + def section_clicked(self, current): + self.set_section(int(current.name) - 1) + + def highlight(self, action): + pass diff --git a/xmlview_widgets.py b/xmlview_widgets.py deleted file mode 100644 index ab30d778b783a239c506c3ffc8666a404d77a8c0..0000000000000000000000000000000000000000 --- a/xmlview_widgets.py +++ /dev/null @@ -1,176 +0,0 @@ -import animate - -from gi.repository import GObject, Gtk, Pango, Gdk -from xml.etree import ElementTree as ET - -class Section(Gtk.VBox): - def __init__(self, section): - super(Section, self).__init__() - self.name = section.get('id') - self.get_style_context().add_class('section-body') - self.title = Gtk.EventBox() - self.label = Gtk.Label('Section %s' % self.name) - self.label.get_style_context().add_class('section-title') - self.title.add(self.label) - self.title.connect('button-press-event', self.clicked) - self.handler = None - #cursor = Gdk.Cursor(Gdk.CursorType.HAND1) - #self.label.get_window().set_cursor(cursor) - self.pack_start(self.title, True, True, 5) - self.sequences = [] - - num = 1 - for sequence in section.findall('./sequence'): - self.sequences.append(Sequence(sequence, section.get('id') + '.' + str(num))) - self.pack_start(self.sequences[-1], True, True, 5) - num += 1 - - def highlight(self, active=True): - if active: - self.label.get_style_context().remove_class('section-title') - self.label.get_style_context().add_class('selected-section-title') - self.get_style_context().add_class('selected-section-body') - self.get_style_context().remove_class('section-body') - else: - self.label.get_style_context().remove_class('selected-section-title') - self.label.get_style_context().add_class('section-title') - self.get_style_context().remove_class('selected-section-body') - self.get_style_context().add_class('section-body') - - def clicked(self, widget, event): - if self.handler: - self.handler(int(self.name) - 1) - - def set_handler(self, handler): - self.handler = handler - - -class Sequence(Gtk.VBox): - def __init__(self, sequence, name): - super(Sequence, self).__init__() - self.name = name - self.get_style_context().add_class('sequence-body') - self.label = Gtk.Label('Sequence %s' % name) - self.label.get_style_context().add_class('sequence-title') - self.pack_start(self.label, True, True, 5) - - self.lines = [] - elements = [] - for line in sequence.text.split('\n'): - line = line.strip() - if line != '': - elements.append(Text(line)) - if len(elements) > 0: - self.lines.append(Line(elements)) - self.pack_start(self.lines[-1], True, True, 5) - elements = [] - for node in sequence: - if node.tag == 'keyword': - text = str(node.text).strip() - if node.get('action').strip() != '': - elements.append(Keyword(text, node.get('action'), node.get('lang'))) - else: - elements.append(Text(text)) - for line in node.tail.split('\n'): - line = line.strip() - if line != '': - elements.append(Text(line)) - if len(elements) > 0: - self.lines.append(Line(elements)) - self.pack_start(self.lines[-1], True, True, 5) - elements = [] - if len(elements) > 0: - self.lines.append(Line(elements)) - self.pack_start(self.lines[-1], True, True, 5) - -class Line(Gtk.HBox): - def __init__(self, elements): - super(Line, self).__init__() - self.pack_start(Gtk.Label(' '), False, False, 0) - for element in elements: - self.pack_start(element, False, False, 0) - self.elements = elements - self.get_style_context().add_class('text-line') - - def set_handler(self, handler): - for element in self.elements: - if hasattr(element, 'set_handler'): - element.set_handler(handler) - - def highlight(self, active=True): - if active: - self.label.get_style_context().add_class('highlighted') - else: - self.label.get_style_context().remove_class('highlighted') - -class Keyword(Gtk.Label): - def __init__(self, text, action, lang): - super(Keyword, self).__init__() - self.action = action - self.lang = lang - text = '\n'.join([x.strip() for x in text.split('\n')]) - self.set_markup(text + ' [<a href="%s">%s</a>] ' % (action, action)) - self.get_style_context().add_class('keyword') - self.connect('activate-link', self.link_clicked) - self.handler = None - - def set_handler(self, handler): - self.handler = handler - - def link_clicked(self, widget, uri): - if self.handler: - self.handler(uri) - return True - -class Text(Gtk.Label): - def __init__(self, text): - super(Text, self).__init__() - text = '\n'.join([x.strip() for x in text.split('\n')]) - self.set_text(text + ' ') - self.get_style_context().add_class('text') - -class XmlView(Gtk.ScrolledWindow): - def __init__(self, filename): - super(XmlView, self).__init__() - self.sections = [] - self.words = [] - - self.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.ALWAYS) - - self.vbox = self.parse_xml(filename) - self.add_with_viewport(self.vbox) - - self.last_section = None - self.connect('scroll-event', lambda widget, event: animate.cancel()) - - def get_view(self): - return self - - def parse_xml(self, filename): - self.sections = [] - root = ET.parse(filename) - vbox = Gtk.VBox() - vbox.get_style_context().add_class('xmlview') - for section in root.findall(".//section"): - self.sections.append(Section(section)) - vbox.pack_start(self.sections[-1], True, True, 5) - return vbox - - def set_action_clicked_handler(self, handler): - for line in self.get_line_iterator(): - line.set_handler(handler) - - def get_line_iterator(self): - for section in self.sections: - for sequence in section.sequences: - for line in sequence.lines: - yield line - - def highlight_section(self, section): - if section < 1 or section > len(self.sections): - print "invalid section", section - else: - for current in range(len(self.sections)): - self.sections[current].highlight(current == section - 1) - animate.scroll_to(self, self.sections[section - 1]) -