#! /usr/bin/env python
# -*- coding: utf-8 -*-

# Segments.py
# Copyright (C) 2006 Artem Popov <artfwo@gmail.com>
#
# This example demonstrates segment seeking
# and seamless looping within playbin.

import pygst
pygst.require ("0.10")
import gst

import pygtk
pygtk.require ("2.0")
import gobject

class Looper (gobject.GObject):
	__gproperties__ = {
		"loop": (gobject.TYPE_BOOLEAN,
			"loop",
			"Whether to loop the segment",
			False,
			gobject.PARAM_READWRITE),
		"start-pos": (gobject.TYPE_UINT64,
			"start position",
			"The segment start marker",
			0,
			0xfffffffffffffff, # max long possible
			0,
			gobject.PARAM_READWRITE),
		"stop-pos": (gobject.TYPE_UINT64,
			"stop position",
			"The segment stop marker",
			0,
			0xfffffffffffffff, # max long possible
			0,
			gobject.PARAM_READWRITE),
	} # __gproperties__
	
	__gsignals__ = {
		"stopped": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
		"position-updated": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_FLOAT,)),
		"error": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
	} # __gsignals__
	
	def __init__ (self, location = None):
		gobject.GObject.__init__ (self)
		
		self.__playbin = gst.element_factory_make ("playbin")
		self.__playbin.props.video_sink = gst.element_factory_make ("fakesink")
		
		bus = self.__playbin.get_bus ()
		bus.add_watch (self.__on_bus_message)
		
		self.__loop = False
		self.__start_pos = 0
		self.__stop_pos = 0
		
		self.__timeout_id = 0
		
		if location:
			self.load (location)
	
	def load (self, location):
		self.__playbin.props.uri = location
		self.__start_position = 0
		self.__stop_position = 0
	
	def set_segment (self, start, stop):
		self.props.start_pos = start
		self.props.stop_pos = stop
	
	def play (self):
		if not (self.__start_pos or self.__stop_pos):
			raise RuntimeError, "Cannot start playback, segment was not set!"
		
		self.__playbin.set_state (gst.STATE_PLAYING)
	
	def stop (self, silent = False):
		self.__playbin.set_state (gst.STATE_NULL)
		if not silent:
			self.emit ("stopped")
	
	def do_get_property (self, property):
		if property.name == "loop":
			return self.__loop
		elif property.name == "start-pos":
			return self.__start_pos
		elif property.name == "stop-pos":
			return self.__stop_pos
		else:
			raise AttributeError, "Unknown property %s" % property.name
	
	def do_set_property (self, property, value):
		if property.name == "loop":
			self.__loop = value
		elif property.name == "start-pos":
			self.__start_pos = value
		elif property.name == "stop-pos":
			self.__stop_pos = value
		else:
			raise AttributeError, "Unknown property %s" % property.name
	
	def do_stopped (self):
		if self.__timeout_id:
			gobject.source_remove (self.__timeout_id)
			self.__timeout_id = 0
	
	def __seek (self, start, stop, flush):
		flags = gst.SEEK_FLAG_SEGMENT | gst.SEEK_FLAG_ACCURATE
		if flush:
			flags = flags | gst.SEEK_FLAG_FLUSH
		self.__playbin.seek (1.0, gst.FORMAT_TIME, flags,
			gst.SEEK_TYPE_SET, start,
			gst.SEEK_TYPE_SET, stop)
	
	def __on_timeout (self):
		position = self.__playbin.query_position (gst.FORMAT_TIME) [0]
		self.emit ("position-updated", float (position))
		return True
	
	def __on_bus_message (self, bus, message):
		if message.type == gst.MESSAGE_ERROR:
			error, debug = message.parse_error ()
			self.stop () # this looks neccessary here
			self.emit ("error", (error, debug))
		
		elif message.type == gst.MESSAGE_NEW_CLOCK:
			# we connect the timeout handler here to be sure that further queries succeed
			interval = int ((self.__stop_position - self.__start_position) / (2 * gst.SECOND) + 50)
			self.__timeout_id = gobject.timeout_add (interval, self.__on_timeout)
		
		elif message.type == gst.MESSAGE_STATE_CHANGED:
			old_state, new_state, pending = message.parse_state_changed ()
			if old_state == gst.STATE_READY and new_state == gst.STATE_PAUSED and message.src == self.__playbin:
				self.__seek (self.__start_pos, self.__stop_pos, True)
		
		elif message.type == gst.MESSAGE_SEGMENT_DONE:
			if self.__loop:
				self.__seek (self.__start_pos, self.__stop_pos, False)
			else:
				src = self.__playbin.get_property ("source")
				pad = src.get_pad ('src')
				pad.push_event (gst.event_new_eos ())
				
				# this is the good old way:
				#
				# pads = src.src_pads ()
				# while True:
				# 	try:
				# 		pad = pads.next ()
				# 		pad.push_event (gst.event_new_eos ())
				# 	except:
				# 		break
		
		elif message.type == gst.MESSAGE_EOS:
			self.stop ()
		
		return True

mainloop = gobject.MainLoop ()

def on_looper_stopped (looper):
	mainloop.quit ()

def on_looper_pos_updated (looper, position):
	print round (position / gst.SECOND, 2)

def on_looper_error (looper, error_tuple):
	error, debug = error_tuple
	print "\n\n%s\n\n%s\n\n" % (error, debug)
	mainloop.quit ()

if __name__ == "__main__":
	import sys
	if len (sys.argv) != 5:
		print "Usage: %s <filename|uri> <start_seconds> <stop_seconds> <loop = 0|1>" % sys.argv [0]
		sys.exit (1)
	
	if "://" in sys.argv [1]:
		uri = sys.argv [1]
	else:
		import os.path
		uri = "file://" + os.path.abspath (sys.argv [1])
	
	looper = Looper (uri)
	
	looper.props.start_pos = long (sys.argv [2]) * gst.SECOND
	looper.props.stop_pos = long (sys.argv [3]) * gst.SECOND
	looper.props.loop = int (sys.argv [4])
	
	looper.connect ("stopped", on_looper_stopped)
	looper.connect ("position-updated", on_looper_pos_updated)
	looper.connect ("error", on_looper_error)
	
	looper.play ()
	mainloop.run ()