#!/usr/bin/env python
# vim: ai ts=4 sts=4 et sw=4

#    Copyright (c) 2007 Intel Corporation
#
#    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; version 2 of the License
#
#    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, os, os.path, string
import pygtk, gtk, gobject
import pygst
pygst.require("0.10")
import gst
import dbus
import dbus.service
import dbus.glib

import timeit
import types

import re
from xml.dom import minidom	
from optparse import OptionParser

# for video frame feature
import Image, ImageStat
from threading import Event
class VideoFrameError(Exception):
    def __init__(self, uri, error_message=None):
        self.uri = uri
        self.error_message = error_message


class VideoSinkBin(gst.Bin):
    def __init__(self, needed_caps):
        self.reset()
        gst.Bin.__init__(self)
        self._capsfilter = gst.element_factory_make('capsfilter', 'capsfilter')

        self.set_caps(needed_caps)
        self.add(self._capsfilter)

        fakesink = gst.element_factory_make('fakesink', 'fakesink')
        fakesink.set_property("sync", False)
        self.add(fakesink)
        self._capsfilter.link(fakesink)

        pad = self._capsfilter.get_pad("sink")
        ghostpad = gst.GhostPad("sink", pad)

        pad2probe = fakesink.get_pad("sink")
        pad2probe.add_buffer_probe(self.buffer_probe)

        self.add_pad(ghostpad)
        self.sink = self._capsfilter

    def set_current_frame(self, value):
        self._current_frame = value

    def set_caps(self, caps):
        gst_caps = gst.caps_from_string(caps)
        self._capsfilter.set_property("caps", gst_caps)

    def get_current_frame(self):
        frame = self._current_frame
        self._current_frame = None
        return frame

    def buffer_probe(self, pad, buffer):
        caps = buffer.caps
        if caps != None:
            s = caps[0]
            self.width = s['width']
            self.height = s['height']
        if self.width != None and self.height != None and buffer != None:
            self.set_current_frame(buffer.data)
        return True

    def reset(self):
        self.width = None
        self.height = None
        self.set_current_frame(None)

gobject.type_register(VideoSinkBin)

