#!/usr/bin/env python
# -*- Mode: Python -*-
# vi:si:et:sw=4:sts=4:ts=4

import sys, os

import gobject
gobject.threads_init()

# import pygst
# pygst.require('0.10')
import gst
import math

class CustomTextBin(gst.Bin):

    def __init__(self):
        gst.Bin.__init__(self, 'TextBin')

        queue = gst.element_factory_make('queue', 'tqueue')
        self.add(queue)

        trender = gst.element_factory_make('textrender', 'trender')
        self.add(trender)

        tcsp = gst.element_factory_make('ffmpegcolorspace', 'tcsp')
        self.add(tcsp)
       
        vsink = gst.element_factory_make('ximagesink', 'vsink')
        self.add(vsink)
        
        gst.element_link_many(queue, trender, tcsp, vsink)
        sink = queue.get_pad('sink')
        sink.add_buffer_probe(self.buffer_probe) 
        
        self.add_pad(gst.GhostPad('sink', sink))        

    def buffer_probe(self, pad, buffer):
        print "got text buffer with ts %s and duration %s" % (gst.TIME_ARGS(buffer.timestamp), gst.TIME_ARGS(buffer.duration))
        return True
        
class GstPlayer:
    STOPPED = 0
    PLAYING = 1
    PAUSED = 2
    BUFFERING = 3
    PREROLLING = 4

    def __init__(self):
        self.playing = False
        self.on_eos = False
        self._cbuffering = -1
        self.status = self.STOPPED
        self.target_status = self.STOPPED
        self._rate = 1.0
        self.textbin = CustomTextBin()
        self.player = gst.element_factory_make('playbin2', None)
        #self.player.set_property('flags', 0x43)
        
        self.player.set_property('text-sink', self.textbin)
        self.player.connect('notify::source', self.on_source_created)

        bus = self.player.get_bus()
        bus.connect('message::eos', self.on_message_eos)
        bus.connect('message::error', self.on_message_error)
        bus.connect('message::state-changed', self.on_message_state_changed)
        bus.connect('message::buffering', self.on_message_buffering)
        bus.connect('message::clock-lost', self.on_message_clock_lost)
        bus.connect('message::async-done', self.on_message_async_done)
        bus.add_signal_watch()

    def do_cleanup(self):
        current = self.player.get_state(0)[1]
        if current != gst.STATE_NULL:
            gst.info('do_cleanup')
            self.player.set_state(gst.STATE_READY)
            current = self.player.get_state(0)[1]
            gst.info('changed state to %s' % current)
            self.player.set_state(gst.STATE_NULL)
            current = self.player.get_state(0)[1]
            gst.info('changed state to %s' % current)

    def on_message_async_done(self, bus, message):
        self.n_video = self.player.get_property('n-video')
        self.n_audio = self.player.get_property('n-audio')
        self.n_text = self.player.get_property('n-text')
        print "%d video %d audio %d text" % (self.n_video, self.n_audio, self.n_text)
        if self.n_text > 0:
            self.player.set_property("current-text", self.n_text - 1)
        

    def on_source_created(self, pipeline, *args):
        source = self.player.get_property('source')
        try:
            source.set_property("user-agent", "quicktime")
        except:
            pass

    def on_message_eos(self, bus, message):
        if self.on_eos:
            self.on_eos()
        self.playing = False

    def on_message_error(self, bus, message):
        err, msg = message.parse_error()
        code = message.structure['gerror'].code
        self.stop()
        self.status = self.STOPPED
        self.target_status = self.status
        gst.error("Gstreamer %s:%s" % (err, msg))

    def on_message_state_changed(self, bus, message):
        if message.src != self.player:
            return

        old_state, new_state, pending = message.parse_state_changed()
        gst.info ("old %s current %s pending %s status %s target status %s" % \
            (old_state, new_state, pending, self.status, self.target_status))
        if new_state == gst.STATE_PLAYING:
            if self.status != self.PLAYING:
                self.status = self.PLAYING
                self.playing = True

        elif new_state == gst.STATE_PAUSED:
            if self.status != self.BUFFERING:
                if self.target_status == self.PLAYING:
                    self.player.set_state(gst.STATE_PLAYING)
                else:
                    self.status = self.PAUSED

    def on_message_buffering(self, bus, message):
        percent = message.parse_buffering()

        if math.floor(percent/5) > self._cbuffering:
            self._cbuffering = math.floor(percent/5)
            print "buffering %s" % percent

        if percent == 100:
            if self.target_status == self.PLAYING:
                gst.info("buffering done going to play")
                self.status = self.target_status
                self.player.set_state(gst.STATE_PLAYING)
            self._cbuffering = -1
        elif self.status != self.BUFFERING:
            if self.status == self.PLAYING:
                self.player.set_state(gst.STATE_PAUSED)

            self.status = self.BUFFERING

    def on_message_clock_lost(self, bus, message):
        self.player.set_state(gst.STATE_PAUSED)
        self.player.set_state(gst.STATE_PLAYING)

    def set_location(self, location):
        self.player.set_property('uri', location)

    def query_position(self):
        "Returns a (position, duration) tuple"
        try:
            position, format = self.player.query_position(gst.FORMAT_TIME)
        except:
            position = gst.CLOCK_TIME_NONE

        try:
            duration, format = self.player.query_duration(gst.FORMAT_TIME)
        except:
            duration = gst.CLOCK_TIME_NONE

        return (position, duration)

    def seek(self, location):
        """
        @param location: time to seek to, in nanoseconds
        """
        gst.debug("seeking to %r" % location)

        res = self.player.seek (self._rate, gst.FORMAT_TIME,
            gst.SEEK_FLAG_FLUSH | gst.SEEK_FLAG_KEY_UNIT,
            gst.SEEK_TYPE_SET, location,
            gst.SEEK_TYPE_NONE, 0)

        if res:
            gst.info("setting new stream time to 0")
            self.player.set_new_stream_time(0L)
        else:
            gst.error("seek to %r failed" % location)

    def pause(self):
        gst.info("pausing player")
        self.target_status = self.PAUSED
        self.player.set_state(gst.STATE_PAUSED)
        self.playing = False

    def play(self):
        gst.info("playing player")
        self.target_status = self.PLAYING
        current = self.player.get_state(0)[1]
        if current == gst.STATE_PAUSED:
            self.player.set_state(gst.STATE_PLAYING)
        elif current != gst.STATE_PLAYING :
            self.player.set_state(gst.STATE_PAUSED)
            self.status = self.PREROLLING

    def stop(self):
        self.player.set_state(gst.STATE_READY)
        current = self.player.get_state(0)[1]
        gst.info('changed state to %s' % current)
        if self.status != self.STOPPED:
            self.status = self.STOPPED
            self.target_status = self.status
        gst.info("stopped player")

    def get_state(self, timeout=1):
        return self.player.get_state(timeout=timeout)

    def is_playing(self):
        return self.playing

    def set_rate(self, rate):
        self._rate = rate
        try:
            position, format = self.player.query_position(gst.FORMAT_TIME)
        except:
            position = gst.CLOCK_TIME_NONE

        res = self.player.seek(self._rate, gst.FORMAT_TIME,
            gst.SEEK_FLAG_FLUSH | gst.SEEK_FLAG_KEY_UNIT,
            gst.SEEK_TYPE_SET, position,
            gst.SEEK_TYPE_SET, -1)

        if res:
            gst.info("rate set to %s" % rate)
        else:
            gst.warning("change rate failed")


