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.