class VideoFramePlayer:
    def __init__(self):
        self._pipeline = gst.element_factory_make('playbin', 'playbin')
        caps = "video/x-raw-rgb,bpp=24,depth=24"
        self._sink = VideoSinkBin(caps)
        self._blocker = Event()
        self._pipeline.set_property("video-sink", self._sink)
        self._pipeline.set_property('volume', 0)


    def _play_for_frame(self,duration , sink_size ):
        ## Maybe we should set a timer, so that we don't play the whole movie?
	#print "process in play_for_frame"
        id = None
        self._img = None

        if duration >= 250000:
            self._every = 25
        elif duration >= 200000:
            self._every = 15
        elif duration >= 10000:
            self._every = 10
        elif duration >= 5000:
            self._every = 5
        else:
            self._every = 1


        self._every_co = self._every
        self._counter = 5
        #callback function
        def buffer_probe(pad, buffer):
	    print "-->buffer_probe in _play_for_frame"
            #if self._every_co < self._every:
                #self._every_co += 1
                #return
            #self._every_co = 0

            try: 
                img = Image.frombuffer("RGB", sink_size, buffer, "raw", "RGB",0, 1)
            except Exception, e:
                print "Invalid frame"
            else:
                if img.mode != 'RGBA':
                    img = img.convert(mode='RGBA')
                self._img = img
                self._sink.reset()
                pad.remove_buffer_probe(id)
                self._blocker.set()
                return

            #self._counter -= 1
            #if self._counter <= 0:
            	#if self._img:
			#if img.mode != 'RGBA':
                    		#img = img.convert(mode='RGBA')
            self._sink.reset()
            pad.remove_buffer_probe(id)
            self._blocker.set()


        pad = self._sink.get_pad('sink')
        id = pad.add_buffer_probe(buffer_probe)
        self.set_state_blocking(self._pipeline, gst.STATE_PLAYING)
        self._blocker.wait()
        self._pipeline.set_state(gst.STATE_NULL)
	if self._img:
            self._img.save( self.output_file, 'png')
	    return self.output_file
	else:
	    return ""  

    def _seek_for_frame(self, duration , sink_size):
        frame_locations = [ 1.0 / 3.0, 2.0 / 3.0, 0.1, 0.9, 0.5 ]
        img=None
        for location in frame_locations:
            abs_location = int(location * duration)
            event = self._pipeline.seek(1.0, gst.FORMAT_TIME,
                                        gst.SEEK_FLAG_FLUSH | gst.SEEK_FLAG_KEY_UNIT,
                                        gst.SEEK_TYPE_SET, abs_location,
                                        gst.SEEK_TYPE_NONE, 0)
            if not event:
              raise VideoFrameError("Not Seekable")

            if not self.set_state_blocking(self._pipeline, gst.STATE_PAUSED):
                raise VideoFrameError("Not Pausable")

            frame = self._sink.get_current_frame()
            try:
                img = Image.frombuffer("RGB", sink_size, frame, "raw", "RGB", 0, 1)
		print "image get in _seek_for_frame"
		break
            except:
                print "Invalid frame"
                continue

        self._sink.reset()
	self.set_state_blocking(self._pipeline, gst.STATE_NULL)
        if img:
            if img.mode != 'RGBA':
                img = img.convert(mode='RGBA')
            img.save( self.output_file , 'png')
	    return self.output_file
	else:
	    return ""

    def set_state_blocking(self, pipeline, state):
        status = pipeline.set_state(state)
        if status == gst.STATE_CHANGE_ASYNC:
            print "Waiting for state change completion to %s..." % state
            result = [False]
            max_try = 100
            nb_try = 0
            while not result[0] == gst.STATE_CHANGE_SUCCESS:
                if nb_try > max_try:
                    print "State change failed: %s" % result[0]
                    return False
                nb_try += 1
                result = pipeline.get_state(50*gst.MSECOND)

            print "State change completed."
            return True
        elif status == gst.STATE_CHANGE_SUCCESS:
            print "State change completed."
            return True
        else:
            print "State change failed"
            return False
	
    def GetVideoFrame( self, uri , output_file ):
        res = ""
        self.set_state_blocking(self._pipeline, gst.STATE_NULL)
        self._pipeline.set_property('uri', uri)
	self.output_file = output_file
        # start the pipeline
        if not self.set_state_blocking(self._pipeline, gst.STATE_PAUSED):
            print "Error can start pipeline"
            self.set_state_blocking(self._pipeline, gst.STATE_NULL)
            return res #needed?

	if self._sink.width == None or self._sink.height == None:
            print "Cannot determine media size"
            self.set_state_blocking(self._pipeline, gst.STATE_NULL)
            return res
	
	sink_size = ( self._sink.width , self._sink.height )
        try:
            duration, format = self._pipeline.query_duration(gst.FORMAT_TIME)
        except Exception, e:
            print "Gstreamer cannot determine the media duration." , uri
            self.set_state_blocking(self._pipeline, gst.STATE_NULL)
            res = self._play_for_frame(0 , sink_size )
            return res
        else:
            duration /= gst.NSECOND
            try:
               res = self._seek_for_frame(duration,sink_size)
               return res
            except VideoFrameError, e:
                self.set_state_blocking(self._pipeline, gst.STATE_NULL)
                res = self._play_for_frame(duration , sink_size)
            	return res

        # stop the pipeline
        self.set_state_blocking(self._pipeline, gst.STATE_NULL)
	return res
###########

class Timer:
	def __init__(self, time, callback):
		self.__time = time
		self.__callback = callback
		self.__timeout_id = None
	
	def start(self):
		if self.__timeout_id == None:
			self.__timeout_id = gobject.timeout_add(self.__time, self.__callback)
			return True
		return False
		
	def stop(self):
		if self.__timeout_id != None:
			gobject.source_remove(self.__timeout_id)
			self.__timeout_id = None
			return True
		return False
			
	def restart(self):
		self.stop()
		self.start()
