#!/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 pygst pygst.require ("0.10") import types import gobject import sys import gst def format_time(time): hour = str(int(time)/3600) minute = int(time)/60%60 if minute < 10: minute = "0" + str(minute) else: minute = str(minute) seconds = int(time)%60 if seconds < 10: seconds = "0" + str(seconds) else: seconds = str(seconds) return hour + ":" + minute + ":" + seconds def pipe_error(bin, element, error, debug): print "error:" + debug class AnxPlayer: def __init__(self): self.print_error = False self.print_debug = True self.print_debug_level = 0 self.bin = gst.Pipeline() self.playbin = gst.element_factory_make ("playbin") self.playbin.props.audio_sink = gst.element_factory_make ("alsasink") self.playbin.props.video_sink = gst.element_factory_make ("ximagesink") self.bin.add (self.playbin) bus = self.bin.get_bus() bus.add_watch (self.bus_call) self.pos = -1 self.between = False self.current = -1 self.on_clip_change = None self.on_position_change = None self.on_duration_change = None self.on_tag_info_ready = None self.on_file_finish = None self.duration = 0 self.timeout_id = -1 self.nextclip_timeout_id = -1 self.tag_info_map = {} def update_time(self): time = -1 try: time = self.bin.query_position(gst.FORMAT_TIME)[0]/gst.SECOND except: pass if time >= 0: self.on_position_change(time, self.duration) return True def bus_call (self, bus, message): if message.type == gst.MESSAGE_TAG: self.debug("Tag!") def temp (key,tag): if ( key == "title" ): self.tag_info_map[self.current].title = tag elif key == "cmml-head": self.debug(type(tag)) pass elif key == "cmml-clip": self.debug(type(tag)) self.debug(tag.props.id) taglist = message.parse_tag() for key in taglist.keys(): temp(key,taglist[key]) elif message.type == gst.MESSAGE_ERROR: self.error(message.parse_error()) elif message.type == gst.MESSAGE_EOS: self.on_file_finish(self) return True def switch_source(self,filename): self.debug(self.bin.set_state(gst.STATE_NULL)) self.debug(self.bin.get_state()) #block until the state is really changed self.playbin.props.uri = "file://" + filename self.pause() #we'll be in a state where we can seek, if necessary self.duration = self.playbin.props.audio_sink.query_duration(gst.FORMAT_TIME)[0]/gst.SECOND self.debug("Update duration: " + str(self.duration) + " sec") self.on_duration_change(self.duration) self.pos = -1 self.current = filename #new clip info may have become available. TODO: Change this to be sent as it is available self.on_tag_info_ready( filename, self.tag_info_map[self.current] ) self.on_clip_change(self.current,self.pos) def load_tag_info(self,filename): #create a fake pipeline which we'll play, gathering all the tags and storing this information fake = gst.Pipeline () filesrc = gst.element_factory_make ("filesrc") decodebin = gst.element_factory_make ("decodebin") fakesink = gst.element_factory_make ("fakesink") filesrc.set_property ("location", filename) def on_unknown_type ( decodebin, pad, arg1, fake, fakesink): caps_string = str (pad.get_caps ()) if (caps_string.startswith ("text/x-cmml")): cmmldec = pad.get_parent_element() cmmldec.set_property ("wait-clip-end-time", True) ghost = gst.GhostPad ("casper", pad) decodebin.add_pad (ghost) ghost.link (fakesink.get_pad ("sink")) decodebin.connect ("unknown-type", on_unknown_type, fake, fakesink) fake.add (filesrc, decodebin, fakesink) filesrc.link(decodebin) #bus.remove_signal_watch () tag_info = TagInfo(); self.tag_info_map[filename] = tag_info def bus_call (bus, message): if message.type == gst.MESSAGE_EOS: #song is over self.debug("We've come to the end of a file.") self.tag_info_map[filename] = tag_info self.on_tag_info_ready(filename, tag_info) fake.set_state(gst.STATE_NULL) elif message.type == gst.MESSAGE_WARNING: self.debug("whooops, warning: "+message.parse_warning()) elif message.type == gst.MESSAGE_ERROR: self.tag_info_map[filename] = tag_info self.on_tag_info_ready(filename, tag_info) self.error(message.parse_error()) fake.set_state(gst.STATE_NULL) elif message.type == gst.MESSAGE_TAG: def temp (key, tag, tag_info): if key == "cmml-head": pass elif key == "cmml-clip": self.debug("tag: " + str(tag.props.description),1) self.debug("start: " + format_time(tag.props.start_time/gst.SECOND),1) self.debug("end: " + format_time(tag.props.end_time/gst.SECOND),1) tag_info.clip_info.append(tag) #clip info elif key == "title": tag_info.title = tag else: self.debug("Unhandled tag: "+key+" ("+str(tag)+")") taglist = message.parse_tag() for key in taglist.keys(): temp(key, taglist[key], tag_info) return True bus = fake.get_bus () bus.add_watch (bus_call) result = fake.set_state (gst.STATE_PLAYING) def update_clip(self, position): self.debug("Updating clip") if self.pos == -1: self.debug("Before first clip") self.on_clip_change(self.current,-1) self.nextclip_timeout_id = gobject.timeout_add(self.tag_info_map[self.current].clip_info[0].props.start_time/gst.MSECOND, self.clip_starting ) self.debug("Nextclip timeout in: "+format_time(self.tag_info_map[self.current].clip_info[0].props.start_time/gst.SECOND)) return if self.pos == len(self.tag_info_map[self.current].clip_info)-1: self.debug("No more clips") self.on_clip_change(self.current,-1) return end_time = self.tag_info_map[self.current].clip_info[self.pos].props.end_time/gst.MSECOND start_time = self.tag_info_map[self.current].clip_info[self.pos+1].props.start_time/gst.MSECOND self.debug("position: "+format_time(position/1000),2) self.debug("end_time: "+format_time(end_time/1000),2) self.debug("start_time: "+format_time(start_time/1000),2) if end_time <= start_time: self.nextclip_timeout_id = gobject.timeout_add(end_time - int(position), self.clip_ending ) self.debug("Nextclip timeout in: "+format_time((end_time - position)/1000)) else: self.debug("In between clips") self.nextclip_timeout_id = gobject.timeout_add(start_time - position, self.clip_starting ) self.debug("Nextclip timeout in: "+format_time((start_time - position)/1000)) def clip_starting(self): self.debug("Clip started") self.pos+=1 self.on_clip_change(self.current,self.pos) end_time = self.tag_info_map[self.current].clip_info[self.pos].props.end_time/gst.MSECOND position = self.bin.query_position(gst.FORMAT_TIME)[0]/gst.MSECOND self.debug("position: "+format_time(position/1000),2) self.debug("end_time: "+format_time(end_time/1000),2) diff = end_time - position if diff < 0: diff = 0 #fudge the difference to aovid negatives self.on_clip_change(self.current,self.pos) self.nextclip_timeout_id = gobject.timeout_add(diff, self.clip_ending ) self.debug("Nextclip timeout in: "+format_time(diff/1000)) def clip_ending(self): self.debug("Clip ended") if self.pos == len(self.tag_info_map[self.current].clip_info)-1: self.debug("No more clips") self.on_clip_change(self.current,-1) return start_time = self.tag_info_map[self.current].clip_info[self.pos+1].props.start_time/gst.MSECOND position = self.bin.query_position(gst.FORMAT_TIME)[0]/gst.MSECOND self.debug("position: "+format_time(position/1000),2) self.debug("start_time: "+format_time(start_time/1000),2) diff = start_time - position if diff < 0: diff = 0 #fudge the difference to aovid negatives self.on_clip_change(self.current,-1) self.debug("In between clips") self.nextclip_timeout_id = gobject.timeout_add(diff, self.clip_starting ) self.debug("Nextclip timeout in: "+format_time(diff/1000)) return False def play(self): if self.current == -1: return result = self.bin.set_state(gst.STATE_PLAYING) self.bin.get_state() #block until the state is really changed if self.timeout_id == -1: self.timeout_id = gobject.timeout_add(200, self.update_time) if self.nextclip_timeout_id == -1: self.update_clip(self.bin.query_position(gst.FORMAT_TIME)[0]/gst.MSECOND) def pause(self): result = self.bin.set_state(gst.STATE_PAUSED) self.bin.get_state() #block until the state is really changed if self.timeout_id != -1: gobject.source_remove(self.timeout_id) self.timeout_id = -1 if self.nextclip_timeout_id != 1: gobject.source_remove(self.nextclip_timeout_id) self.nextclip_timeout_id = -1 def seek(self,location): if self.current == -1: return result = self.bin.seek(1.0, gst.FORMAT_TIME, gst.SEEK_FLAG_FLUSH | gst.SEEK_FLAG_ACCURATE, gst.SEEK_TYPE_SET, gst.SECOND*location, gst.SEEK_TYPE_NONE, 0) if result == True: clip = self.clip_at(location) if self.on_clip_change != None: self.on_clip_change(self.current, clip[0]) self.on_position_change(location, self.duration) self.pos = clip[1] self.debug("pos is: "+str(self.pos)+" curr_clip is "+str(clip[0])) if self.nextclip_timeout_id != -1: gobject.source_remove(self.nextclip_timeout_id) self.nextclip_timeout_id = -1 self.update_clip(location*1000) self.debug("Seek: " + str(result)) return result def next(self): if self.current == -1 or self.pos == len(self.tag_info_map[self.current].clip_info)-1: return sec = self.tag_info_map[self.current].clip_info[self.pos+1].props.start_time/gst.SECOND self.seek(sec) #seek will update self.pos def prev(self): if self.pos < 1: return sec = self.tag_info_map[self.current].clip_info[self.pos-1].props.start_time/gst.SECOND self.seek(sec) #seek will update self.pos # Returns a tuple containing the current clip position and the last clip at the current position # in the stream. # # These will differ when the stream is between clips. def clip_at(self,location): if self.current == -1: return (-1,-1) clip_end_at = -1 for i in range(0,len(self.tag_info_map[self.current].clip_info)): clip_start_at = self.tag_info_map[self.current].clip_info[i].props.start_time/gst.SECOND clip_end_at = self.tag_info_map[self.current].clip_info[i].props.end_time/gst.SECOND if clip_start_at > location < clip_end_at: return (i-1,i-1) elif location < clip_start_at: return (-1,i-1) if location > clip_end_at: #we're past the end of the last clip return (-1,len(self.tag_info_map[self.current].clip_info)-1) return (len(self.tag_info_map[self.current].clip_info)-1,len(self.tag_info_map[self.current].clip_info)-1) def remove_file(self, filename): del self.tag_info_map[filename] def debug(self, message, level = 0): if self.print_debug == True and level <= self.print_debug_level: print "DEBUG_PLYR: " + str(message) def error(self, message): if self.print_error == True: print "ERROR_PLYR: " + str(message) class TagInfo: def __init__(self): #Stores the following properties of a clip: #anchor_uri, anchor_text, description, empty, end_time, id, img_alt, img_uri, meta, start_time, track self.clip_info = [] self.title = ""