GStreamer Python Bindings for Windows
Escrito por:
Andoni Morales29 de Noviembre de 2023
GStreamer is an incredibly powerful and flexible open-source multimedia framework that enables developers to create almost any kind of multimedia application, including media players, non-linear video editors, streaming services, or video conferencing systems. However, developing GStreamer applications can be challenging since the framework has an extensive API with a steep learning curve, which, combined with its C API, can act as an entry barrier for new developers.
The GStreamer Python bindings offer a very convenient way to write GStreamer applications and plugins, both for newcomers taking their first steps in the frameworks and for experienced developers needing to prototype a plugin or a multimedia application quickly. They provide complete access to the API in an idiomatic way, allowing anyone familiar with Python to start leveraging all the power of GStreamer immediately. The bindings are based on PyGObject with an additional layer of overrides to make them more idiomatic.
Even though the Python bindings have existed for a long time, they aren't packaged in the official Windows installers and need to be built manually, involving several steps. Thanks to work done by Fluendo, it's now possible to build the Python bindings with meson and subprojects as part of the regular GStreamer monorepo build, simplifying this process a lot.
In this blog post, we will explain the work done to achieve this milestone and a guide on building and using the bindings.
Building GObject Introspection as a meson subproject
The first part of getting the bindings building with meson was adding support to build GObject Introspection as a meson subproject. The main problem is that the GLib introspection data (the .gir's and .typelib's for GLib, GObject, etc...) is built by GObject Introspection instead of done by GLib and GObject Introspection depends on GLib, creating a circular dependency between them. This means GObject Introspection needs to retrieve some information from the GLib subproject, such as the location of the source files and headers for example.
Another problem is that GObject Introspection can't use meson's gnome.generate_dir since this meson module requires the 'gobject-introspection-1.0' dependency that is provided by GObject Introspection itself, which is a chicken-egg problem. This means all of the gir's need to be built with custom commands (in this MR we solved the problem for when GObject Introspection is a subproject dependency, but it wouldn’t work when it's a pkg-config dependency)
Fixing everything required several MRs on both GLib and GObject Introspection:
-
GObject introspection:
-
https://gitlab.gnome.org/GNOME/gobject-introspection/-/merge_requests/312
-
https://gitlab.gnome.org/GNOME/gobject-introspection/-/merge_requests/330
-
https://gitlab.gnome.org/GNOME/gobject-introspection/-/merge_requests/333
-
https://gitlab.gnome.org/GNOME/gobject-introspection/-/merge_requests/363
-
https://gitlab.gnome.org/GNOME/gobject-introspection/-/merge_requests/412
-
-
GLib
Some work is planned to break that circular dependency and improve the current situation.
Adding introspection support in GStreamer
The second part involved enabling introspection to build in GStreamer with meson using subprojects. The work done here consisted of adding GObject Introspection as a subproject, ensuring the introspection, and enabling the python and introspection options in the Windows CI jobs. All this work is upstream, and it was merged in the gi: support gi generation with subprojects MR
GStreamer Python Bindings on Windows
All the work described in the previous steps makes it now possible to build the GStreamer Python bindings using the monorepo and subprojects. In this section, we will explain how to set up the build environment, build the bindings, and use our freshly built bindings.
Building the bindings
Building the GStreamer bindings is now a straightforward process compiling from the main branch.
The first step is to check out the GStreamer sources
git clone https://gitlab.freedesktop.org/gstreamer/gstreamer.git
cd gstreamer
GObject Introspection requires pkg-config, which GStreamer provides through a subproject. Due to a limitation in Meson, we still need to instruct g-ir-scanner on how to find it with the PKG_CONFIG environment variable. This is only required if you don't have pkg-config available in your PATH.
$env:PKG_CONFIG="${pwd}\subprojects\win-pkgconfig\pkg-config.exe"
Let's now configure the project with introspection enabled and set the installation path to c:\gstreamer-python:
meson setup build -Dintrospection=enabled --prefix c:/gstreamer-python
If everything is correct, you should gobject-introspection and gst-python reporting YES in the subprojects summary:
gobject-introspection : YES 4 warnings (from gst-python => pygobject)
gst-python : YES 1 warning
Let's finally build and install everything:
meson compile -C .\build
meson install -C .\build
To verify everything was built and installed correctly, you can check the following files were installed:
C:/gstreamer-python\lib\girepository-1.0\Gst-1.0.typelib
C:/gstreamer-python\lib\site-packages\gi\module.py
C:/gstreamer-python\lib\site-packages\gi\overrides\Gst.py
Let's wrap it up:
git clone https://gitlab.freedesktop.org/gstreamer/gstreamer.git
cd gstreamer
$env:PKG_CONFIG="${pwd}\subprojects\win-pkgconfig\pkg-config.exe"
meson setup build --prefix c:/gstreamer-python
meson compile -C .\build
meson install -C .\build
Using the bindings
To use the bindings in our applications, we will need to let our application know where GI's Python module and GStreamer are installed. We will show how to do it with this sample application that prints the GStreamer version:
import gi
gi.require_version('Gst', '1.0')
from gi.repository import Gst
Gst.init(sys.argv)
print(Gst.version_string())
We will see different errors if we try to launch this application without configuring it correctly. Let's solve them one by one in the next sections.
Unable to find the GI module from the GStreamer installation
The first issue is that Python is unable to locate the PyGObject's GI module:
ModuleNotFoundError: No module named 'gi'
We need to let Python know where our freshly built module adding the site-packages directory to the sys.path
import sys
sys.path.append('C:/gstreamer-python/Lib/site-packages')
This can also be done with an environment variable:
env:PYTHONPATH="C:/gstreamer-python/Lib/site-packages"
Unable to load GI's C extension
The next issue we will see is that we are unable to load our module because it can't load GI's C extension:
ImportError: DLL load failed while importing _gi:
The specified module could not be found.
This happens because GI's module C extension links to GLib and GObject Introspection, among other libraries, and Python cannot find them. Starting with Python 3.8, DLL's search paths must be added with os.add_dll_directory since PATH is no longer used to resolve dependencies for imported extension modules.
os.add_dll_directory('C:/gstreamer-python/bin')
Unable to initialize GStreamer
The next issue when trying to import the GStreamer module is that it's unable to locate the GStreamer shared library: 'gstreamer-1.0-0.dll'
Failed to load shared library 'gstreamer-1.0-0.dll' referenced by the typelib: 'gstreamer-1.0-0.dll':
The specified module could not be found.
To fix it, we also need to add our binaries directory to PATH so that shared libraries can be resolved by the dynamic linker:
os.environ['PATH'] = 'C:/gstreamer-python/bin'
This can also be done with an environment variable in PowerShell:
env:PATH="C:/gstreamer-python/bin;$env:PATH"
Application
After initializing our application correctly, everything should be working correctly. Let's wrap it up:
import os
import sys
from pathlib import Path
gst_install_dir = Path('c:/gstreamer-python')
gst_bin_dir = gst_install_dir / 'bin'
sys.path.append(str(gst_install_dir / 'Lib' / 'site-packages'))
os.environ['PATH'] = str(gst_bin_dir)
os.add_dll_directory(gst_bin_dir)
import gi
gi.require_version('Gst', '1.0')
from gi.repository import Gst
Gst.init(sys.argv)
print(Gst.version_string())
Next steps
We are working on building the Python bindings with cerbero and shipping them as part of the official installers. Most of the comments from this MR have been resolved, and we hope to have it merged for the next stable release. Stay tuned for our next update!