class PlayerTUI():
    def __init__(self, args):

        self.player = GstPlayer()

        self.duration = gst.CLOCK_TIME_NONE
        self.pos = 0
        self.media_list = args
        self.media_idx = 1
        self.media_count = len(args)
        self.moving = False
        self.pos = 0

        def on_eos():
            self.player.stop()
            self.media_idx = self.media_idx + 1
            if self.media_idx < self.media_count:
                self.player.set_location(self.media_list[self.media_idx])
                self.player.play()
            else:
                self.quit()

        print "q: quit"
        print "p: show position"
        print "f: seek forward"
        print "b: seek backward"
        print "s: pause"
        print "r: resume playback (play)"
        print "h: half speed"
        print "1: normal rate"
        print "2: 2x playback"        
        print "3: 3x playback"
        print "4: 4x playback"
        print "n: next clip"

        self.player.on_eos = lambda *x: on_eos()
        # The MainLoop
        self.mainloop = gobject.MainLoop()
        gobject.io_add_watch(sys.stdin, gobject.IO_IN, self.on_stdin)

        self.player.set_location(self.media_list[self.media_idx])
        self.player.play()
        try:
            self.mainloop.run()
        except KeyboardInterrupt:
            self.quit()

    def on_stdin(self, fd, condition):
        c = os.read(fd.fileno(), 1)

        if c == "q":
            self.quit()
        elif c == "p":
            self.show_position()
        elif c == "f":
            self.seek_forward()
        elif c == "b":
            self.seek_backward()
        elif c == "s":
            self.player.pause()
        elif c == "r":
            self.player.play()
        elif c == "h":
            self.player.set_rate(0.5)
        elif c == "1":
            self.player.set_rate(1.0)
        elif c == "2":
            self.player.set_rate(2.0)
        elif c == "3":
            self.player.set_rate(3.0)
        elif c == "4":
            self.player.set_rate(4.0)
        elif c == "n":
            self.player.stop()
            self.media_idx = self.media_idx + 1
            if self.media_idx < self.media_count:
                self.player.set_location(self.media_list[self.media_idx])
                self.player.play()
            else:
                self.quit()

        return True

    def quit(self):
        self.player.do_cleanup()
        gst.info("finished cleanup")
        del self.player
        self.player = None
        self.mainloop.quit()

    def show_position(self):
        position, duration = self.player.query_position()
        print "%s / %s" % (gst.TIME_ARGS(position), gst.TIME_ARGS(duration))

    def seek_forward(self):
        c_position, c_duration = self.player.query_position()
        if c_duration == gst.CLOCK_TIME_NONE:
            n_position = c_position + 5 * gst.SECOND
        else:
            if self.duration == gst.CLOCK_TIME_NONE:
                self.duration = c_duration
            self.pos = min (self.pos + 10, 90)
            n_position = self.duration * self.pos / 100
        print "-> %s ( %d )" % (gst.TIME_ARGS(n_position), self.pos)
        self.player.seek (n_position)

    def seek_backward(self):
        c_position, c_duration = self.player.query_position()
        if c_duration == gst.CLOCK_TIME_NONE:
            n_position = max (0,c_position - 5 * gst.SECOND)
        else:
            if self.duration == gst.CLOCK_TIME_NONE:
                self.duration = c_duration
            self.pos = max (self.pos - 10, 0)
            n_position = self.duration * self.pos / 100
        print "-> %s ( %d )" % (gst.TIME_ARGS(n_position), self.pos)
        self.player.seek (n_position)

def main(args):
    def usage():
        sys.stderr.write("usage: %s list of URI-OF-MEDIA-FILE\n" % args[0])
        sys.exit(1)

    if len(args) < 2:
        usage()

    if not gst.uri_is_valid(args[1]):
        sys.stderr.write("Error: Invalid URI: %s\n" % args[1])
        sys.exit(1)

    tui = PlayerTUI(args)

if __name__ == '__main__':
    sys.exit(main(sys.argv))
