...
 
Commits (38)
include: "https://gitlab.freedesktop.org/gstreamer/gst-ci/raw/master/gitlab/ci_template.yml"
[submodule "common"]
path = common
url = https://anongit.freedesktop.org/git/gstreamer/common.git
url = https://gitlab.freedesktop.org/gstreamer/common.git
=== release 1.16.0 ===
2019-04-19 00:37:16 +0100 Tim-Philipp Müller <tim@centricular.com>
* ChangeLog:
* NEWS:
* RELEASE:
* configure.ac:
* gst-python.doap:
* meson.build:
Release 1.16.0
2019-04-11 15:00:15 -0400 Luis de Bethencourt <luisbg@amazon.com>
* TODO:
Update TODO
=== release 1.15.90 ===
2019-04-11 00:38:39 +0100 Tim-Philipp Müller <tim@centricular.com>
* ChangeLog:
* NEWS:
* RELEASE:
* configure.ac:
* gst-python.doap:
* meson.build:
Release 1.15.90
2019-03-04 09:15:26 +0000 Tim-Philipp Müller <tim@centricular.com>
* NEWS:
* RELEASE:
* configure.ac:
* meson.build:
Back to development
=== release 1.15.2 ===
2019-02-26 12:00:58 +0000 Tim-Philipp Müller <tim@centricular.com>
* ChangeLog:
* NEWS:
* RELEASE:
* configure.ac:
* gst-python.doap:
* meson.build:
Release 1.15.2
2019-01-30 15:45:21 -0300 Thibault Saunier <tsaunier@igalia.com>
* gi/overrides/Gst.py:
* gi/overrides/GstPbutils.py:
Gst.init() has to be called before GstPbutils is imported
This makes sure that we do not try to use GstPbutils before Gst is init
and in case GstPbutils is imported while Gst is not imported, use the
`GstPbutils.pb_utils_init()` function to have the oportunity to
initialize the overrides.
Not that we also introduce a `GstPbutils.init()` variant because
`GstPbutils.pb_utils_init()` is an ugly name.
2019-01-22 16:59:02 -0300 Thibault Saunier <tsaunier@igalia.com>
* meson.build:
* meson_options.txt:
meson: Re add workarounds to detect libpython path
This was removed all together in af4ade37435fcc31c8489f4d7c7496fef5f74b05
"meson: use new python module".
And add `-Dlibpython-dir` option for the cases the logic fails.
=== release 1.15.1 ===
2019-01-17 02:33:52 +0000 Tim-Philipp Müller <tim@centricular.com>
* ChangeLog:
* NEWS:
* RELEASE:
* configure.ac:
* gst-python.doap:
* meson.build:
Release 1.15.1
2019-01-09 11:39:19 +0100 Antonio Ospite <ao2@ao2.it>
* gi/overrides/Gst.py:
overrides: add a set_caps() method to the Pad override
The C API provides the gst_pad_set_caps() helper which makes it easier
to set caps on pads (see gst/gstcompat.h in gstreamer core).
Add such handy helper to the python bindings too.
The implementation follows as close as possible the one in gstcompat.h
with two changes:
1. the type check on the pad has been removed because self is
guaranteed to be a Gst.Pad in python.
2. the null check on the caps has been extended to be a type check.
Fixes https://gitlab.freedesktop.org/gstreamer/gst-python/issues/19
2018-12-30 23:53:03 +0000 Tim-Philipp Müller <tim@centricular.com>
* autogen.sh:
autogen.sh: update to match updated common submodule
Unbreaks the autotools build and fixes #15.
2018-12-15 13:55:07 +0000 Tim-Philipp Müller <tim@centricular.com>
* testsuite/old/test-object.c:
* testsuite/old/testhelpermodule.c:
Fix indentation of .c files
Required to make gst-indent linter on CI happy.
2018-12-05 18:43:06 -0300 Thibault Saunier <tsaunier@igalia.com>
* common:
Update common submodule
2018-11-12 13:26:58 +0200 Jordan Petridis <jordan@centricular.com>
* .gitlab-ci.yml:
Add Gitlab CI configuration
This commit adds a .gitlab-ci.yml file, which uses a feature
to fetch the config from a centralized repository. The intent is
to have all the gstreamer modules use the same configuration.
The configuration is currently hosted at the gst-ci repository
under the gitlab/ci_template.yml path.
Part of https://gitlab.freedesktop.org/gstreamer/gstreamer-project/issues/29
2018-11-05 05:51:05 +0000 Matthew Waters <matthew@centricular.com>
* .gitmodules:
* gst-python.doap:
Update git locations to gitlab
2018-10-31 17:02:24 +0100 Mathieu Duponchelle <mathieu@centricular.com>
* testsuite/overrides_hack.py:
Tests: the sys.meta_path trick does not work for python2
Instead, for python2 revert to manipulating gi.overrides.__path__
2018-10-31 00:41:31 +0100 Mathieu Duponchelle <mathieu@centricular.com>
* gi/overrides/Gst.py:
* gi/overrides/meson.build:
* testsuite/Makefile.am:
* testsuite/meson.build:
* testsuite/overrides_hack.py:
Tests: refactor testing approach
Instead of fiddling with sys.path, we instead use a custom
sys.meta_path importer
2018-10-28 17:52:33 +0100 Mathieu Duponchelle <mathieu@centricular.com>
* Makefile.am:
* meson.build:
meson: address python module port comments
2018-05-14 10:05:15 +0200 Havard Graff <havard.graff@gmail.com>
* gi/overrides/Gst.py:
* gi/overrides/meson.build:
* meson.build:
* meson_options.txt:
* plugin/meson.build:
* scripts/pythondetector:
* testsuite/meson.build:
meson: use new python module
This patch makes the tests pass running uninstalled and installed, with
python2 and python3 on linux, windows and osx.
The main gist is to use the new python-module to do the lifting done
by pythondetector, and with that add support for python2 and windows.
2018-10-28 17:51:44 +0100 Mathieu Duponchelle <mathieu@centricular.com>
* gi/Makefile.am:
* gi/overrides/Makefile.am:
automake: remove __init__.py's
2018-10-28 14:14:09 +0100 Mathieu Duponchelle <mathieu@centricular.com>
* gi/__init__.py:
* gi/overrides/__init__.py:
Remove __init__.py files
They were not installed, and were simply used for our
uninstalled setup, which we now implement differently.
2018-10-27 18:04:11 +0200 Mathieu Duponchelle <mathieu@centricular.com>
* meson.build:
meson: add pygobject fallback
2018-07-29 20:06:09 +0200 Mathieu Duponchelle <mathieu@centricular.com>
* examples/plugins/python/py_audiotestsrc.py:
* examples/requirements.txt:
Examples: add audiotestsrc plugin example
2018-07-29 20:00:43 +0200 Mathieu Duponchelle <mathieu@centricular.com>
* examples/plugins/python/audioplot.py:
* examples/requirements.txt:
Examples: add audioplot plugin example
2018-07-29 19:51:34 +0200 Mathieu Duponchelle <mathieu@centricular.com>
* examples/README.md:
* examples/plugins/python/mixer.py:
* examples/requirements.txt:
Examples: add mixer plugin example
2018-07-20 17:00:22 +0200 Mathieu Duponchelle <mathieu@centricular.com>
* examples/dynamic_src.py:
examples: add a dynamic pipeline example
2018-07-20 15:58:35 +0200 Mathieu Duponchelle <mathieu@centricular.com>
* examples/helloworld.py:
helloworld: fix typo
2018-03-20 08:54:24 +0100 Havard Graff <havard.graff@gmail.com>
* gi/overrides/gstmodule.c:
gstmodule: fix warning when building against python2
PyMapping_GetItemString’ discards ‘const’ qualifier from pointer target type
https://bugzilla.gnome.org/show_bug.cgi?id=796093
2018-05-01 15:01:11 +0100 Tim-Philipp Müller <tim@centricular.com>
* Makefile.am:
Fix distcheck
2018-05-01 12:08:54 +0100 Tim-Philipp Müller <tim@centricular.com>
* config.h.meson:
* meson.build:
meson: drop config.h.meson template
2018-04-25 15:11:31 -0300 Thibault Saunier <tsaunier@igalia.com>
* configure.ac:
* meson.build:
Bump pygobject dependency to 3.8
2018-04-25 19:47:19 +0200 Emilio Pozuelo Monfort <pochu27@gmail.com>
* gi/overrides/Gst.py:
* gi/overrides/GstPbutils.py:
overrides: use get_introspection_module
https://bugzilla.gnome.org/show_bug.cgi?id=795555
2018-04-07 21:46:07 -0300 Thibault Saunier <tsaunier@igalia.com>
* gi/overrides/Gst.py:
overrides: Fix mixup between query function and chain one
2018-04-03 13:28:16 +0100 Tim-Philipp Müller <tim@centricular.com>
* Makefile.am:
Dist autogen.sh and configure.ac
2018-03-20 10:27:38 +0000 Tim-Philipp Müller <tim@centricular.com>
* NEWS:
* RELEASE:
* configure.ac:
* meson.build:
Back to development
=== release 1.14.0 ===
2018-03-19 20:29:28 +0000 Tim-Philipp Müller <tim@centricular.com>
......
......@@ -30,11 +30,11 @@ include $(top_srcdir)/common/cruft.mak
all-local: check-cruft
EXTRA_DIST = \
configure.ac autogen.sh depcomp \
RELEASE gst-python.doap \
meson.build \
meson_options.txt \
config.h.meson \
pygst.py.in \
scripts/pythondetector \
examples/plugins/python/sinkelement.py \
examples/plugins/python/identity.py \
examples/helloworld.py
This source diff could not be displayed because it is too large. You can view the blob instead.
This is GStreamer gst-python 1.14.0.
This is GStreamer gst-python 1.17.0.1.
The GStreamer team is thrilled to announce a new major feature release in the
stable 1.x API series of your favourite cross-platform multimedia framework!
stable 1.0 API series of your favourite cross-platform multimedia framework!
As always, this release is again packed with new features, bug fixes and
other improvements.
The 1.14 release series adds new features on top of the 1.12 series and is
The 1.16 release series adds new features on top of the 1.14 series and is
part of the API and ABI-stable 1.x release series of the GStreamer multimedia
framework.
Full release notes can be found at:
Full release notes will one day be found at:
https://gstreamer.freedesktop.org/releases/1.14/
https://gstreamer.freedesktop.org/releases/1.16/
Binaries for Android, iOS, Mac OS X and Windows will be provided shortly
after the release.
Binaries for Android, iOS, Mac OS X and Windows will usually be provided
shortly after the release.
This module will not be very useful by itself and should be used in conjunction
with other GStreamer modules for a complete multimedia experience.
......@@ -60,7 +60,7 @@ You can find source releases of gstreamer in the download
directory: https://gstreamer.freedesktop.org/src/gstreamer/
The git repository and details how to clone it can be found at
http://cgit.freedesktop.org/gstreamer/gstreamer/
https://cgit.freedesktop.org/gstreamer/gstreamer/
==== Homepage ====
......@@ -68,10 +68,16 @@ The project's website is https://gstreamer.freedesktop.org/
==== Support and Bugs ====
We use GNOME's bugzilla for bug reports and feature requests:
http://bugzilla.gnome.org/enter_bug.cgi?product=GStreamer
We have recently moved from GNOME Bugzilla to GitLab on freedesktop.org
for bug reports and feature requests:
Please submit patches via bugzilla as well.
https://gitlab.freedesktop.org/gstreamer
Please submit patches via GitLab as well, in form of Merge Requests. See
https://gstreamer.freedesktop.org/documentation/contribute/
for more details.
For help and support, please subscribe to and send questions to the
gstreamer-devel mailing list (see below for details).
......@@ -80,8 +86,14 @@ There is also a #gstreamer IRC channel on the Freenode IRC network.
==== Developers ====
GStreamer is stored in Git, hosted at git.freedesktop.org, and can be cloned
from there (see link above).
GStreamer source code repositories can be found on GitLab on freedesktop.org:
https://gitlab.freedesktop.org/gstreamer
and can also be cloned from there and this is also where you can submit
Merge Requests or file issues for bugs or feature requests.
Interested developers of the core library, plugins, and applications should
subscribe to the gstreamer-devel list.
subscribe to the gstreamer-devel list:
https://lists.freedesktop.org/mailman/listinfo/gstreamer-devel
Port tests and example to GStreamer 1.0
Port old examples to GStreamer 1.0
#!/bin/sh
#
# gst-python autogen.sh
#
# Run this to generate all the initial makefiles, etc.
#
# This file has been generated from common/autogen.sh.in via common/update-autogen
test -n "$srcdir" || srcdir=`dirname "$0"`
test -n "$srcdir" || srcdir=.
olddir=`pwd`
cd "$srcdir"
DIE=0
package=gst-python
srcfile=gi/overrides/gstmodule.c
srcfile=gst-python.doap
# Make sure we have common
if test ! -f common/gst-autogen.sh;
then
if test ! -f common/gst-autogen.sh;
then
echo "+ Setting up common submodule"
git submodule init
fi
git submodule update
# ensure that we have the dirs we put ext libs in to appease automake
mkdir -p gst-libs/ext/ffmpeg/ffmpeg
# source helper functions
if test ! -f common/gst-autogen.sh;
then
......@@ -29,34 +37,52 @@ fi
if test ! \( -x .git/hooks/pre-commit -a -L .git/hooks/pre-commit \);
then
rm -f .git/hooks/pre-commit
ln -s ../../common/hooks/pre-commit.hook .git/hooks/pre-commit
if ! ln -s ../../common/hooks/pre-commit.hook .git/hooks/pre-commit 2> /dev/null
then
echo "Failed to create commit hook symlink, copying instead ..."
cp common/hooks/pre-commit.hook .git/hooks/pre-commit
fi
fi
# GNU gettext automake support doesn't get along with git.
# https://bugzilla.gnome.org/show_bug.cgi?id=661128
if test -d po ; then
touch -t 200001010000 po/gst-python-1.0.pot
fi
CONFIGURE_DEF_OPT='--enable-maintainer-mode'
autogen_options $@
echo -n "+ check for build tools"
if test ! -z "$NOCHECK"; then echo " skipped"; else echo; fi
version_check "autoconf" "$AUTOCONF autoconf autoconf270 autoconf269 autoconf268 autoconf267 autoconf266 autoconf265 autoconf264 autoconf263 autoconf262 autoconf261 autoconf260" \
"ftp://ftp.gnu.org/pub/gnu/autoconf/" 2 60 || DIE=1
version_check "automake" "$AUTOMAKE automake automake-1.11 automake-1.10" \
"ftp://ftp.gnu.org/pub/gnu/automake/" 1 10 || DIE=1
version_check "libtoolize" "$LIBTOOLIZE libtoolize glibtoolize" \
"ftp://ftp.gnu.org/pub/gnu/libtool/" 1 5 0 || DIE=1
version_check "pkg-config" "" \
"http://www.freedesktop.org/software/pkgconfig" 0 8 0 || DIE=1
CONFIGURE_DEF_OPT='--enable-maintainer-mode --enable-gtk-doc'
die_check $DIE
if test "x$package" = "xgstreamer"; then
CONFIGURE_DEF_OPT="$CONFIGURE_DEF_OPT --enable-failing-tests --enable-poisoning"
elif test "x$package" = "xgst-plugins-bad"; then
CONFIGURE_DEF_OPT="$CONFIGURE_DEF_OPT --with-player-tests"
fi
aclocal_check || DIE=1
autoheader_check || DIE=1
autogen_options $@
die_check $DIE
printf "+ check for build tools"
if test -z "$NOCHECK"; then
echo
printf " checking for autoreconf ... "
echo
which "autoreconf" 2>/dev/null || {
echo "not found! Please install the autoconf package."
exit 1
}
printf " checking for pkg-config ... "
echo
which "pkg-config" 2>/dev/null || {
echo "not found! Please install pkg-config."
exit 1
}
else
echo ": skipped version checks"
fi
# if no arguments specified then this will be printed
if test -z "$*"; then
if test -z "$*" && test -z "$NOCONFIGURE"; then
echo "+ checking for autogen.sh options"
echo " This autogen script will automatically run ./configure as:"
echo " ./configure $CONFIGURE_DEF_OPT"
......@@ -66,29 +92,16 @@ fi
toplevel_check $srcfile
tool_run "$libtoolize" "--copy --force"
tool_run "$aclocal" "-I m4 -I common/m4 $ACLOCAL_FLAGS"
tool_run "$autoheader"
# touch the stamp-h.in build stamp so we don't re-run autoheader in maintainer mode
echo timestamp > stamp-h.in 2> /dev/null
tool_run "$autoconf"
tool_run "$automake" "-a -c -Wno-portability"
# if enable exists, add an -enable option for each of the lines in that file
if test -f enable; then
for a in `cat enable`; do
CONFIGURE_FILE_OPT="--enable-$a"
done
# autopoint
if test -d po && grep ^AM_GNU_GETTEXT_VERSION configure.ac >/dev/null ; then
tool_run "autopoint" "--force"
fi
# if disable exists, add an -disable option for each of the lines in that file
if test -f disable; then
for a in `cat disable`; do
CONFIGURE_FILE_OPT="$CONFIGURE_FILE_OPT --disable-$a"
done
fi
# aclocal
# we need AM_CHECK_PYTHON_HEADERS and AM_CHECK_PYTHON_LIBS from acinclude.m4
# if test -f acinclude.m4; then rm acinclude.m4; fi
autoreconf --force --install || exit 1
test -n "$NOCONFIGURE" && {
echo "+ skipping configure stage for package $package, as requested."
......@@ -96,13 +109,15 @@ test -n "$NOCONFIGURE" && {
exit 0
}
cd "$olddir"
echo "+ running configure ... "
test ! -z "$CONFIGURE_DEF_OPT" && echo " ./configure default flags: $CONFIGURE_DEF_OPT"
test ! -z "$CONFIGURE_EXT_OPT" && echo " ./configure external flags: $CONFIGURE_EXT_OPT"
test ! -z "$CONFIGURE_FILE_OPT" && echo " ./configure enable/disable flags: $CONFIGURE_FILE_OPT"
test ! -z "$CONFIGURE_DEF_OPT" && echo " default flags: $CONFIGURE_DEF_OPT"
test ! -z "$CONFIGURE_EXT_OPT" && echo " external flags: $CONFIGURE_EXT_OPT"
echo
./configure $CONFIGURE_DEF_OPT $CONFIGURE_EXT_OPT $CONFIGURE_FILE_OPT || {
echo "$srcdir/configure" $CONFIGURE_DEF_OPT $CONFIGURE_EXT_OPT
"$srcdir/configure" $CONFIGURE_DEF_OPT $CONFIGURE_EXT_OPT || {
echo " configure failed"
exit 1
}
......
Subproject commit d7ecca16114e443dab9d6f8cbc47a1554e3d4b30
Subproject commit 59cb678164719ff59dcf6c8b93df4617a1075d11
#mesondefine PACKAGE
#mesondefine VERSION
#mesondefine GST_PACKAGE_NAME
#mesondefine PACKAGE_NAME
#mesondefine GST_API_VERSION
#mesondefine PLUGINDIR
#mesondefine PY_LIB_LOC
#mesondefine PY_ABI_FLAGS
#mesondefine PY_LIB_SUFFIX
#mesondefine PYTHON_VERSION
......@@ -3,7 +3,7 @@ AC_PREREQ([2.68])
dnl initialize autoconf
dnl when going to/from release please set the nano (fourth number) right !
dnl releases only do Wall, cvs and prerelease does Werror too
AC_INIT(GStreamer GObject Introspection overrides for Python , 1.14.0,
AC_INIT(GStreamer GObject Introspection overrides for Python , 1.17.0.1,
http://bugzilla.gnome.org/enter_bug.cgi?product=GStreamer,
gst-python)
......@@ -38,8 +38,8 @@ AC_SUBST(ACLOCAL_AMFLAGS, "-I m4 -I common/m4")
dnl required versions of other packages
dnl Note that they are runtime requirements
AC_SUBST(GST_REQ, 1.14.0)
AC_SUBST(PYGOBJECT_REQ, 3.0)
AC_SUBST(GST_REQ, 1.17.0.1)
AC_SUBST(PYGOBJECT_REQ, 3.8)
AC_DISABLE_STATIC
......
# Dependencies
Some of the examples require external python dependencies, for this purpose
an illustrative requirements.txt is provided, with annotations documenting
which example requires a dependency.
You can install all the dependencies with:
```
python3 -m pip install -r requirements.txt --user
```
#!/usr/bin/env python3
'''
Simple example to demonstrate dynamically adding and removing source elements
to a playing pipeline.
'''
import sys
import random
import gi
gi.require_version('Gst', '1.0')
gi.require_version('GLib', '2.0')
gi.require_version('GObject', '2.0')
from gi.repository import GLib, GObject, Gst
class ProbeData:
def __init__(self, pipe, src):
self.pipe = pipe
self.src = src
def bus_call(bus, message, loop):
t = message.type
if t == Gst.MessageType.EOS:
sys.stdout.write("End-of-stream\n")
loop.quit()
elif t == Gst.MessageType.ERROR:
err, debug = message.parse_error()
sys.stderr.write("Error: %s: %s\n" % (err, debug))
loop.quit()
return True
def dispose_src_cb(src):
src.set_state(Gst.State.NULL)
def probe_cb(pad, info, pdata):
peer = pad.get_peer()
pad.unlink(peer)
pdata.pipe.remove(pdata.src)
# Can't set the state of the src to NULL from its streaming thread
GLib.idle_add(dispose_src_cb, pdata.src)
pdata.src = Gst.ElementFactory.make('videotestsrc')
pdata.src.props.pattern = random.randint(0, 24)
pdata.pipe.add(pdata.src)
srcpad = pdata.src.get_static_pad ("src")
srcpad.link(peer)
pdata.src.sync_state_with_parent()
GLib.timeout_add_seconds(1, timeout_cb, pdata)
return Gst.PadProbeReturn.REMOVE
def timeout_cb(pdata):
srcpad = pdata.src.get_static_pad('src')
srcpad.add_probe(Gst.PadProbeType.IDLE, probe_cb, pdata)
return GLib.SOURCE_REMOVE
def main(args):
GObject.threads_init()
Gst.init(None)
pipe = Gst.Pipeline.new('dynamic')
src = Gst.ElementFactory.make('videotestsrc')
sink = Gst.ElementFactory.make('autovideosink')
pipe.add(src, sink)
src.link(sink)
pdata = ProbeData(pipe, src)
loop = GObject.MainLoop()
GLib.timeout_add_seconds(1, timeout_cb, pdata)
bus = pipe.get_bus()
bus.add_signal_watch()
bus.connect ("message", bus_call, loop)
# start play back and listen to events
pipe.set_state(Gst.State.PLAYING)
try:
loop.run()
except:
pass
# cleanup
pipe.set_state(Gst.State.NULL)
if __name__ == '__main__':
sys.exit(main(sys.argv))
......@@ -9,7 +9,7 @@ from gi.repository import GObject, Gst
def bus_call(bus, message, loop):
t = message.type
if t == Gst.MessageType.EOS:
sys.stout.write("End-of-stream\n")
sys.stdout.write("End-of-stream\n")
loop.quit()
elif t == Gst.MessageType.ERROR:
err, debug = message.parse_error()
......
'''
Element that transforms audio samples to video frames representing
the waveform.
Requires matplotlib, numpy and numpy_ringbuffer
Example pipeline:
gst-launch-1.0 audiotestsrc ! audioplot window-duration=0.01 ! videoconvert ! autovideosink
'''
import gi
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:
import numpy as np
import matplotlib.patheffects as pe
from numpy_ringbuffer import RingBuffer
from matplotlib import pyplot as plt
from matplotlib.backends.backend_agg import FigureCanvasAgg
except ImportError:
Gst.error('audioplot requires numpy, numpy_ringbuffer and matplotlib')
raise
Gst.init(None)
AUDIO_FORMATS = [f.strip() for f in
GstAudio.AUDIO_FORMATS_ALL.strip('{ }').split(',')]
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))))
OCAPS = Gst.Caps(Gst.Structure('video/x-raw',
format='ARGB',
width=Gst.IntRange(range(1, GLib.MAXINT)),
height=Gst.IntRange(range(1, GLib.MAXINT)),
framerate=Gst.FractionRange(Gst.Fraction(1, 1),
Gst.Fraction(GLib.MAXINT, 1))))
DEFAULT_WINDOW_DURATION = 1.0
DEFAULT_WIDTH = 640
DEFAULT_HEIGHT = 480
DEFAULT_FRAMERATE_NUM = 25
DEFAULT_FRAMERATE_DENOM = 1
class AudioPlotFilter(GstBase.BaseTransform):
__gstmetadata__ = ('AudioPlotFilter','Filter', \
'Plot audio waveforms', 'Mathieu Duponchelle')
__gsttemplates__ = (Gst.PadTemplate.new("src",
Gst.PadDirection.SRC,
Gst.PadPresence.ALWAYS,
OCAPS),
Gst.PadTemplate.new("sink",
Gst.PadDirection.SINK,
Gst.PadPresence.ALWAYS,
ICAPS))
__gproperties__ = {
"window-duration": (float,
"Window Duration",
"Duration of the sliding window, in seconds",
0.01,
100.0,
DEFAULT_WINDOW_DURATION,
GObject.ParamFlags.READWRITE
)
}
def __init__(self):
GstBase.BaseTransform.__init__(self)
self.window_duration = DEFAULT_WINDOW_DURATION
def do_get_property(self, prop):
if prop.name == 'window-duration':
return self.window_duration
else:
raise AttributeError('unknown property %s' % prop.name)
def do_set_property(self, prop, value):
if prop.name == 'window-duration':
self.window_duration = value
else:
raise AttributeError('unknown property %s' % prop.name)
def do_transform(self, inbuf, outbuf):
if not self.h:
self.h, = self.ax.plot(np.array(self.ringbuffer),
lw=0.5,
color='k',
path_effects=[pe.Stroke(linewidth=1.0,
foreground='g'),
pe.Normal()])
else:
self.h.set_ydata(np.array(self.ringbuffer))
self.fig.canvas.restore_region(self.background)
self.ax.draw_artist(self.h)
self.fig.canvas.blit(self.ax.bbox)
s = self.agg.tostring_argb()
outbuf.fill(0, s)
outbuf.pts = self.next_time
outbuf.duration = self.frame_duration
self.next_time += self.frame_duration
return Gst.FlowReturn.OK
def __append(self, data):
arr = np.array(data)
end = self.thinning_factor * int(len(arr) / self.thinning_factor)
arr = np.mean(arr[:end].reshape(-1, self.thinning_factor), 1)
self.ringbuffer.extend(arr)
def do_generate_output(self):
inbuf = self.queued_buf
_, info = inbuf.map(Gst.MapFlags.READ)
res, data = self.converter.convert(GstAudio.AudioConverterFlags.NONE,
info.data)
data = memoryview(data).cast('i')
nsamples = len(data) - self.buf_offset
if nsamples == 0:
self.buf_offset = 0
inbuf.unmap(info)
return Gst.FlowReturn.OK, None
if self.cur_offset + nsamples < self.next_offset:
self.__append(data[self.buf_offset:])
self.buf_offset = 0
self.cur_offset += nsamples
inbuf.unmap(info)
return Gst.FlowReturn.OK, None
consumed = self.next_offset - self.cur_offset
self.__append(data[self.buf_offset:self.buf_offset + consumed])
inbuf.unmap(info)
_, outbuf = GstBase.BaseTransform.do_prepare_output_buffer(self, inbuf)
ret = self.do_transform(inbuf, outbuf)
self.next_offset += self.samplesperbuffer
self.cur_offset += consumed
self.buf_offset += consumed
return ret, outbuf
def do_transform_caps(self, direction, caps, filter_):
if direction == Gst.PadDirection.SRC:
res = ICAPS
else:
res = OCAPS
if filter_:
res = res.intersect(filter_)
return res
def do_fixate_caps(self, direction, caps, othercaps):
if direction == Gst.PadDirection.SRC:
return othercaps.fixate()
else:
so = othercaps.get_structure(0).copy()
so.fixate_field_nearest_fraction("framerate",
DEFAULT_FRAMERATE_NUM,
DEFAULT_FRAMERATE_DENOM)
so.fixate_field_nearest_int("width", DEFAULT_WIDTH)
so.fixate_field_nearest_int("height", DEFAULT_HEIGHT)
ret = Gst.Caps.new_empty()
ret.append_structure(so)
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)
self.convert_info = GstAudio.AudioInfo()
self.convert_info.set_format(GstAudio.AudioFormat.S32,
in_info.rate,
in_info.channels,
in_info.position)
self.converter = GstAudio.AudioConverter.new(GstAudio.AudioConverterFlags.NONE,
in_info,
self.convert_info,
None)
self.fig = plt.figure()
dpi = self.fig.get_dpi()
self.fig.patch.set_alpha(0.3)
self.fig.set_size_inches(out_info.width / float(dpi),
out_info.height / float(dpi))
self.ax = plt.Axes(self.fig, [0., 0., 1., 1.])
self.fig.add_axes(self.ax)
self.ax.set_axis_off()
self.ax.set_ylim((GLib.MININT, GLib.MAXINT))
self.agg = self.fig.canvas.switch_backends(FigureCanvasAgg)
self.h = None
samplesperwindow = int(in_info.rate * in_info.channels * self.window_duration)
self.thinning_factor = max(int(samplesperwindow / out_info.width - 1), 1)
cap = int(samplesperwindow / self.thinning_factor)
self.ax.set_xlim([0, cap])
self.ringbuffer = RingBuffer(capacity=cap)
self.ringbuffer.extend([0.0] * cap)
self.frame_duration = Gst.util_uint64_scale_int(Gst.SECOND,
out_info.fps_d,
out_info.fps_n)
self.next_time = self.frame_duration
self.agg.draw()
self.background = self.fig.canvas.copy_from_bbox(self.ax.bbox)
self.samplesperbuffer = Gst.util_uint64_scale_int(in_info.rate * in_info.channels,
out_info.fps_d,
out_info.fps_n)
self.next_offset = self.samplesperbuffer
self.cur_offset = 0
self.buf_offset = 0
return True
GObject.type_register(AudioPlotFilter)
__gstelementfactory__ = ("audioplot", Gst.Rank.NONE, AudioPlotFilter)
'''
Simple mixer element, accepts 320 x 240 RGBA at 30 fps
on any number of sinkpads.
Requires PIL (Python Imaging Library)
Example pipeline:
gst-launch-1.0 py_videomixer name=mixer ! videoconvert ! autovideosink \
videotestsrc ! mixer. \
videotestsrc pattern=ball ! mixer. \
videotestsrc pattern=snow ! mixer.
'''
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(None)
try:
from PIL import Image
except ImportError:
Gst.error('py_videomixer requires PIL')
raise
# Completely fixed input / output
ICAPS = Gst.Caps(Gst.Structure('video/x-raw',
format='RGBA',
width=320,
height=240,
framerate=Gst.Fraction(30, 1)))
OCAPS = Gst.Caps(Gst.Structure('video/x-raw',
format='RGBA',
width=320,
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')
__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__)
)
def mix_buffers(self, agg, pad, bdata):
buf = pad.pop_buffer()
_, info = buf.map(Gst.MapFlags.READ)
img = Image.frombuffer('RGBA', (320, 240), info.data, "raw", 'RGBA', 0, 1)
bdata.outimg = Image.blend(bdata.outimg, img, alpha=0.5)
bdata.pts = buf.pts
buf.unmap(info)
bdata.eos = False
return True
def do_aggregate(self, timeout):
outimg = Image.new('RGBA', (320, 240), 0x00000000)
bdata = BlendData(outimg)
self.foreach_sink_pad(self.mix_buffers, bdata)
data = bdata.outimg.tobytes()
outbuf = Gst.Buffer.new_allocate(None, len(data), None)
outbuf.fill(0, data)
outbuf.pts = bdata.pts
self.finish_buffer (outbuf)
# We are EOS when no pad was ready to be aggregated,
# this would obviously not work for live
if bdata.eos:
return Gst.FlowReturn.EOS
return Gst.FlowReturn.OK
GObject.type_register(Videomixer)
__gstelementfactory__ = ("py_videomixer", Gst.Rank.NONE, Videomixer)
'''
Element that generates a sine audio wave with the specified frequency
Requires numpy
Example pipeline:
gst-launch-1.0 py_audiotestsrc ! autoaudiosink
'''
import gi
gi.require_version('Gst', '1.0')
gi.require_version('GstBase', '1.0')
gi.require_version('GstAudio', '1.0')
from gi.repository import Gst, GLib, GObject, GstBase, GstAudio
try:
import numpy as np
except ImportError:
Gst.error('py_audiotestsrc requires numpy')
raise
OCAPS = Gst.Caps.from_string (
'audio/x-raw, format=F32LE, layout=interleaved, rate=44100, channels=2')
SAMPLESPERBUFFER = 1024
DEFAULT_FREQ = 440
DEFAULT_VOLUME = 0.8
DEFAULT_MUTE = False
DEFAULT_IS_LIVE = False
class AudioTestSrc(GstBase.BaseSrc):
__gstmetadata__ = ('CustomSrc','Src', \
'Custom test src element', 'Mathieu Duponchelle')
__gproperties__ = {
"freq": (int,
"Frequency",
"Frequency of test signal",
1,
GLib.MAXINT,
DEFAULT_FREQ,
GObject.ParamFlags.READWRITE
),
"volume": (float,
"Volume",
"Volume of test signal",
0.0,
1.0,
DEFAULT_VOLUME,
GObject.ParamFlags.READWRITE
),
"mute": (bool,
"Mute",
"Mute the test signal",
DEFAULT_MUTE,
GObject.ParamFlags.READWRITE
),
"is-live": (bool,
"Is live",
"Whether to act as a live source",
DEFAULT_IS_LIVE,
GObject.ParamFlags.READWRITE
),
}
__gsttemplates__ = Gst.PadTemplate.new("src",
Gst.PadDirection.SRC,
Gst.PadPresence.ALWAYS,
OCAPS)
def __init__(self):
GstBase.BaseSrc.__init__(self)
self.info = GstAudio.AudioInfo()
self.freq = DEFAULT_FREQ
self.volume = DEFAULT_VOLUME
self.mute = DEFAULT_MUTE
self.set_live(DEFAULT_IS_LIVE)
self.set_format(Gst.Format.TIME)
def do_set_caps(self, caps):
self.info.from_caps(caps)
self.set_blocksize(self.info.bpf * SAMPLESPERBUFFER)
return True
def do_get_property(self, prop):
if prop.name == 'freq':
return self.freq
elif prop.name == 'volume':
return self.volume
elif prop.name == 'mute':
return self.mute
elif prop.name == 'is-live':
return self.is_live
else:
raise AttributeError('unknown property %s' % prop.name)
def do_set_property(self, prop, value):
if prop.name == 'freq':
self.freq = value
elif prop.name == 'volume':
self.volume = value
elif prop.name == 'mute':
self.mute = value
elif prop.name == 'is-live':
self.set_live(value)
else:
raise AttributeError('unknown property %s' % prop.name)
def do_start (self):
self.next_sample = 0
self.next_byte = 0
self.next_time = 0
self.accumulator = 0
self.generate_samples_per_buffer = SAMPLESPERBUFFER
return True
def do_gst_base_src_query(self, query):
if query.type == Gst.QueryType.LATENCY:
latency = Gst.util_uint64_scale_int(self.generate_samples_per_buffer,
Gst.SECOND, self.info.rate)
is_live = self.is_live
query.set_latency(is_live, latency, Gst.CLOCK_TIME_NONE)
res = True
else:
res = GstBase.BaseSrc.do_query(self, query)
return res
def do_get_times(self, buf):
end = 0
start = 0
if self.is_live:
ts = buf.pts
if ts != Gst.CLOCK_TIME_NONE:
duration = buf.duration
if duration != Gst.CLOCK_TIME_NONE:
end = ts + duration
start = ts
else:
start = Gst.CLOCK_TIME_NONE
end = Gst.CLOCK_TIME_NONE
return start, end
def do_create(self, offset, length):
if length == -1:
samples = SAMPLESPERBUFFER
else:
samples = int(length / self.info.bpf)
self.generate_samples_per_buffer = samples
bytes_ = samples * self.info.bpf
next_sample = self.next_sample + samples
next_byte = self.next_byte + bytes_
next_time = Gst.util_uint64_scale_int(next_sample, Gst.SECOND, self.info.rate)
if not self.mute:
r = np.repeat(
np.arange(self.accumulator, self.accumulator + samples),
self.info.channels)
data = ((np.sin(2 * np.pi * r * self.freq / self.info.rate) * self.volume)
.astype(np.float32))
else:
data = [0] * bytes_
buf = Gst.Buffer.new_wrapped(bytes(data))
buf.offset = self.next_sample
buf.offset_end = next_sample
buf.pts = self.next_time
buf.duration = next_time - self.next_time
self.next_time = next_time
self.next_sample = next_sample
self.next_byte = next_byte
self.accumulator += samples
self.accumulator %= self.info.rate / self.freq
return (Gst.FlowReturn.OK, buf)
__gstelementfactory__ = ("py_audiotestsrc", Gst.Rank.NONE, AudioTestSrc)
# py_videomixer plugin
Pillow >= 5.1.0
# audioplot plugin
matplotlib >= 2.1.1
numpy_ringbuffer >= 0.2.1
# audioplot and py_audiotestsrc plugins
numpy >= 1.14.5
SUBDIRS = overrides
EXTRA_DIST = \
__init__.py \
meson.build
#!/usr/bin/env python
#
# __init__.py
#
# Copyright (C) 2012 Thibault Saunier <thibaul.saunier@collabora.com>
#
# 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, write to the
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
# Boston, MA 02110-1301, USA.
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)
......@@ -26,8 +26,9 @@
import sys
import inspect
import itertools
from ..overrides import override
from ..importer import modules
from ..module import get_introspection_module
from gi.repository import GLib
......@@ -40,7 +41,8 @@ else:
_basestring = basestring
_callable = callable
Gst = modules['Gst']._introspection_module
Gst = get_introspection_module('Gst')
__all__ = []
if Gst._version == '0.10':
......@@ -54,6 +56,22 @@ python module to use with Gst 0.10"
warnings.warn(warn_msg, RuntimeWarning)
class Element(Gst.Element):
@staticmethod
def link_many(*args):
'''
@raises: Gst.LinkError
'''
for pair in pairwise(args):
if not pair[0].link(pair[1]):
raise LinkError(
'Failed to link {} and {}'.format(pair[0], pair[1]))
Element = override(Element)
__all__.append('Element')
class Bin(Gst.Bin):
def __init__(self, name=None):
Gst.Bin.__init__(self, name=name)
......@@ -63,6 +81,18 @@ class Bin(Gst.Bin):
if not Gst.Bin.add(self, arg):
raise AddError(arg)
def make_and_add(self, factory_name, instance_name=None):
'''
@raises: Gst.AddError
'''
elem = Gst.ElementFactory.make(factory_name, instance_name)
if not elem:
raise AddError(
'No such element: {}'.format(factory_name))
self.add(elem)
return elem
Bin = override(Bin)
__all__.append('Bin')
......@@ -146,7 +176,7 @@ class Pad(Gst.Pad):
def set_query_function(self, func):
self._real_query_func = func
self.set_query_function_full(self._chain_override, None)
self.set_query_function_full(self._query_override, None)
def set_query_function_full(self, func, udata):
self._real_query_func = func
......@@ -155,6 +185,22 @@ class Pad(Gst.Pad):
def query_caps(self, filter=None):
return Gst.Pad.query_caps(self, filter)
def set_caps(self, caps):
if not isinstance(caps, Gst.Caps):
raise TypeError("%s is not a Gst.Caps." % (type(caps)))
if not caps.is_fixed():
return False
event = Gst.Event.new_caps(caps)
if self.direction == Gst.PadDirection.SRC:
res = self.push_event(event)
else:
res = self.send_event(event)
return res
def link(self, pad):
ret = Gst.Pad.link(self, pad)
if ret != Gst.PadLinkReturn.OK:
......@@ -554,6 +600,14 @@ class ValueList(Gst.ValueList):
ValueList = override(ValueList)
__all__.append('ValueList')
# From https://docs.python.org/3/library/itertools.html
def pairwise(iterable):
a, b = itertools.tee(iterable)
next(b, None)
return zip(a, b)
def TIME_ARGS(time):
if time == Gst.CLOCK_TIME_NONE:
......@@ -565,7 +619,7 @@ def TIME_ARGS(time):
time % Gst.SECOND)
__all__.append('TIME_ARGS')
from . import _gi_gst
from gi.overrides import _gi_gst
_gi_gst
# maybe more python and less C some day if core turns a bit more introspection
......@@ -609,7 +663,7 @@ def init_pygst():
def deinit_pygst():
for fname, func in real_functions:
if fname not in ["init", "init_check", "deinit"]:
if fname not in ["init", "init_check", "deinit", "is_initialized"]:
setattr(Gst, fname, fake_method)
for cname_class, methods in class_methods:
for mname, method in methods:
......
......@@ -25,14 +25,14 @@
# any later version.
from ..overrides import override as override_
from ..importer import modules
from ..module import get_introspection_module
import gi
gi.require_version('Gst', '1.0')
from gi.repository import Gst # noqa
GstPbutils = modules['GstPbutils']._introspection_module
GstPbutils = get_introspection_module('GstPbutils')
__all__ = []
......@@ -43,41 +43,50 @@ def override(cls):
return cls
real_init = GstPbutils.pb_utils_init
def init():
if not Gst.is_initialized():
raise RuntimeError("Gst.init() needs to be called before importing GstPbutils")
@override
class EncodingVideoProfile(GstPbutils.EncodingVideoProfile):
def __init__(self, format, preset=None, restriction=None, presence=0):
GstPbutils.EncodingVideoProfile.__init__(self)
self.set_format(format)
if preset is not None:
self.set_preset(preset)
if restriction is None:
restriction = Gst.Caps('ANY')
self.set_restriction(restriction)
self.set_presence(presence)
real_init()
@override
class EncodingVideoProfile(GstPbutils.EncodingVideoProfile):
def __init__(self, format, preset=None, restriction=None, presence=0):
GstPbutils.EncodingVideoProfile.__init__(self)
self.set_format(format)
if preset is not None:
self.set_preset(preset)
if restriction is None:
restriction = Gst.Caps('ANY')
self.set_restriction(restriction)
self.set_presence(presence)
@override
class EncodingAudioProfile(GstPbutils.EncodingAudioProfile):
def __init__(self, format, preset=None, restriction=None, presence=0):
GstPbutils.EncodingAudioProfile.__init__(self)
self.set_format(format)
if preset is not None:
self.set_preset(preset)
if restriction is None:
restriction = Gst.Caps('ANY')
self.set_restriction(restriction)
self.set_presence(presence)
@override
class EncodingAudioProfile(GstPbutils.EncodingAudioProfile):
def __init__(self, format, preset=None, restriction=None, presence=0):
GstPbutils.EncodingAudioProfile.__init__(self)
self.set_format(format)
if preset is not None:
self.set_preset(preset)
if restriction is None:
restriction = Gst.Caps('ANY')
self.set_restriction(restriction)
self.set_presence(presence)
@override
class EncodingContainerProfile(GstPbutils.EncodingContainerProfile):
def __init__(self, name, description, format, preset=None):
GstPbutils.EncodingContainerProfile.__init__(self)
self.set_format(format)
if name is not None:
self.set_name(name)
if description is not None:
self.set_description(description)
if preset is not