0

I'm working on program, that should display big raw video, seek in it that I'd be able to set from and to times cuts in it, set black borders sizes to hide shaked borders of image. The crucial part of this project is seeking. I've tried 5min file cutoff and when I seek at the start of the video, it's OK but after middle something goes wrong. Since it seems there is no much examples and documentation I'm using this:

self.pipe.seek_simple(
        Gst.Format.TIME,
        Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT,
        destSeek
    )

My actual (non-mature) code:

import sys
import gi
gi.require_version('Gst', '1.0')
gi.require_version('Gtk', '3.0')
gi.require_version('GdkX11', '3.0')
gi.require_version('GstVideo', '1.0')
from gi.repository import GObject, Gst, Gtk, GdkX11, GstVideo, Gdk

GObject.threads_init()
Gst.init(None)

testGrab = "testRAW.mkv"

class VideoDec(Gst.Bin):
    def __init__(self):
        super().__init__()

        # elements
        q1 = Gst.ElementFactory.make('queue', None)
        videoparse = Gst.ElementFactory.make('videoparse', None)
        q2 = Gst.ElementFactory.make('queue', None)

        self.add(q1)
        self.add(videoparse)
        self.add(q2)

        videoparse.set_property('width', 720)
        videoparse.set_property('height', 576)
        videoparse.set_property('format', 4)

        # link
        q1.link(videoparse)
        videoparse.link(q2)

        # Add Ghost Pads
        self.add_pad(
            Gst.GhostPad.new('sink', q1.get_static_pad('sink'))
        )
        self.add_pad(
            Gst.GhostPad.new('src', q2.get_static_pad('src'))
        )

class AudioDec(Gst.Bin):
    def __init__(self):
        super().__init__()

        # elements
        q1 = Gst.ElementFactory.make('queue', None)
        audioparse = Gst.ElementFactory.make('audioparse', None)
        q2 = Gst.ElementFactory.make('queue', None)
        #sink = Gst.ElementFactory.make('autoaudiosink', None)

        self.add(q1)
        self.add(audioparse)
        self.add(q2)
        #self.add(sink)

        # link
        q1.link(audioparse)
        audioparse.link(q2)
        #audioparse.link(sink)

        # Add Ghost Pads
        self.add_pad(
            Gst.GhostPad.new('sink', q1.get_static_pad('sink'))
        )
        self.add_pad(
            Gst.GhostPad.new('src', q2.get_static_pad('src'))
        )

