#!/usr/bin/env python #Copyright (C) 2005-2006 Scott Shawcroft # Jason Kivlighn # #This program is free software; you can redistribute it and/or #modify it under the terms of the GNU General Public License #as published by the Free Software Foundation; either version 2 #of 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 of #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #GNU General Public License for more details. # #You should have received a copy of the GNU General Public License #along with this program; if not, write to the Free Software #Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. import sys import pygtk pygtk.require('2.0') import gtk import gobject import gtk.glade import xml.dom.minidom as xml_dom import os import ConfigParser import anxplayer import m3uhandler #To spawn links to webpages import webbrowser #To download images off the web. import urllib2 import string import gst TITLE_COL = 0 POSITION_COL = 1 FILENAME_COL = 2 class VideoWidget: def __init__(self, drawingwidget, player): self.drawing_widget = drawingwidget self.drawing_widget.connect('destroy', self.destroy_cb) self.drawing_widget.connect_after('realize', self.after_realize_cb) self.drawing_widget.set_size_request(400,400) self.player = player self.imagesink = player.playbin.props.video_sink def destroy_cb(self, da): self.set_window_id(0L) # Sort of a hack, but it works for now. def after_realize_cb(self, window): gobject.idle_add(self.frame_video_sink) def frame_video_sink(self): #self.drawing_widget.show() #self.set_window_id(self.drawing_widget.window.xid) pass def set_window_id(self, xid): #self.imagesink.set_xwindow_id(xid) pass def unframe_video_sink(self): #self.drawing_widget.hide() self.set_window_id(0L) def destroy(widget, self): widget.get_parent_window().destroy() def end (widget, self): global loop global player global history if player.current != -1: old_file = playlist.get_value(playlist.get_iter(player.current), FILENAME_COL) history[old_file][0] = int(xml.get_widget("slider").get_value()) loop.quit() def call_play(player): videowidget.frame_video_sink() player.play() def play(widget): global player global playlist global videowidget if len(player.tag_info_list) != 0: #use the first file if nothing is already selected if player.current == -1: player.switch_source( playlist.get_value(playlist.get_iter_first(), FILENAME_COL), 0 ) player.seek(playlist.get_value(playlist.get_iter_first(), POSITION_COL)) call_play(player) def pause(widget): global player result = player.pause(); def seek_next(widget): global player player.next() def seek_prev(widget): global player player.prev() def seek_clip(treeview, path, column): global player,history treemodel = treeview.get_model() #get the topmost item parent_iter = treemodel.get_iter(path) while treemodel.iter_parent(parent_iter) != None: parent_iter = treemodel.iter_parent(parent_iter) if path[0] != player.current: if player.current != -1: old_file = treemodel.get_value(treemodel.get_iter(player.current), FILENAME_COL) position = int(xml.get_widget("slider").get_value()) history[old_file][0] = position playlist.set_value(treemodel.get_iter(player.current), 1, position) videowidget.unframe_video_sink() player.switch_source(treemodel.get_value(parent_iter, FILENAME_COL), path[0]) player.seek(treemodel.get_value(treemodel.get_iter(path), POSITION_COL)) call_play(player) def slider_seek_play(widget, event): global player value = widget.get_value() call_play(player) player.seek(value) def slider_moved(widget,value): global player if value >= 0 and value <= player.duration: update_gui_time(value,player.duration) update_clip(player.current,player.chapter_at(value)[0]) def slider_pause(widget, event): global player player.pause() def update_clip(current, chapter): global playlist_view global playlist global xml tree_selection = playlist_view.get_selection() if chapter == -1: tree_selection.unselect_all() xml.get_widget("description").set_text('No Clip') xml.get_widget("link").set_label('No Link') else: tree_selection.select_path(playlist.get_path(playlist.iter_nth_child(playlist.iter_nth_child(None,current),chapter))) desc = player.tag_info_list[current].clip_info[chapter].props.description if desc != None: xml.get_widget("description").set_text(player.tag_info_list[current].clip_info[chapter].props.description) else: xml.get_widget("description").set_text("No Description") link_text = player.tag_info_list[current].clip_info[chapter].props.anchor_text if link_text != None: xml.get_widget("link").set_label(link_text) else: xml.get_widget("link").set_label('No Link.') img_src = player.tag_info_list[current].clip_info[chapter].props.img_uri if img_src != None: try: img_file = urllib2.urlopen(img_src) except: xml.get_widget("image").set_from_file("annoamp.png") return pbl = gtk.gdk.PixbufLoader() pbl.write(img_file.read()) img_pb = pbl.get_pixbuf() pbl.close() xml.get_widget("image").set_from_pixbuf(img_pb) else: xml.get_widget("image").set_from_file("annoamp.png") def playlist_expanded(treeview,iter,path): #TODO: only update if expanding the playlist of what is currently playing update_clip(player.current,player.chapter_at(xml.get_widget("slider").get_value())[0]) def playlist_collapsed(treeview,iter,path): #TODO: when collapsed, highlight the currently playing file pass def update_gui_time(time, duration): xml.get_widget("time").set_text(anxplayer.format_time(time) + "/" + anxplayer.format_time(duration)) xml.get_widget("slider").set_value(time) def update_gui_duration(duration): xml.get_widget("slider").set_range(0, duration) def open_playlist(widget): global playlist chooser = gtk.FileChooserDialog("Open a playlist.", None, gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK)) if chooser.run() == gtk.RESPONSE_OK: filename = chooser.get_filename() chooser.destroy() files = m3uhandler.load_m3u(filename) for file in files: load_file(file[1]) else: chooser.destroy() def save_playlist(widget): global playlist chooser = gtk.FileChooserDialog("Save a playlist.", None, gtk.FILE_CHOOSER_ACTION_SAVE, (gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_SAVE,gtk.RESPONSE_OK)) if chooser.run() == gtk.RESPONSE_OK: filelist = [] iter_next = playlist.get_iter_first() while iter_next != None: filelist.append(playlist.get_value(iter_next, FILENAME_COL)) iter_next = playlist.iter_next(iter_next) m3uhandler.save_m3u(chooser.get_filename(),filelist) chooser.destroy() def add_file(widget): chooser = gtk.FileChooserDialog("Add a file.", None, gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK)) if chooser.run() == gtk.RESPONSE_OK: file = chooser.get_filename() chooser.destroy() load_file(file) else: chooser.destroy() #Adds additional information to the play list treeview. def add_file_p2(filename, tag_info): global playlist iter_next = playlist.get_iter_first() parent = None while iter_next != None: if playlist.get_value(iter_next,FILENAME_COL) == filename: parent = iter_next break iter_next = playlist.iter_next(iter_next) if (playlist.iter_n_children(parent) == 0): for tag in tag_info.clip_info: playlist_str = anxplayer.format_time(int(tag.props.start_time/gst.SECOND)) if tag.props.description != None: playlist_str += " - " + tag.props.description else: playlist_str += " - " + tag.props.id playlist.append(parent, [playlist_str, int(tag.props.start_time/gst.SECOND), 0]) if ( tag_info.title != None ): playlist.set_value( parent, 0, tag_info.title ) return playlist.get_path(parent)[0] def remove_file (widget): global playlist_view global playlist tree_selection = playlist_view.get_selection() selected = tree_selection.get_selected_rows() model = selected[0] for filepath in selected[1]: if len(filepath) == 1: fileiter = model.get_iter(filepath) filename = playlist.get_value(fileiter, FILENAME_COL) playlist.remove(fileiter) player.remove_file(filename) def volume_changed(widget): #the volume property ranges from 0-4 player.playbin.props.volume = xml.get_widget("volume_scale").get_value()*4/100 def load_settings( player ): config = ConfigParser.ConfigParser() config.read([os.path.expanduser('~/.annoamp.conf')]) if config.has_option("DEFAULT","lastOpened"): files = config.get("DEFAULT","lastOpened").split("\n") for file in files: if os.path.exists(file): load_file(file) else: print "File: " + file + " cannot be found." def save_settings( player ): sep = "\n" filelist = [] iter_next = playlist.get_iter_first() while iter_next != None: filelist.append(playlist.get_value(iter_next, FILENAME_COL)) iter_next = playlist.iter_next(iter_next) config = ConfigParser.ConfigParser() config.set("DEFAULT","lastOpened",sep.join(filelist)) config_file = os.path.expanduser('~/.annoamp.conf') config.write(file(config_file,'w')) def load_file( filename ): global playlist global history #Add entry to history if necessary. if history.has_key(filename): parent = playlist.append(None, [filename, history[filename][0], filename]) else: history[filename] = [0, 0, 0] parent = playlist.append(None, [filename, 0, filename]) player.load_file_info(filename) def open_URL(widget): webbrowser.open(player.tag_info_list[player.current].clip_info[player.pos].props.anchor_uri) def get_history(): history = {} file_obj = None try: file_obj = open(os.path.expanduser('~/.annoamp_history'), "r") except: print "No history file." if file_obj != None: file_list = file_obj.readlines() file_obj.close() history = {} for line in file_list: temp, listens = string.split(line, "|") filename, time = string.split(temp, "?t=") history[filename] = [int(time), int(string.strip(listens))] return history def write_history(history): content="" for key in history.keys(): #Skip unplayed files since their state is default upon load. if history[key][0]!=0: content += str(key) + "?t=" + str(history[key][0]) + "|" + str(history[key][1]) + "\n" elif history[key][1]!=0: content += str(key) + "?t=" + str(history[key][0]) + "|" + str(history[key][1]) + "\n" file_obj = open(os.path.expanduser('~/.annoamp_history'), "w") file_obj.write(content) file_obj.close() return def file_finish(player): global history global playlist finished = playlist.get_iter(player.current) old_file = playlist.get_value(finished, FILENAME_COL) history[old_file][0] = 0 history[old_file][1] = 1 + int(history[old_file][1]) next_file = playlist.iter_next(finished) if next_file!=None: player.switch_source(playlist.get_value(next_file, FILENAME_COL), player.current+1) #player.seek(playlist.get_value(playlist.get_iter(path), 1)) #May want to resume. player.play() return playlist = gtk.TreeStore(str, int, str) loop = gobject.MainLoop () xml = gtk.glade.XML('annoamp.glade') xml.signal_autoconnect({ 'destroy' : end, 'play' : play, 'pause' : pause, 'seek_next' : seek_next, 'seek_prev' : seek_prev, 'seek_clip' : seek_clip, 'slider_pause' : slider_pause, 'slider_seek_play' : slider_seek_play, 'slider_moved' : slider_moved, 'open_playlist' : open_playlist, 'add_file' : add_file, 'save_playlist' : save_playlist, 'playlist_expanded' : playlist_expanded, 'volume_changed' : volume_changed, 'open_URL' : open_URL, 'remove_file' : remove_file }) playlist_view = xml.get_widget("playlist") tvcolumn = gtk.TreeViewColumn('Playlist') text_render = gtk.CellRendererText() tvcolumn.pack_start(text_render, True) tvcolumn.set_attributes(text_render, text=0) playlist_view.append_column(tvcolumn) playlist_view.set_model(playlist) xml.get_widget("image").set_from_file("annoamp.png") xml.get_widget("main").show() player = anxplayer.AnxPlayer() player.on_clip_change = update_clip player.on_position_change = update_gui_time player.on_duration_change = update_gui_duration player.on_file_added = add_file_p2 player.on_file_finish = file_finish videowidget = VideoWidget(xml.get_widget("drawingarea1"),player) xml.get_widget("volume_scale").set_value(1*100/4) history = get_history() for i in range(1,len(sys.argv)): load_file(sys.argv[i]) if len(sys.argv) == 1: load_settings(player) loop.run() save_settings(player) write_history(history)