#! /usr/bin/python

"""Gstreamer SliceBuffer element

Copyright 2011 Olivier Aubert <olivier.aubert@liris.cnrs.fr>

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""

import sys

import gobject
gobject.threads_init()

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

import cairo

class SliceBuffer(gst.Element):
    __gtype_name__ = 'SliceBuffer'
    __gstdetails__ = ("Slice buffer", "Filter/Editor/Video", "Buffers slices of data",
                      "Olivier Aubert <olivier.aubert@liris.cnrs.fr>")

    __gproperties__ = {
        'slicewidth':  ( gobject.TYPE_INT, 'slicewidth', 'Width of slices', 0, 65536, 0, gobject.PARAM_WRITABLE ),
        'offset': (gobject.TYPE_INT, 'offset', 'Offset of samples in the source video. If < 0, use the original offset.', -65536, 65536, 0, gobject.PARAM_WRITABLE),
        }

    _sinkpadtemplate = gst.PadTemplate ("sink",
                                         gst.PAD_SINK,
                                         gst.PAD_ALWAYS,
                                         gst.caps_from_string ("video/x-raw-rgb,bpp=32,depth=32,blue_mask=-16777216,green_mask=16711680, red_mask=65280, alpha_mask=255,width=[ 1, 2147483647 ],height=[ 1, 2147483647 ],framerate=[ 0/1, 2147483647/1 ]"))
    _srcpadtemplate = gst.PadTemplate ("src",
                                         gst.PAD_SRC,
                                         gst.PAD_ALWAYS,
                                         gst.caps_from_string ("video/x-raw-rgb,bpp=32,depth=32,blue_mask=-16777216,green_mask=16711680, red_mask=65280, alpha_mask=255,width=[ 1, 2147483647 ],height=[ 1, 2147483647 ],framerate=[ 0/1, 2147483647/1 ]"))

    def __init__(self):
        gst.Element.__init__(self)

        self.slicewidth = 1
        self.offset = 128
        self._index = 0
        self._buffer  = None
        self._surface = None

        self.sinkpad = gst.Pad(self._sinkpadtemplate, "sink")
        self.sinkpad.set_chain_function(self.chainfunc)
        self.sinkpad.set_event_function(self.eventfunc)
        self.sinkpad.set_getcaps_function(gst.Pad.proxy_getcaps)
        self.sinkpad.set_setcaps_function(gst.Pad.proxy_setcaps)
        self.add_pad (self.sinkpad)

        self.srcpad = gst.Pad(self._srcpadtemplate, "src")
        self.srcpad.set_event_function(self.srceventfunc)
        self.srcpad.set_query_function(self.srcqueryfunc)
        self.srcpad.set_getcaps_function(gst.Pad.proxy_getcaps)
        self.srcpad.set_setcaps_function(gst.Pad.proxy_setcaps)
        self.add_pad (self.srcpad)

    def chainfunc(self, pad, buf):
        caps = buf.get_caps()
        width = caps[0]['width']
        height = caps[0]['height']
        if self._surface is None:
            # Need to initialize
            self._buffer = buf.copy_on_write ()
            self._surface = cairo.ImageSurface.create_for_data (self._buffer, cairo.FORMAT_ARGB32, width, height, 4 * width)
            self._ctx = cairo.Context(self._surface)
            self._ctx.set_source_rgb(0, 0, 0)
            self._ctx.rectangle(0, 0, width, height)
            self._ctx.fill()
            self._index = 0

        # Get rectangle
        if self.offset < 0:
            # get the rectangle at the position it will be in the destination buffer
            source_x = (self._index * self.slicewidth) % width
        else:
            # Get the rectangle at the position specified by offset
            source_x = self.offset
        dest_x = (self._index * self.slicewidth) % (width - self.slicewidth)
        self._index += 1

        # Copy slice into buffer
        surf = cairo.ImageSurface.create_for_data(buf.copy_on_write(), cairo.FORMAT_ARGB32, width, height, 4 * width)
        self._ctx.set_operator(cairo.OPERATOR_SOURCE)
        self._ctx.set_source_surface(surf, dest_x - source_x, 0)
        self._ctx.rectangle(dest_x, 0, self.slicewidth, height)
        self._ctx.fill()

        # Restamp result buffer using incoming buffer information
        self._buffer.stamp(buf)
        return self.srcpad.push(self._buffer)

    def eventfunc(self, pad, event):
        return self.srcpad.push_event (event)

    def srcqueryfunc (self, pad, query):
        return self.sinkpad.query (query)

    def srceventfunc (self, pad, event):
        return self.sinkpad.push_event (event)

    def do_change_state(self, transition):
        #if transition in [gst.STATE_CHANGE_READY_TO_PAUSED, gst.STATE_CHANGE_PAUSED_TO_READY]:
        #    self._reset()
        return gst.Element.do_change_state(self, transition)

    def do_set_property(self, key, value):
        if key.name == 'slicewidth':
            self.slicewidth = value
        elif key.name == 'offset':
            self.offset = value
        else:
            print "No property %s" % key.name

gst.element_register(SliceBuffer, 'slicebuffer')

if __name__ == '__main__':
    mainloop = gobject.MainLoop()

    files = [ a for a in sys.argv[1:] if not '=' in a ]
    params = [ a for a in sys.argv[1:] if '=' in a ]

    if files:
        player=gst.element_factory_make('playbin')
        player.props.uri = 'file://' + files[0]

        bin=gst.Bin()
        elements = [
            gst.element_factory_make('ffmpegcolorspace'),
            gst.element_factory_make('videoscale'),
            gst.element_factory_make('slicebuffer', 'slicer'),
            gst.element_factory_make('capsfilter', 'capsfilter'),
            gst.element_factory_make('ffmpegcolorspace'),
            gst.element_factory_make('xvimagesink'),
            ]
        bin.add(*elements)
        gst.element_link_many(*elements)
        bin.add_pad(gst.GhostPad('sink', elements[0].get_pad('video_sink') or elements[0].get_pad('sink')))

        slicer = bin.get_by_name('slicer')
        capsfilter = bin.get_by_name('capsfilter')
        for p in params:
            name, value = p.split('=')
            if name == 'width':
                caps = gst.caps_from_string('video/x-raw-rgb,%s' % p)
                capsfilter.set_property('caps', caps)
            else:
                slicer.set_property(name, long(value))
        player.props.video_sink=bin
    else:
        player = gst.parse_launch('videotestsrc ! clockoverlay ! ffmpegcolorspace ! videoscale ! slicebuffer %s ! ffmpegcolorspace ! xvimagesink' % " ".join(params))
        bin = player

    pipe=player
    overlay=bin.get_by_name('overlay')


    def on_msg(bus, msg):
        if msg.structure is None:
            return True
        if msg.structure.has_field('gerror'):
            print "MSG", msg.structure['debug']

    def on_eos (bus, msg):
        mainloop.quit()
    bus = pipe.get_bus()
    bus.add_signal_watch()
    bus.connect('message::eos', on_eos)
    bus.connect('message', on_msg)

    print "PLAYING"
    pipe.set_state (gst.STATE_PLAYING)

    try:
        mainloop.run()
    except:
        pass

    pipe.set_state (gst.STATE_NULL)
    pipe.get_state (gst.CLOCK_TIME_NONE)