class Player(object):
    def __init__(self):
        self.fps = 25
        self.window = Gtk.Window()
        self.window.connect('destroy', self.quit)
        self.window.set_default_size(800, 600)

        self.drawingarea = Gtk.DrawingArea()

        #hbox
        self.hbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)

        self.window.add(self.hbox)
        Gtk.Box.pack_start(self.hbox, self.drawingarea, True, True, 0)

        self.setPipeline()

        self.setGUI()
        self.setShortcuts()

        self.playing = False

    def setPipeline(self):
        self.pipe = Gst.Pipeline.new('player')

        # Create bus to get events from GStreamer pipeline
        self.bus = self.pipe.get_bus()
        self.bus.add_signal_watch()
        self.bus.connect('message::eos', self.on_eos)
        self.bus.connect('message::error', self.on_error)

        # This is needed to make the video output in our DrawingArea:
        self.bus.enable_sync_message_emission()
        self.bus.connect('sync-message::element', self.on_sync_message)

        self.src = Gst.ElementFactory.make('filesrc', None)
        self.src.set_property("location", testGrab)
        self.dec = Gst.ElementFactory.make('decodebin', None)
        self.video = VideoDec()
        self.audio = AudioDec()
        self.glimagesink = Gst.ElementFactory.make('glimagesink', None)
        self.audiosink = Gst.ElementFactory.make('autoaudiosink', None)

        self.pipe.add(self.src)
        self.pipe.add(self.dec)
        self.pipe.add(self.video)
        self.pipe.add(self.audio)
        self.pipe.add(self.glimagesink)
        self.pipe.add(self.audiosink)
        #self.pipe.add(self.autovideosink)

        # Connect signal handlers
        self.dec.connect('pad-added', self.on_pad_added)

        # link
        self.src.link(self.dec)
        self.video.link(self.glimagesink)
        self.audio.link(self.audiosink)

    def on_pad_added(self, element, pad):
        string = pad.query_caps(None).to_string()
        print('on_pad_added():', string)
        if string.startswith('audio/'):
            pad.link(self.audio.get_static_pad('sink'))
        elif string.startswith('video/'):
            pad.link(self.video.get_static_pad('sink'))

    def setGUI(self):
        vbox = Gtk.Box(Gtk.Orientation.HORIZONTAL, 0)
        vbox.set_margin_top(3)
        vbox.set_margin_bottom(3)
        Gtk.Box.pack_start(self.hbox, vbox, False, False, 0)

        self.playButtonImage = Gtk.Image()
        self.playButtonImage.set_from_stock("gtk-media-play", Gtk.IconSize.BUTTON)
        self.playButton = Gtk.Button.new()
        self.playButton.add(self.playButtonImage)
        self.playButton.connect("clicked", self.playToggled)
        Gtk.Box.pack_start(vbox, self.playButton, False, False, 0)

        self.slider = Gtk.HScale()
        self.slider.set_margin_left(6)
        self.slider.set_margin_right(6)
        self.slider.set_draw_value(False)
        self.slider.set_range(0, 100)
        self.slider.set_increments(1, 10)

        Gtk.Box.pack_start(vbox, self.slider, True, True, 0)


        self.label = Gtk.Label(label='0:00')
        self.label.set_margin_left(6)
        self.label.set_margin_right(6)
        Gtk.Box.pack_start(vbox, self.label, False, False, 0)

    def setShortcuts(self):
        accel = Gtk.AccelGroup()
        accel.connect(Gdk.KEY_space, Gdk.ModifierType.CONTROL_MASK, 0, self.playToggled)
        accel.connect(Gdk.KEY_Right, Gdk.ModifierType.CONTROL_MASK, 0, self.seekFW0)
        accel.connect(Gdk.KEY_Right, Gdk.ModifierType.CONTROL_MASK|Gdk.ModifierType.SHIFT_MASK, 0, self.seekFW10s)
        accel.connect(Gdk.KEY_Right, Gdk.ModifierType.SHIFT_MASK, 0, self.seekFW2)
        accel.connect(Gdk.KEY_Right, Gdk.ModifierType.MOD1_MASK, 0, self.seekFW10) # alt key
        self.window.add_accel_group(accel)

    def seekFW0(self, *args):
        self.seekTime = 2 * Gst.SECOND // self.fps
        self.seekFW()

    def seekFW10s(self, *args):
        self.seekTime = Gst.SECOND * 10
        self.seekFW()

    def seekFW2(self, *args):
        self.seekTime = Gst.SECOND * 60 * 2
        self.seekFW()

    def seekFW10(self, *args):
        self.seekTime = Gst.SECOND * 60 * 10
        self.seekFW()

    def seekFW(self, *args):
        nanosecs = self.pipe.query_position(Gst.Format.TIME)[1]
        destSeek = nanosecs + self.seekTime
        self.pipe.seek_simple(
            Gst.Format.TIME,
            Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT,
            destSeek
        )

    def play(self):
        self.pipe.set_state(Gst.State.PLAYING)
        GObject.timeout_add(1000, self.updateSlider)


    def stop(self):
        self.pipe.set_state(Gst.State.PAUSED)


    def playToggled(self, *w):

        if(self.playing == False):
            self.play()
        else:
            self.stop()


        self.playing=not(self.playing)
        self.updateButtons()

    def updateSlider(self):
        try:
            nanosecs = self.pipe.query_position(Gst.Format.TIME)[1]
            duration_nanosecs = self.pipe.query_duration(Gst.Format.TIME)[1]


            # block seek handler so we don't seek when we set_value()
            # self.slider.handler_block_by_func(self.on_slider_change)


            duration = float(duration_nanosecs) / Gst.SECOND
            position = float(nanosecs) / Gst.SECOND
            self.slider.set_range(0, duration)
            self.slider.set_value(position)
            self.label.set_text ("%d" % (position / 60) + ":%02d" % (position % 60))


            #self.slider.handler_unblock_by_func(self.on_slider_change)


        except Exception as e:
            # pipeline must not be ready and does not know position
            print(e)
            pass


        return True

    def updateButtons(self):
        if(self.playing == False):
            self.playButtonImage.set_from_stock("gtk-media-play", Gtk.IconSize.BUTTON)
        else:
            self.playButtonImage.set_from_stock("gtk-media-pause", Gtk.IconSize.BUTTON)

    def run(self):
        self.window.show_all()
        # You need to get the XID after window.show_all().  You shouldn't get it
        # in the on_sync_message() handler because threading issues will cause
        # segfaults there.
        self.xid = self.drawingarea.get_property('window').get_xid()
        #self.pipeline.set_state(Gst.State.PLAYING)
        Gtk.main()

    def quit(self, window):
        self.pipe.set_state(Gst.State.NULL)
        Gtk.main_quit()

    def on_sync_message(self, bus, msg):
        if msg.get_structure().get_name() == 'prepare-window-handle':
            print('prepare-window-handle')
            msg.src.set_window_handle(self.xid)

    def on_eos(self, bus, msg):
        #print('on_eos(): seeking to start of video')
        print('on_eos(): pausing video')
        self.stop()
        #self.pipeline.seek_simple(
        #    Gst.Format.TIME,        
        #    Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT,
        #    0
        #)
        #self.playing = False
        #self.slider.set_value(0)
        #self.label.set_text("0:00")
        #self.updateButtons()

    def on_error(self, bus, msg):
        print('on_error():', msg.parse_error())


