#! /usr/bin/env python"""This program will allow annotating without taking your fingers off of the keyboard.Copyright (C) 2006 Scott ShawcroftThis program is free software; you can redistribute it and/ormodify it under the terms of the GNU General Public Licenseas published by the Free Software Foundation; either version 2of 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 ofMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See theGNU General Public License for more details.You should have received a copy of the GNU General Public Licensealong with this program; if not, write to the Free SoftwareFoundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.This program will allow annotating without taking your fingers off of the keyboard.It will automatically store start and stop times along with the text."""##Program Info##"""Keyboard shortcuts:Enter: play and submit text or pause (only works when bottom right entry is focussed)ctrl-o: open filectrl-s: save fileAll other keys will simply enter text.Text shortcuts: (you must use one of these initially!!!')<text>' - Chapters used for navigation.'<name>:<text>' - Subtitles.Note: end times are at the beginning of the same type of entry (ie. chapter or subtitle).Also, you may edited the description text in the main text window.Just be aware that anything not underlined is not considered part of the clip's text."""import pygtkpygtk.require('2.0')import gtkimport pangoimport pygstpygst.require ("0.10")import gstimport mimetypesdef format_time(time): hour = int(time)/1000/3600 if hour<10: hour = "0" + str(hour) else: hour = str(hour) minute = int(time)/1000/60%60 if minute < 10: minute = "0" + str(minute) else: minute = str(minute) seconds = int(time)/1000%60 if seconds < 10: seconds = "0" + str(seconds) else: seconds = str(seconds) mseconds = int(time)%1000 if mseconds<10: mseconds = "00" + str(mseconds) elif mseconds<100: mseconds = "0" + str(mseconds) else: mseconds = str(mseconds) return hour + ":" + minute + ":" + seconds + "." + msecondsdef to_ms(string): h,m,ms = string.replace(".","").split(":") return int(h)*3600*1000 + int(m)*60*1000 + int(ms)class Annotool: #This class handles all aspects of the program. Nothing fancy here. #Below are all of our callbacks. def delete_event(self,widget,event,data=None): # Closes the window smoothly. pass def destroy(self, widget, data=None): # Closes the window. gtk.main_quit() def open_file(self, accel_group, window, keyval, modifier): if self.show_keystrokes: print "Ctrl - O" dialog = gtk.FileChooserDialog("Open",self.window,gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK)) dialog.set_default_response(gtk.RESPONSE_OK) #File filters. filter = gtk.FileFilter() filter.set_name("Yay for ogg!") filter.add_mime_type("application/ogg") dialog.add_filter(filter) filter = gtk.FileFilter() filter.set_name("MP3 Please") filter.add_mime_type("audio/mpeg") dialog.add_filter(filter) response = dialog.run() if response == gtk.RESPONSE_OK: self.pipeline.set_state(gst.STATE_NULL) self.current_file = dialog.get_filename() self.playbin.props.uri = "file://" + self.current_file self.pipeline.set_state(gst.STATE_PAUSED); self.pipeline_duration = 10 dialog.destroy() def import_file(self, accel_group, window, keyval, modifier): if self.show_keystrokes: print "Ctrl - I" dialog = gtk.FileChooserDialog("Import",self.window,gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK)) dialog.set_default_response(gtk.RESPONSE_OK) #File filters. filter = gtk.FileFilter() filter.set_name("CMML") filter.add_pattern("*.cmml") dialog.add_filter(filter) filter = gtk.FileFilter() filter.set_name("SMIL") filter.add_pattern("*.smil") dialog.add_filter(filter) response = dialog.run() filename = None if response == gtk.RESPONSE_OK: filename = dialog.get_filename() format = dialog.get_filter() dialog.destroy() if filename != None: self.progress.props.text = "Importing " + filename self.text.delete(self.text.get_start_iter(),self.text.get_end_iter()) self.times = {"chapter": {"start": [], "end" : [] },"subtitle": { "start":[], "end":[]} } self.start_time.set_text("xx:xx:xx.xxx") self.end_time.set_text("xx:xx:xx.xxx") if format.get_name() == "CMML": self.import_cmml(filename) elif format.get_name() == "SMIL": self.import_smil(filename) def save_file(self, accel_group, window, keyval, modifier): if self.show_keystrokes: print "Ctrl - S" dialog = gtk.FileChooserDialog("Save",self.window,gtk.FILE_CHOOSER_ACTION_SAVE, (gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_SAVE,gtk.RESPONSE_OK)) dialog.set_default_response(gtk.RESPONSE_OK) dialog.set_current_name(self.current_file) #File filters. filter = gtk.FileFilter() filter.set_name("SRT") filter.add_pattern("*.srt") dialog.add_filter(filter) filter = gtk.FileFilter() filter.set_name("CMML") filter.add_pattern("*.cmml") dialog.add_filter(filter) filter = gtk.FileFilter() filter.set_name("TXT") filter.add_pattern("*.txt") dialog.add_filter(filter) response = dialog.run() if response == gtk.RESPONSE_OK: filename = dialog.get_filename() format = dialog.get_filter() if format.get_name() == "SRT": data = self.export_buffer() self.export_srt(filename,data) elif format.get_name() == "CMML": data = self.export_buffer() self.export_cmml(filename,data) elif format.get_name() == "TXT": self.export_txt(filename) dialog.destroy() # Text overlay functions. def toggle_track(self, accel_group, window, keyval, modifier): if self.show_keystrokes: print "Ctrl - T" keys = self.times.keys() new_track = keys.index(self.current_track)+1 print "Switched to " + keys[new_track%len(keys)] + "." self.current_track = keys[new_track%len(keys)] # Navigation functions. def restart_clip(self, accel_group, window, keyval, modifier): if self.show_keystrokes: print "Ctrl - R" self.seek(self.start_time.get_text()) def current_clip(self,current_time): x = 0 while x<len(self.times[self.current_track]['start']) and current_time>=to_ms(self.times[self.current_track]['start'][x]): x += 1 return x-1 def previous_clip(self, accel_group, window, keyval, modifier): if self.show_keystrokes: print "Ctrl - B" clip_now = self.current_clip(self.pipeline.query_position(gst.FORMAT_TIME)[0]/gst.MSECOND) if clip_now > 0: self.seek(self.times[self.current_track]['start'][clip_now-1],self.times[self.current_track]['start'][clip_now]) #Highlight chapter entry. self.text.remove_tag(self.tag_current, self.text.get_start_iter(),self.text.get_end_iter()) mark = self.text.get_mark(self.current_track + "-" + self.times[self.current_track]['start'][clip_now-1]) start_iter = self.text.get_iter_at_mark(mark) if not start_iter.begins_tag(self.tag_clip): start_iter.forward_to_tag_toggle(self.tag_clip) end_iter = start_iter.copy() end_iter.forward_to_tag_toggle(self.tag_clip) self.text.apply_tag(self.tag_current, start_iter, end_iter) #Scroll to mark self.textView.scroll_to_mark(mark,0.1) self.mode = 'review' self.start_time.set_text(self.times[self.current_track]['start'][clip_now-1]) self.end_time.set_text(self.times[self.current_track]['start'][clip_now]) if self.pipeline_duration==10: self.pipeline_duration = self.pipeline.query_duration(gst.FORMAT_TIME)[0]/gst.MSECOND self.progress.props.fraction = float(to_ms(self.times[self.current_track]['start'][clip_now-1]))/self.pipeline_duration else: print self.current_track + " has no previous clips." def next_clip(self, accel_group, window, keyval, modifier): if self.show_keystrokes: print "Ctrl - N" #Only seek forward clips if more clips exist. clip_now = self.current_clip(self.pipeline.query_position(gst.FORMAT_TIME)[0]/gst.MSECOND) if clip_now < len(self.times[self.current_track]['start'])-1: if clip_now < len(self.times[self.current_track]['start'])-2: self.seek(self.times[self.current_track]['start'][clip_now+1], self.times[self.current_track]['start'][clip_now+2]) self.start_time.set_text(self.times[self.current_track]['start'][clip_now+1]) self.end_time.set_text(self.times[self.current_track]['start'][clip_now+2]) self.mode = 'review' elif to_ms(self.times[self.current_track]['end'][-1]) > self.pipeline.query_position(gst.FORMAT_TIME)[0]/gst.MSECOND: self.seek(self.times[self.current_track]['start'][clip_now+1],self.times[self.current_track]['end'][-1]) self.start_time.set_text(self.times[self.current_track]['start'][clip_now+1]) self.end_time.set_text(self.times[self.current_track]['end'][-1]) #Highlight chapter entry. self.text.remove_tag(self.tag_current, self.text.get_start_iter(),self.text.get_end_iter()) mark = self.text.get_mark(self.current_track + "-" + self.times[self.current_track]['start'][clip_now+1]) start_iter = self.text.get_iter_at_mark(mark) if not start_iter.begins_tag(self.tag_clip): start_iter.forward_to_tag_toggle(self.tag_clip) end_iter = start_iter.copy() end_iter.forward_to_tag_toggle(self.tag_clip) self.text.apply_tag(self.tag_current, start_iter, end_iter) #Scroll to mark self.textView.scroll_to_mark(mark,0.1) if self.pipeline_duration==10: self.pipeline_duration = self.pipeline.query_duration(gst.FORMAT_TIME)[0]/gst.MSECOND self.progress.props.fraction = float(to_ms(self.times[self.current_track]['start'][clip_now+1]))/self.pipeline_duration elif self.end_time.get_text()!="xx:xx:xx.xxx": self.seek(self.end_time.get_text()) self.text.remove_tag(self.tag_current, self.text.get_start_iter(),self.text.get_end_iter()) self.mode = 'transcribe' def just_play(self, accel_group, window, keyval, modifier): if self.show_keystrokes: print "Ctrl - P" if (self.pipeline.get_state()[1] != gst.STATE_PLAYING): self.pipeline.set_state(gst.STATE_PLAYING); self.progress.props.text = self.current_file + ": Playing." def seek(self,start,end=None): if start==end: if self.debug: print "Error: Trying to seek to two identical times at " + start + "!" return elif end==None: result = self.pipeline.seek(1.0, gst.FORMAT_TIME, gst.SEEK_FLAG_FLUSH | gst.SEEK_FLAG_ACCURATE, gst.SEEK_TYPE_SET, gst.MSECOND*to_ms(start), gst.SEEK_TYPE_SET, self.pipeline_duration*gst.MSECOND) else: result = self.pipeline.seek(1.0, gst.FORMAT_TIME, gst.SEEK_FLAG_FLUSH | gst.SEEK_FLAG_ACCURATE, gst.SEEK_TYPE_SET, gst.MSECOND*to_ms(start), gst.SEEK_TYPE_SET, gst.MSECOND*to_ms(end)) #Push relevant text to overlay. return result def push_text(self, start=None,end=None): data = self.export_buffer() for clip in data["subtitle-en"]: buf = gst.Buffer (clip[2]) buf.timestamp = to_ms(clip[0])*gst.MSECOND buf.duration = (to_ms(clip[1]) - to_ms(clip[0]))*gst.MSECOND print "time: " + str(buf.timestamp) + " duration: " + str(buf.duration) print self.text_pad.push (buf) def enter_activated(self, accel_group, window, keyval, modifier): #Handles clip to text buffer, formatting and pipeline control. if self.show_keystrokes: print "Enter" if (self.pipeline.get_state()[1] == gst.STATE_PLAYING): self.pipeline.set_state(gst.STATE_PAUSED); current_time = self.pipeline.query_position(gst.FORMAT_TIME)[0]/gst.MSECOND self.end_time.set_text(format_time(current_time)) self.progress.props.text = self.current_file + ": Paused." if self.pipeline_duration==10: self.pipeline_duration = self.pipeline.query_duration(gst.FORMAT_TIME)[0]/gst.MSECOND self.progress.props.fraction = float(current_time)/self.pipeline_duration elif self.pipeline.get_state()[1] == gst.STATE_PAUSED and self.mode=='transcribe': text = self.clip_text.get_text() if text[:1] == ")" or (text.find(":")==-1 and text != "" and self.last_type_def==")") and self.start_time.get_text() != 'xx:xx:xx.xxx': #Chapter deocration. self.text.insert(self.text.get_end_iter(),"\n\nChapter " + str(len(self.times["chapter"])+1) + ": ") #Mark handling. self.times["chapter"]['start'].append(self.start_time.get_text()) #Add mark only if last end time does not equal this start time. if len(self.times["chapter"]['end'])>0 and self.times["chapter"]['end'][-1]!=self.start_time.get_text(): mark = self.text.create_mark("chapter-" + self.start_time.get_text(),self.text.get_end_iter(),True) mark.set_visible(self.visible_marks) #Insert text. self.text.insert_with_tags(self.text.get_end_iter(),text.strip().strip(")"), self.tag_clip) self.last_type_def = ")" #Insert end mark. self.times["chapter"]['end'].append(self.end_time.get_text()) mark = self.text.create_mark("chapter-" + self.end_time.get_text(),self.text.get_end_iter(),True) mark.set_visible(self.visible_marks) elif text != "" and (text.find(":")!=-1 or self.last_type_def.endswith(":")) and self.start_time.get_text() != 'xx:xx:xx.xxx': # If using the last type def make sure doesn't have ';' instead of intended ':' if text.find(":")==-1 and text.find(";")!=-1: dialog = gtk.Dialog("Did you mean...", self.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, (gtk.STOCK_NO, gtk.RESPONSE_NO, gtk.STOCK_YES, gtk.RESPONSE_YES)) label = gtk.Label("It looks like you may have intended to have a new speaker but mistakenly typed a semi-colon (;) instead of a colon (:). Would you like to change the semi-colon to a colon in, '"+text+"'?") label.set_line_wrap(True) dialog.vbox.pack_start(label, True, False, 0) dialog.set_default_response(gtk.RESPONSE_YES) label.show() if dialog.run() == gtk.RESPONSE_YES: text = text.replace(";", ":", 1) dialog.destroy() if text.find(":")!=-1 and not text.lower().startswith(self.last_type_def): self.text.insert(self.text.get_end_iter(),"\n\n"+text[:text.find(":")+1]+"\n") self.last_type_def = text[:text.find(":")+1].lower() elif text!='': #Handles spaces between clips. self.text.insert(self.text.get_end_iter()," ") #Mark handling. self.times["subtitle"]['start'].append(self.start_time.get_text()) if len(self.times["subtitle"]['end'])>0 and self.times["subtitle"]['end'][-1]!=self.start_time.get_text(): mark = self.text.create_mark("subtitle-" + self.start_time.get_text(),self.text.get_end_iter(),True) mark.set_visible(self.visible_marks) #Insert and tag speaker string. (will be invisible) self.text.insert_with_tags(self.text.get_end_iter(),self.last_type_def.strip(":"),self.tag_speaker) #Insert text. self.text.insert_with_tags(self.text.get_end_iter(),text[text.find(":")+1:].strip(),self.tag_clip) #Write end mark. self.times["subtitle"]['end'].append(self.end_time.get_text()) mark = self.text.create_mark("subtitle-" + self.end_time.get_text(),self.text.get_end_iter(),True) mark.set_visible(self.visible_marks) self.clip_text.set_text("") self.start_time.set_text(format_time(self.pipeline.query_position(gst.FORMAT_TIME)[0]/gst.MSECOND)) self.end_time.set_text("xx:xx:xx.xxx") self.scroll_to_bottom() self.pipeline.set_state(gst.STATE_PLAYING); self.progress.props.text = self.current_file + ": Playing." elif self.pipeline.get_state()[1] == gst.STATE_PAUSED: self.start_time.set_text(format_time(self.pipeline.query_position(gst.FORMAT_TIME)[0]/gst.MSECOND)) self.end_time.set_text("xx:xx:xx.xxx") self.pipeline.set_state(gst.STATE_PLAYING); self.progress.props.text = self.current_file + ": Playing." return def scroll_to_bottom(self): if self.mode=='transcribe': self.textView.scroll_to_iter(self.text.get_end_iter(),0.1) def export_buffer(self): # Returns buffer as list of tuples for conversion to different formats. export = {"default":[],"subtitle-en":[]} tracks = {"chapter" : "default","subtitle" : "subtitle-en"} for key in tracks.keys(): x = 1 times = self.times[key]['start'] + self.times[key]['end'] times.sort(lambda x,y: cmp(to_ms(x), to_ms(y))) for time in times: iter = self.text.get_iter_at_mark(self.text.get_mark(key + "-" + time)) #Checks that the next mark is not at the same location. try: count = iter.get_marks().count(self.text.get_mark(key + "-" + times[x])) except: count = 0 #Checks for content/text between this iter and the next. if len(times) > x: tmp_iter = iter.copy() tmp_iter.forward_to_tag_toggle(self.tag_clip) tmp_mark = self.text.get_mark(key + "-" + times[x]) con = self.text.get_iter_at_mark(tmp_mark).compare(tmp_iter) if con>0 or (con==0 and tmp_iter.ends_tag(self.tag_clip)): content = True else: content = False else: tmp_iter = iter.copy() tmp_iter.forward_to_tag_toggle(self.tag_clip) con = self.text.get_end_iter().compare(tmp_iter) if con>0 or (con==0 and tmp_iter.ends_tag(self.tag_clip)): content = True else: content = False #If the conditions check out, pull the content. if count<1 and not iter.is_end() and content: if not iter.begins_tag(): iter.forward_to_tag_toggle(None) if key=="subtitle": middle_iter = iter.copy() middle_iter.forward_to_tag_toggle(None) last_iter = middle_iter.copy() speaker = self.text.get_text(iter,middle_iter) iter = middle_iter else: last_iter = iter.copy() last_iter.forward_to_tag_toggle(None) desc = self.text.get_text(iter,last_iter) if desc != "/placeholder/": desc = desc.strip("/") if len(times)>x: clip = [time,times[x],desc] else: if self.pipeline.query_position(gst.FORMAT_TIME)[0]/gst.MSECOND > to_ms(time): clip = [time,format_time(self.pipeline.query_position(gst.FORMAT_TIME)[0]/gst.MSECOND),desc] else: clip = [time,format_time(self.pipeline.query_duration(gst.FORMAT_TIME)[0]/gst.MSECOND),desc] if key=="subtitle": clip.append(speaker) export[tracks[key]].append(clip) x += 1 return export def export_txt(self,filename): #Check for valid filename. if filename[filename.rfind("."):].lower()!=".txt": #not correct extension/no extension if filename.rfind(".")>len(filename)-6: #incorrect extension removed filename = filename.rsplit(".",1)[0] filename = filename + ".txt" f = open(filename,"w") f.write(self.text.get_text(self.text.get_start_iter(),self.text.get_end_iter(),False)) f.close() def export_srt(self,filename,data): #Check for valid filename. tracks = "subtitle-en" if filename[filename.rfind("."):].lower()!=".srt": #not correct extension/no extension if filename.rfind(".")>len(filename)-6: #incorrect extension removed filename = filename.rsplit(".",1)[0] tracks = "all" for track in data.keys(): if track == tracks or tracks == "all": x = 1 file_buffer = '' for clip in data[track]: file_buffer += str(x) + "\n" + clip[0].replace(".",",") + " --> " + clip[1].replace(".",",") + "\n" + clip[2] + "\n\n" x += 1 if tracks!="all": f = open(filename,"w") f.write(file_buffer) f.close() else: f = open(filename + "_" + track + ".srt","w") f.write(file_buffer) f.close() def export_cmml(self,filename,data): # generate header. cmml_file = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n\n<cmml>\n\n' if (self.current_file.find('/') != -1): stream_id = self.current_file.rsplit("/")[1].rsplit(".")[0] else: stream_id = self.current_file.rsplit(".")[0] stream_type = mimetypes.guess_type(self.current_file)[0] cmml_file += '<stream id="%s" timebase="npt:0">\n\t<import contenttype="%s" src="%s" start="npt:0">\n\t</import>\n</stream>\n\n' % (stream_id,stream_type,self.current_file) # Add each clip. x = 1 for track in data.keys(): for clip in data[track]: if track.startswith("subtitle"): cmml_file += '<clip id="%s" title="%s" start="%s" end="%s" track="%s">\n' % (x,clip[3].capitalize(),clip[0],clip[1],track) else: cmml_file += '<clip id="%s" start="%s" end="%s">\n' % (x,clip[0],clip[1]) cmml_file += '\t<desc>%s</desc>\n' % (clip[2].strip()) cmml_file += '</clip>\n\n' x += 1 cmml_file += '</cmml>' if filename[filename.rfind("."):].lower()!=".cmml": #not correct extension/no extension if filename.rfind(".")>len(filename)-6: #incorrect extension removed filename = filename.rsplit(".",1)[0] filename = filename + ".cmml" f = open(filename,"w") f.write(cmml_file) f.close() def import_cmml(self, filename): import xml.dom.minidom f = open(filename,"r") doc = xml.dom.minidom.parse(f) last_speaker = '' last_time = {} last_title = '' file_imports = doc.getElementsByTagName('import') for node in file_imports: self.pipeline.set_state(gst.STATE_NULL) self.current_file = node.getAttribute('src') self.playbin.props.uri = "file://" + self.current_file self.pipeline.set_state(gst.STATE_PAUSED); self.pipeline_duration = 10 clips = doc.getElementsByTagName('clip') self.progress.props.fraction = 0 total_clips = len(clips) x=0 self.pipeline.get_state() # Let pipeline catch up to script. for node in clips: # load data start_time = node.getAttribute('start') end_time = node.getAttribute('end') track = node.getAttribute('track') title = node.getAttribute('title') #don't force child nodes :-) if node.getElementsByTagName('desc')[0].firstChild != None: desc = node.getElementsByTagName('desc')[0].firstChild.nodeValue #Make sure last_time has a record for the track. if not last_time.has_key(track): last_time[track]=0 # Add current entry. if desc != '': if track.startswith('subtitle') and not title==last_speaker: self.text.insert(self.text.get_end_iter(),"\n\n"+title+":\n") last_speaker = title elif track.startswith('subtitle'): #Handles spaces between clips. self.text.insert(self.text.get_end_iter()," ") else: self.text.insert(self.text.get_end_iter(),"\n\nChapter " + str(len(self.times["chapter"]["start"])+1) + ": ") #Create start mark. if track.startswith('subtitle'): self.times["subtitle"]['start'].append(start_time) self.last_type_def = title + ":" else: self.times["chapter"]['start'].append(start_time) self.last_type_def = ')' if last_time[track] != start_time: #Avoid duplicate mark if same mark time ended last clip. if track.startswith('subtitle'): mark = self.text.create_mark('subtitle-' + start_time,self.text.get_end_iter(),True) else: mark = self.text.create_mark('chapter-' + start_time,self.text.get_end_iter(),True) mark.set_visible(self.visible_marks) #Insert and tag speaker string. (will be invisible) self.text.insert_with_tags(self.text.get_end_iter(),title,self.tag_speaker) #Insert text. self.text.insert_with_tags(self.text.get_end_iter(),desc.strip(),self.tag_clip) if end_time != None: if track.startswith('subtitle'): self.times["subtitle"]['end'].append(end_time) mark = self.text.create_mark("subtitle-" + end_time,self.text.get_end_iter(),True) else: self.times["chapter"]['end'].append(end_time) mark = self.text.create_mark("chapter-" + end_time,self.text.get_end_iter(),True) mark.set_visible(self.visible_marks) ended_previous = True else: ended_previous = False last_time[track] = end_time elif not ended_previous: if track.startswith('subtitle'): self.times["subtitle"]['end'].append(start_time) mark = self.text.create_mark("subtitle-" + start_time,self.text.get_end_iter(),True) else: self.times["chapter"]['end'].append(start_time) mark = self.text.create_mark("chapter-" + start_time,self.text.get_end_iter(),True) mark.set_visible(self.visible_marks) x += 1 self.progress.props.fraction = x/total_clips self.pipeline_duration = self.pipeline.query_duration(gst.FORMAT_TIME)[0]/gst.MSECOND if (end_time!="" and to_ms(end_time) < self.pipeline_duration-3*60*1000) or to_ms(start_time) < self.pipeline_duration-4*60*1000: #If last clip is towards end, skip and play from the beginning. If not go to the last clip. if end_time!="": self.seek(end_time) self.start_time.set_text(end_time) else: self.seek(start_time) self.start_time.set_text(start_time) f.close() return def import_smil(self,filename): import xml.dom.minidom f = open(filename,"r") doc = xml.dom.minidom.parse(f) clips = doc.getElementsByTagName('video') self.progress.props.fraction = 0 total_clips = len(clips) last_time=None x=0 for node in clips: # load data start_time = format_time(int(node.getAttribute('clipBegin'))/29.97*1000) end_time = format_time(int(node.getAttribute('clipEnd'))/29.97*1000) # If a gap exists between previous end and current start add a mark to end previous clip. self.times["chapter"]["start"].append(start_time) if last_time!=start_time: mark = self.text.create_mark("chapter-" + start_time,self.text.get_end_iter(),True) mark.set_visible(self.visible_marks) # Add current entry. self.text.insert(self.text.get_end_iter(),"\n\nChapter " + str(x+1) + ": ") self.last_type_def = ')' self.text.insert_with_tags(self.text.get_end_iter(),'/placeholder/',self.tag_clip) #Add end mark. self.times["chapter"]["end"].append(end_time) mark = self.text.create_mark("chapter-" + end_time,self.text.get_end_iter(),True) mark.set_visible(self.visible_marks) last_time = end_time x += 1 self.progress.props.fraction = x/total_clips self.current_track = "chapter" f.close() return def bus_call(self, bus, message): if message.type == gst.MESSAGE_ERROR: print message.parse_error()[1] elif message.type == gst.MESSAGE_STATE_CHANGED and (message.parse_state_changed()[0]==gst.STATE_PAUSED or message.parse_state_changed()[0]==gst.STATE_PLAYING) and message.parse_state_changed()[2]==gst.STATE_VOID_PENDING and message.src == self.pipeline: print message.parse_state_changed() self.push_text() else: print "Unhandled: " + str(message.type) return True #Below are functions generally called once. def __init__(self): # Setup the program. The gui and gstreamer pipeline. self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) self.window.set_title("Annotool") self.window.set_default_size(500,300) self.vbox = gtk.VBox() #Window accelerators/hotkeys. self.accels = gtk.AccelGroup() self.accels.connect_group(gtk.gdk.keyval_from_name("o"),gtk.gdk.CONTROL_MASK,gtk.ACCEL_VISIBLE,self.open_file) # ctrl - o self.accels.connect_group(gtk.gdk.keyval_from_name("s"),gtk.gdk.CONTROL_MASK,gtk.ACCEL_VISIBLE,self.save_file) # ctrl - s self.accels.connect_group(gtk.gdk.keyval_from_name("t"),gtk.gdk.CONTROL_MASK,gtk.ACCEL_VISIBLE,self.toggle_track) # ctrl - t self.accels.connect_group(gtk.gdk.keyval_from_name("b"),gtk.gdk.CONTROL_MASK,gtk.ACCEL_VISIBLE,self.previous_clip) # ctrl - b self.accels.connect_group(gtk.gdk.keyval_from_name("n"),gtk.gdk.CONTROL_MASK,gtk.ACCEL_VISIBLE,self.next_clip) # ctrl - n self.accels.connect_group(gtk.gdk.keyval_from_name("r"),gtk.gdk.CONTROL_MASK,gtk.ACCEL_VISIBLE,self.restart_clip) # ctrl - r self.accels.connect_group(gtk.gdk.keyval_from_name("p"),gtk.gdk.CONTROL_MASK,gtk.ACCEL_VISIBLE,self.just_play) # ctrl - p self.accels.connect_group(gtk.gdk.keyval_from_name("i"),gtk.gdk.CONTROL_MASK,gtk.ACCEL_VISIBLE,self.import_file) # ctrl - i self.accels.connect_group(gtk.gdk.keyval_from_name("Return"),0,gtk.ACCEL_VISIBLE,self.enter_activated) # enter self.window.add_accel_group(self.accels) self.sw = gtk.ScrolledWindow() self.sw.set_policy(gtk.POLICY_NEVER,gtk.POLICY_AUTOMATIC) self.sw.set_shadow_type(gtk.SHADOW_IN) self.text = gtk.TextBuffer() self.tag_clip = self.text.create_tag("clip",underline=pango.UNDERLINE_SINGLE) self.tag_speaker = self.text.create_tag("speaker",invisible=True) self.tag_current = self.text.create_tag("current",foreground="red") self.textView = gtk.TextView(self.text) self.textView.set_editable(True) self.textView.set_wrap_mode(gtk.WRAP_WORD) self.textView.props.can_focus = True self.sw.add(self.textView) self.hbox = gtk.HBox() self.start_time = gtk.Entry() self.start_time.set_width_chars(12) self.start_time.set_text("xx:xx:xx.xxx") self.start_time.set_max_length(12) self.start_time.set_size_request(92,-1) self.start_time.props.can_focus = False self.end_time = gtk.Entry() self.end_time.set_width_chars(12) self.end_time.set_text("xx:xx:xx.xxx") self.end_time.set_max_length(12) self.end_time.set_size_request(92,-1) self.end_time.props.can_focus = False self.progress = gtk.ProgressBar() self.progress.set_orientation(gtk.PROGRESS_LEFT_TO_RIGHT) self.progress.set_size_request(280, 20) self.progress.props.text = "Nothing loaded." self.progress.set_ellipsize(pango.ELLIPSIZE_END) self.clip_text = gtk.Entry() self.hbox.pack_start(self.start_time, False, False) self.hbox.pack_start(self.end_time, False, False) self.hbox.pack_start(self.clip_text, True, True) self.hbox.set_spacing(6) self.vbox.pack_start(self.sw,True,True) self.vbox.pack_start(self.hbox,False,False) self.vbox.pack_start(self.progress,False,False) self.vbox.set_spacing(6) self.window.add(self.vbox) self.window.set_border_width(6) #gstreamer setup self.use_overlays = True self.pipeline = gst.Pipeline() self.playbin = gst.element_factory_make ("playbin") self.playbin.props.audio_sink = gst.element_factory_make ("alsasink") if self.use_overlays: self.video_process = gst.Bin() self.timeoverlay = gst.element_factory_make ("timeoverlay") self.textoverlay = gst.element_factory_make ("textoverlay") self.xvimagesink = gst.element_factory_make ("xvimagesink") self.effect = gst.element_factory_make ("agingtv") self.colorspace = gst.element_factory_make("ffmpegcolorspace"); self.colorspace2 = gst.element_factory_make("ffmpegcolorspace"); self.queue = gst.element_factory_make ("queue") self.video_process.add (self.timeoverlay,self.textoverlay,self.xvimagesink,self.queue,self.effect,self.colorspace,self.colorspace2) self.text_pad = gst.Pad ("mysrc", gst.PAD_SRC) self.text_pad.set_caps(gst.Caps("text/plain")) self.text_pad.link(self.queue.get_pad ("sink")) self.text_pad.activate_push(True) self.timeoverlay.link(self.textoverlay) self.queue.get_pad("src").link(self.textoverlay.get_pad ("text_sink")) self.textoverlay.link(self.colorspace) self.colorspace.link(self.effect) self.effect.link(self.colorspace2) self.colorspace2.link (self.xvimagesink) self.video_process.add_pad (gst.GhostPad ("sink",self.timeoverlay.get_pad ("video_sink"))) self.playbin.props.video_sink = self.video_process else: self.playbin.props.video_sink = gst.element_factory_make ("cacasink") self.playbin.props.vis_plugin = gst.element_factory_make ("goom") self.pipeline.add (self.playbin) bus = self.pipeline.get_bus() bus.add_watch (self.bus_call) #store times self.times = {"chapter": {"start": [], "end" : [] },"subtitle": { "start":[], "end":[]} } self.last_type_def = "$" self.current_track = "subtitle" self.visible_marks = False self.pipeline_duration = 1000 #Something non-zero to avoid non-zero errors. self.mode = 'transcribe' self.show_keystrokes = False self.debug = False self.window.show_all() self.clip_text.grab_focus() def main(self): gtk.main()if __name__ == "__main__": annotool = Annotool() annotool.main()