class MediaService(dbus.service.Object):
    def __init__(self):
        self.version = "0.0.1"
        self.identity = "Mobile Media Service"
        self.xid = 0
        self.width = 800
        self.height = 600
        self.name = 'org.moblin.MobileMediaService'
        self.path = '/org/moblin/MobileMediaService'
        self.player = gst.element_factory_make("playbin", "player")
        self.recreate_window()
        self.old_pos = 0
        gstbus = self.player.get_bus()
        gstbus.enable_sync_message_emission()
        gstbus.add_signal_watch()
        gstbus.connect('message', self.on_message)
        gstbus.connect('sync-message::element', self.on_sync_message)
        bus_name = dbus.service.BusName(self.name, bus=dbus.SessionBus())
        dbus.service.Object.__init__(self, bus_name, self.path) 
        self.vsink = 0;
        print "MediaService init end\n"
        self.pos_timer = Timer( 500 , self.pos_timer_cb );
        self.registry=gst.registry_get_default()
        self.InitPlayerCaps()
	
        #members for fast forward feature
        self.pos_timer = Timer( 500 , self.pos_timer_cb )
        self.fast_mode_timer = Timer( 500 , self.fast_mode_cb )
        self.FAST_MODE_STEP = 1
        self.fast_mode_rate = 0
        self.duration = 0

	#memver for get video frame
	self.frame_player = VideoFramePlayer()
	
    def ReloadCaps(self):
        #rewrite the Gst registry,but test result show that the plugin number not change.
        #remind that the info of path should be gotten dynamic according to real system
        gst.Registry.scan_path(self.registry,"/usr/lib/gst-gstreamer-0.10/")
    def InitPlayerCaps(self):
            self.GstCapsFinalList=list()
            #print "//////////////test start......\n"
            #print "write registry in xml format test start.....\n"
            os.system('mkdir -p $HOME/.mobile-player')
            MobileHomeDir=os.path.expanduser("~")
            RegXmlPath=MobileHomeDir+'/.mobile-player/myregistry.xml'
            #print MobileHomeDir
            if(gst.Registry.xml_write_cache(self.registry,RegXmlPath)):
                        rlist=list()
                        xmldoc=minidom.parse(RegXmlPath)
                    	for i in xmldoc.childNodes[0].childNodes:
                    		if i.localName=="plugin":
                    			for j in i.childNodes:
                    				if j.localName=='feature':
                    					for k in j.childNodes:
                    						if k.localName=='class':
                                               # if re.findall('Codec\/Decoder',k.toxml()):
                    							if 1+string.find(k.toxml(),"Codec/Decoder") or 1+string.find(k.toxml(),'Codec/Muxer'):
                    								for k in j.childNodes:
                    									if k.localName=='padtemplate':
                    										for m in k.childNodes:
                    											if m.localName=='direction':
                    												if 1+string.find(m.toxml(),'sink'):
                    													tmp=re.findall('[a-zA-Z\-]+/[a-zA-Z0-9\-\.]+',k.toxml())
                    													rlist.append(tmp)
    	
            else:
                      print "faile in this write cache test\n"
            #self.GstList= gst.Registry.get_plugin_list(self.registry)
            #print (gst.Plugin.get_filename(gst.Registry.find_plugin(self.registry,"typefindfunctions")))
            # print gst.Plugin.get_description(self.GstList[21])
            #self.list=gst.registry_get_plugin_list(gst.registry_get_default())
            new_list=list()												
            for i in rlist:
            	for j in i:
            		if not j in new_list:
            			new_list.append(j)
            new_list.sort()								
            #for k in new_list:
            #	    print k
            #print len(new_list)
            self.GstPluginCaps=string.join(new_list,sep=';')
            #change the caps list into a string so as to transfer it over dbus 
            #--for debug--#print self.GstPluginCaps
            #what we want is the GstPluginCaps 
            #print "\n////////////////end of test\n"
    def recreate_window(self):
        try:
            self.window.destory()
        except:
            pass
        window = gtk.Window(gtk.WINDOW_TOPLEVEL)
        window.set_title("Media-Player")
        window.set_position(gtk.WIN_POS_CENTER)
        window.set_default_size(self.width, self.height)
        window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_SPLASHSCREEN)
        window.connect("destroy", self.on_destroy, "WM destroy")
        self.surface = gtk.DrawingArea()
        window.add(self.surface)
        self.window = window
	
    def on_err_message( self, message ) :
            self.player.set_state(gst.STATE_NULL)
            try:
                if self.xid == self.surface.window.xid:
                    self.xid = 0
                    self.window.destroy()
            except:
                pass
            err = message.parse_error()
            msg = err[0].message
            if err[0].code == gst.RESOURCE_ERROR_NOT_FOUND:
                   msg = "Invalid media."
            self.ErrorOccur( msg )

    def on_message(self, bus, message):
 		
        if message.type == gst.MESSAGE_TAG:
			tags = {}
			for key in message.parse_tag().keys():
				if type(message.structure[key]) is types.StringType or type(message.structure[key]) is types.IntType :
					self.UpdateMediaInfo( key ,  message.structure[key] )
				else :	
					if key == "date":
						self.UpdateMediaInfo( "album_year" ,  message.structure[key].year )
					if key == "image":
 						cover = file( '/tmp/cover.jpg' , 'w')
						cover.write(message.structure[key])
						cover.close();
						self.UpdateMediaInfo( "image", 0)

    def on_message(self, bus, message):
        #print "in on_message" , message.type , message , "\n"
        if message.type == gst.MESSAGE_EOS : 
            self.Stop()
            return
        if message.type == gst.MESSAGE_ERROR:
            self.on_err_message(message)
            return
        if message.type == gst.MESSAGE_STATE_CHANGED:
            prev, new, pending = message.parse_state_changed()
            self.StatusChange(prev, new, pending)
            if new == gst.STATE_PLAYING and new != prev :
                dur = gst.query_new_duration(gst.FORMAT_TIME)
                self.player.query( dur )
                res_dur = dur.parse_duration()
                self.duration = res_dur[1]/gst.SECOND
                self.UpdateMediaInfo( "duration" ,  res_dur[1]/gst.SECOND )
            if new < gst.STATE_PAUSED :	
                self.pos_timer.stop() 
            else :
                self.pos_timer.start()
            return	

        if message.type == gst.MESSAGE_WARNING:
            return

        if message.type == gst.MESSAGE_TAG:
            tags = {}
            for key in message.parse_tag().keys():
                #print "in on_message tag" , key, message.structure[key] , "\n"
                if type(message.structure[key]) is types.StringType or type(message.structure[key]) is types.IntType :
                    self.UpdateMediaInfo( key ,  message.structure[key] )
                else :
                    if key == "date":
                        self.UpdateMediaInfo( "album_year" ,  message.structure[key].year )
                    if key == "image":
                        cover = file( '/tmp/cover.jpg' , 'w')
                        cover.write(message.structure[key])
                        cover.close();
                        self.UpdateMediaInfo( "image", 0)
            return

        if message.type == gst.MESSAGE_ELEMENT:
            if message.structure.has_name("missing-plugin") :	
                self.player.set_state(gst.STATE_NULL)
                try:
                    if self.xid == self.surface.window.xid:
                        self.xid = 0
                        self.window.destroy()
                except:
                    pass
                #msg = "Missing" ,  message.structure['name'] , ". Can't handle " , message.structure['detail'].to_string() , "\n"
                #msg = message.structure['detail'].to_string()
                msg = "Missing plugin " +  message.structure['name'] +". Please install this plugin."
                self.ErrorOccur( msg )						
            return
	
        print "unhandled message type" , message.type , message
        return

    def on_sync_message(self, bus, message):
        if message.structure is None:
            return
        if message.structure.get_name() == 'prepare-xwindow-id':
            imagesink = message.src
            imagesink.set_property('force-aspect-ratio', True)
            imagesink.set_xwindow_id(self.xid)
            self.vsink = imagesink

    def on_destroy(self, object, data):
        self.player.set_state(gst.STATE_NULL)

    def pos_timer_cb(self): 
        # print "---pos_timer_cb\n"
        format = gst.FORMAT_TIME
        cur = 0
        pos = gst.query_new_position(gst.FORMAT_TIME) 
        self.player.query( pos ) 
        res = pos.parse_position()
        self.UpdatePos( res[1]/gst.SECOND )
        self.pos_timer.restart()
	
    def fast_mode_cb(self):
        pos = self.GetPosition( )
        pos += self.FAST_MODE_STEP * self.fast_mode_rate
        #print "fast_mode_cb pos=" , pos , "\n"
        if pos > 0 and pos < self.duration :
            self.SetPosition(pos)
            self.fast_mode_timer.restart()
        else:
            self.Stop()
            self.fast_mode_rate = 0

    @dbus.service.method(dbus_interface='org.gnome.MobileMediaService', in_signature='', out_signature='s')
    def GetPluginCaps(self):
        #just get the copy of the plugin caps
        self.ReloadCaps()
        self.InitPlayerCaps()
        return self.GstPluginCaps

    @dbus.service.signal(dbus_interface='org.gnome.MobileMediaService')
    def StatusChange(self, prev, new, pending):
        return [prev, new, pending]

    @dbus.service.signal(dbus_interface='org.gnome.MobileMediaService')
    def ErrorOccur(self, msg):
        return [msg]
    
    @dbus.service.signal(dbus_interface='org.gnome.MobileMediaService')
    def UpdateMediaInfo(self, key, value):
        return [key, value]
		
    @dbus.service.signal(dbus_interface='org.gnome.MobileMediaService')
    def UpdatePos(self, cur ):
        return [cur]
    
    @dbus.service.method(dbus_interface='org.gnome.MobileMediaService', in_signature='i', out_signature='')
    def SetWindow(self, xid):
        self.xid = xid

    @dbus.service.method(dbus_interface='org.gnome.MobileMediaService', in_signature='', out_signature='i')
    def GetWindow(self):
        return self.xid

    @dbus.service.method(dbus_interface='org.gnome.MobileMediaService', in_signature='ii', out_signature='')
    def SetGeometry(self, width, height):
        self.width = width
        self.height = height
        self.window.set_default_size(self.width, self.height)

    @dbus.service.method(dbus_interface='org.gnome.MobileMediaService', in_signature='', out_signature='ii')
    def GetGeometry(self):
        return [self.width, self.height]

    @dbus.service.method(dbus_interface='org.gnome.MobileMediaService', in_signature='s', out_signature='')
    def OpenUri(self, uri):
        self.player.set_state(gst.STATE_NULL)
        try:
            if self.xid == self.surface.window.xid:
                self.xid = 0
            self.window.destroy()
        except:
            pass
        self.player.set_property('uri', uri)

    @dbus.service.method(dbus_interface='org.gnome.MobileMediaService', in_signature='', out_signature='')
    def Play(self):
        if self.player.get_state()[1] == gst.STATE_PLAYING:
            return
        if self.xid == 0:
            self.recreate_window()
            self.window.show_all()
            self.xid = self.surface.window.xid
        self.player.set_state(gst.STATE_PLAYING)

    @dbus.service.method(dbus_interface='org.gnome.MobileMediaService', in_signature='', out_signature='')
    def Pause(self):
        if self.player.get_state()[1] == gst.STATE_PLAYING:
            self.player.set_state(gst.STATE_PAUSED)

    @dbus.service.method(dbus_interface='org.gnome.MobileMediaService', in_signature='', out_signature='')
    def Stop(self):
        self.player.set_state(gst.STATE_READY)
        try:
            if self.xid == self.surface.window.xid:
                self.xid = 0
                self.window.destroy()
        except:
            pass

    @dbus.service.method(dbus_interface='org.gnome.MobileMediaService', in_signature='', out_signature='v')    
    def Status(self):
        return self.player.get_state()[1]

    @dbus.service.method(dbus_interface='org.gnome.MobileMediaService', in_signature='i', out_signature='')
    def SetVolume(self, volume):
        self.player.set_property('volume', volume)

    @dbus.service.method(dbus_interface='org.gnome.MobileMediaService', in_signature='', out_signature='i')
    def GetVolume(self):
        self.player.get_property('volume')

    @dbus.service.method(dbus_interface='org.gnome.MobileMediaService', in_signature='i', out_signature='')
    def SetPosition(self, position):
        gst_time = position * gst.SECOND
        event = gst.event_new_seek(1.0, gst.FORMAT_TIME, gst.SEEK_FLAG_FLUSH | gst.SEEK_FLAG_ACCURATE, gst.SEEK_TYPE_SET, gst_time, gst.SEEK_TYPE_NONE, 0)
        res = self.player.send_event(event)
        if res:
            self.player.set_new_stream_time(0L)
        else:
            print "res = %d" % res

    @dbus.service.method(dbus_interface='org.gnome.MobileMediaService', in_signature='', out_signature='i')
    def GetPosition(self):
        pos = gst.query_new_position( gst.FORMAT_TIME )
        self.player.query(pos)
        res = pos.parse_position()
        return ( res[1]/gst.SECOND )

    @dbus.service.method(dbus_interface='org.gnome.MobileMediaService', in_signature='', out_signature='')
    def Quit(self):
        gtk.main_quit()

    @dbus.service.method(dbus_interface='org.gnome.MobileMediaService', in_signature='', out_signature='')
    def Expose(self):
        print "Expose called"
        self.vsink.expose()
	
    @dbus.service.method(dbus_interface='org.gnome.MobileMediaService', in_signature='', out_signature='')
    def FastForward(self, rate ):		
        if rate == self.fast_mode_rate :
            return
        else :
            self.fast_mode_timer.stop()
            self.fast_mode_rate = rate
            if rate != 0 :
                self.fast_mode_timer.start()

    @dbus.service.method(dbus_interface='org.gnome.MobileMediaService', in_signature='', out_signature='')
    def FastRewind(self,rate ):
        if rate == -self.fast_mode_rate :
            return	
        else :
            self.fast_mode_timer.stop()
        self.fast_mode_rate = -rate
        if rate != 0 :
            self.fast_mode_timer.start()
	
    #call this service method to get a frame of the video, will write to a tmp file in png format
    # uri: the uri of the video
    # output_file: the output png file 
    # return output file name if success, otherwise return ""
    @dbus.service.method(dbus_interface='org.gnome.MobileMediaService', in_signature='ss', out_signature='s')
    def GetVideoFrame(self, uri, output_file):
	return self.frame_player.GetVideoFrame( uri , output_file )
	
		
if __name__ == '__main__':
    gtk.gdk.threads_init()
    MediaService()
    gtk.main()