p = Player()
p.run()

If somebody could share wisdom or documentation/example links I'd be happy.

d3im
  • 323
  • 2
  • 4
  • 18
  • please describe "something go wrong" .. [here](https://cgit.freedesktop.org/gstreamer/gst-python/tree/old_examples/segments.py?h=1.6) is some old example (maybe its for 0.10 .. so it may be unusable) – nayana May 16 '16 at 07:20
  • When I seek on start of the video it's OK, when I seek 2 mins once - it's almost immediately but after that when I try to seek next 2 mins forward it hangs. – d3im May 24 '16 at 16:34
  • ok then try the obvious things.. check gstreamer logs :) try running your app with GST_DEBUG=3 or 4.. gl.. then you may update your question with new informations.. – nayana May 25 '16 at 06:24

1 Answers1

2

I no longer remember quite why I gave up using seek_simple but I do recall that it caused me no end of grief.
Use instead a standard seek:

Gplayer.seek(self.rate, Gst.Format.TIME, 
    (Gst.SeekFlags.FLUSH | Gst.SeekFlags.ACCURATE),
    Gst.SeekType.SET, seek_time , Gst.SeekType.NONE, -1)

Gstreamer1.0 seek

Late Edit:
I believe the issue was getting accurate and consistent timestamps, when I was using the scaletempo element. The scaletempo element caters for adjusting the speed at which playback occurs and I was getting inconsistent times from the player if I varied the rate of play. I moved from seek_simple to seek to resolve the issue.
Note: self.rate above is a user defined variable, which for normal playback would be 1.00, dropping below 1.00 for slower playback and above 1.00 for faster playback.

Rolf of Saxony
  • 21,661
  • 5
  • 39
  • 60