From f48442d62abc6f014523efa4f95e38c4f5fdcc49 Mon Sep 17 00:00:00 2001 From: Jan Schmidt Date: Tue, 15 Aug 2023 14:22:12 +1000 Subject: [PATCH] python: Update plugin examples Fix warnings from bindings changes in various plugin examples Fix the python mixer plugin by ensuring that PIL is not holding a reference to mapped GstBuffer memory. Port the filesrc example from old_examples Part-of: --- .../examples/plugins/python/audioplot.py | 32 +++-- .../examples/plugins/python/filesrc.py | 116 ++++++++++++++++++ .../examples/plugins/python/mixer.py | 34 ++--- 3 files changed, 151 insertions(+), 31 deletions(-) create mode 100755 subprojects/gst-python/examples/plugins/python/filesrc.py diff --git a/subprojects/gst-python/examples/plugins/python/audioplot.py b/subprojects/gst-python/examples/plugins/python/audioplot.py index 8747fee1a1..d53d977e0b 100644 --- a/subprojects/gst-python/examples/plugins/python/audioplot.py +++ b/subprojects/gst-python/examples/plugins/python/audioplot.py @@ -15,7 +15,6 @@ gi.require_version('Gst', '1.0') gi.require_version('GstBase', '1.0') gi.require_version('GstAudio', '1.0') gi.require_version('GstVideo', '1.0') - from gi.repository import Gst, GLib, GObject, GstBase, GstAudio, GstVideo try: @@ -37,8 +36,8 @@ AUDIO_FORMATS = [f.strip() for f in ICAPS = Gst.Caps(Gst.Structure('audio/x-raw', format=Gst.ValueList(AUDIO_FORMATS), layout='interleaved', - rate = Gst.IntRange(range(1, GLib.MAXINT)), - channels = Gst.IntRange(range(1, GLib.MAXINT)))) + rate=Gst.IntRange(range(1, GLib.MAXINT)), + channels=Gst.IntRange(range(1, GLib.MAXINT)))) OCAPS = Gst.Caps(Gst.Structure('video/x-raw', format='ARGB', @@ -55,8 +54,8 @@ DEFAULT_FRAMERATE_DENOM = 1 class AudioPlotFilter(GstBase.BaseTransform): - __gstmetadata__ = ('AudioPlotFilter','Filter', \ - 'Plot audio waveforms', 'Mathieu Duponchelle') + __gstmetadata__ = ('AudioPlotFilter', 'Filter', + 'Plot audio waveforms', 'Mathieu Duponchelle') __gsttemplates__ = (Gst.PadTemplate.new("src", Gst.PadDirection.SRC, @@ -68,13 +67,13 @@ class AudioPlotFilter(GstBase.BaseTransform): ICAPS)) __gproperties__ = { "window-duration": (float, - "Window Duration", - "Duration of the sliding window, in seconds", - 0.01, - 100.0, - DEFAULT_WINDOW_DURATION, - GObject.ParamFlags.READWRITE - ) + "Window Duration", + "Duration of the sliding window, in seconds", + 0.01, + 100.0, + DEFAULT_WINDOW_DURATION, + GObject.ParamFlags.READWRITE + ) } def __init__(self): @@ -128,7 +127,7 @@ class AudioPlotFilter(GstBase.BaseTransform): inbuf = self.queued_buf _, info = inbuf.map(Gst.MapFlags.READ) res, data = self.converter.convert(GstAudio.AudioConverterFlags.NONE, - info.data) + info.data) data = memoryview(data).cast('i') nsamples = len(data) - self.buf_offset @@ -187,10 +186,8 @@ class AudioPlotFilter(GstBase.BaseTransform): return ret.fixate() def do_set_caps(self, icaps, ocaps): - in_info = GstAudio.AudioInfo() - in_info.from_caps(icaps) - out_info = GstVideo.VideoInfo() - out_info.from_caps(ocaps) + in_info = GstAudio.AudioInfo.new_from_caps(icaps) + out_info = GstVideo.VideoInfo().new_from_caps(ocaps) self.convert_info = GstAudio.AudioInfo() self.convert_info.set_format(GstAudio.AudioFormat.S32, @@ -238,5 +235,6 @@ class AudioPlotFilter(GstBase.BaseTransform): return True + GObject.type_register(AudioPlotFilter) __gstelementfactory__ = ("audioplot", Gst.Rank.NONE, AudioPlotFilter) diff --git a/subprojects/gst-python/examples/plugins/python/filesrc.py b/subprojects/gst-python/examples/plugins/python/filesrc.py new file mode 100755 index 0000000000..53f31d5ebd --- /dev/null +++ b/subprojects/gst-python/examples/plugins/python/filesrc.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python +# -*- Mode: Python -*- +# vi:si:et:sw=4:sts=4:ts=4 + +# GStreamer python bindings +# Copyright (C) 2002 David I. Lehn +# 2004 Johan Dahlin +# +# filesrc.py: implements a file source element completely in python +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Library General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library 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 +# Library General Public License for more details. +# +# You should have received a copy of the GNU Library General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +# Boston, MA 02110-1301, USA. +import gi +import sys + +gi.require_version('Gst', '1.0') +gi.require_version('GstBase', '1.0') +from gi.repository import GLib, Gst, GObject, GstBase + + +if __name__ == '__main__': + Gst.init(None) +Gst.init_python() + + +class FileSource(GstBase.BaseSrc): + __gstmetadata__ = ('PyFileSource', 'Source', + 'Custom Python filesrc example', 'Jan Schmidt') + + __gsttemplates__ = ( + Gst.PadTemplate.new("src", + Gst.PadDirection.SRC, + Gst.PadPresence.ALWAYS, + Gst.Caps.new_any()), + ) + + blocksize = 4096 + fd = None + + def __init__(self, name): + super().__init__() + self.curoffset = 0 + self.set_name(name) + + def set_property(self, name, value): + if name == 'location': + self.fd = open(value, 'rb') + + def do_create(self, offset, size, wot): + if offset != self.curoffset: + self.fd.seek(offset, 0) + data = self.fd.read(self.blocksize) + if data: + self.curoffset += len(data) + return Gst.FlowReturn.OK, Gst.Buffer.new_wrapped(data) + else: + return Gst.FlowReturn.EOS, None + + +GObject.type_register(FileSource) +__gstelementfactory__ = ("filesrc_py", Gst.Rank.NONE, FileSource) + + +def main(args): + Gst.init() + + if len(args) != 3: + print(f'Usage: {args[0]} input output') + return -1 + + bin = Gst.Pipeline('pipeline') + + filesrc = FileSource('filesource') + assert filesrc + filesrc.set_property('location', args[1]) + + filesink = Gst.ElementFactory.make('filesink', 'sink') + filesink.set_property('location', args[2]) + + bin.add(filesrc, filesink) + Gst.Element.link_many(filesrc, filesink) + + bin.set_state(Gst.State.PLAYING) + mainloop = GLib.MainLoop() + + def bus_event(bus, message): + t = message.type + if t == Gst.MessageType.EOS: + mainloop.quit() + elif t == Gst.MessageType.ERROR: + err, debug = message.parse_error() + print(f"Error: {err}, {debug}") + mainloop.quit() + return True + bus = bin.get_bus() + bus.add_signal_watch() + bus.connect('message', bus_event) + + mainloop.run() + bin.set_state(Gst.State.NULL) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/subprojects/gst-python/examples/plugins/python/mixer.py b/subprojects/gst-python/examples/plugins/python/mixer.py index 846508a8a5..34e9aad588 100644 --- a/subprojects/gst-python/examples/plugins/python/mixer.py +++ b/subprojects/gst-python/examples/plugins/python/mixer.py @@ -17,7 +17,6 @@ import gi gi.require_version('Gst', '1.0') gi.require_version('GstBase', '1.0') gi.require_version('GObject', '2.0') - from gi.repository import Gst, GObject, GstBase Gst.init_python() @@ -41,27 +40,29 @@ OCAPS = Gst.Caps(Gst.Structure('video/x-raw', height=240, framerate=Gst.Fraction(30, 1))) + class BlendData: def __init__(self, outimg): self.outimg = outimg self.pts = 0 self.eos = True + class Videomixer(GstBase.Aggregator): - __gstmetadata__ = ('Videomixer','Video/Mixer', \ - 'Python video mixer', 'Mathieu Duponchelle') + __gstmetadata__ = ('Videomixer', 'Video/Mixer', + 'Python video mixer', 'Mathieu Duponchelle') __gsttemplates__ = ( - Gst.PadTemplate.new_with_gtype("sink_%u", - Gst.PadDirection.SINK, - Gst.PadPresence.REQUEST, - ICAPS, - GstBase.AggregatorPad.__gtype__), - Gst.PadTemplate.new_with_gtype("src", - Gst.PadDirection.SRC, - Gst.PadPresence.ALWAYS, - OCAPS, - GstBase.AggregatorPad.__gtype__) + Gst.PadTemplate.new_with_gtype("sink_%u", + Gst.PadDirection.SINK, + Gst.PadPresence.REQUEST, + ICAPS, + GstBase.AggregatorPad.__gtype__), + Gst.PadTemplate.new_with_gtype("src", + Gst.PadDirection.SRC, + Gst.PadPresence.ALWAYS, + OCAPS, + GstBase.AggregatorPad.__gtype__) ) def mix_buffers(self, agg, pad, bdata): @@ -73,6 +74,10 @@ class Videomixer(GstBase.Aggregator): bdata.outimg = Image.blend(bdata.outimg, img, alpha=0.5) bdata.pts = buf.pts + # Need to ensure the PIL image has been released, or unmap will fail + # with an outstanding memoryview buffer error + del img + buf.unmap(info) bdata.eos = False @@ -91,7 +96,7 @@ class Videomixer(GstBase.Aggregator): outbuf = Gst.Buffer.new_allocate(None, len(data), None) outbuf.fill(0, data) outbuf.pts = bdata.pts - self.finish_buffer (outbuf) + self.finish_buffer(outbuf) # We are EOS when no pad was ready to be aggregated, # this would obviously not work for live @@ -100,5 +105,6 @@ class Videomixer(GstBase.Aggregator): return Gst.FlowReturn.OK + GObject.type_register(Videomixer) __gstelementfactory__ = ("py_videomixer", Gst.Rank.NONE, Videomixer)