UP | HOME

Python GStreamer Tutorial

Table of Contents

This tutorial aims at giving a brief introduction to the GStreamer 1.0 multimedia framework using its Python bindings.

1 Meta

A GStreamer application is built as a directed, acyclic graph. In the figures these graphs are illustrated with the convention:

  • right solid line rectangles indicate basic GStreamer elements
  • rounded solid line rectangles to indicate GStreamer bins (and pipelines) subclasses of elements.
  • rounded dashed line rectangles to indicate pads
If you are reading this from a web browser that supports JavaScript you can use some of the built-in features for navigating the document. Click on the "HELP" link or type "?" for instructions on how to use this feature.

2 Introduction

This tutorial is meant to be a quick way to get to know more about GStreamer but it'll take some time to write it though because we don't know it ourselves … yet. We're usually using GNU/Linux and GTK in the examples but we try to keep the GUI code to an absolute minimum so it should not get in the way. Just remember that GStreamer depends heavily on Glib so you must make sure that the Glib Mainloop is running if you want to catch events on the bus. We take for granted that you are at least a fairly descent Python coder. For problems related to the Python language we redirect you over to Online Python docs.

There are also some example coding distributed with the PyGST source which you may browse at the gst-python git repository. Reference documents for GStreamer and the rest of the ecosystem it relies on are available at lazka's GitHub site. The main GStreamer site has Reference Manual, FAQ, Applications Development Manual and Plugin Writer's Guide. This tutorial targets the GStreamer 1.0 API which all v1.x releases should follow. The Novacut project has a guide to porting Python applications from the prior 0.1 API to 1.0. Finally, GStreamer provides the GstSDK documentation which includes substantial C programming tutorials.

As you may see this tutorial is far from done and we are always looking for new people to join this project. If you want to write a chapter, fix the examples, provide alternatives or otherwise improve this document please do so. It is suggested to clone the repository on GitHub and issue a pull request. Note, the original source of this document was here but is now dead.

2.1 Command Line

Before getting started with Python some of the command line interface (CLI) programs that come with GStreamer are explored. Besides being generally useful they can help you find and try out what you need in a very fast and convenient way without writing a bit of code. With gst-inspect you can track highlevel elements which are shipped with the various plugins packages.

man gst-inspect-1.0

If you are looking for an element but you don't know its name you can use it with grep. Getting the elements that handles ie mp3 is done like this:

gst-inspect-1.0 | grep mp3 | sort | head -3
flump3dec:  flump3dec: Fluendo MP3 Decoder (liboil build)
lame:  lamemp3enc: L.A.M.E. mp3 encoder
libav:  avdec_mp3adufloat: libav ADU (Application Data Unit) MP3 (MPEG audio layer 3) decoder

The playbin element is an autoplugger which usually plays anything you throw at it, if you have the appropriate plugins installed.

gst-inspect-1.0 playbin > gst-inspect-playbin.txt

Browse example output here.

You can also run pipelines directly in a terminal with gst-launch:

man gst-launch-1.0

For playing a file with playbin:

gst-launch-1.0 playbin \
    uri=http://docs.gstreamer.com/media/sintel_trailer-480p.webm

It's also possible to link elements together with "!":

gst-launch-1.0 audiotestsrc ! alsasink

You may also make different streams in the pipeline:

gst-launch-1.0 audiotestsrc ! alsasink videotestsrc ! xvimagesink

Or, you can make a single frame JPEG

gst-launch-1.0 videotestsrc num-buffers=1 ! jpegenc ! \
  filesink location=videotestsrc-frame.jpg

videotestsrc-frame.jpg

If you are using the "name" property you may use the same element more than once. Just put a "." after its name, eg with oggmux here.

gst-launch-1.0 audiotestsrc ! vorbisenc ! oggmux name=mux ! \
  filesink location=file.ogg videotestsrc ! theoraenc ! mux.

In the next chapter we will show you more examples with Playbin.

3 Playbin

The playbin element was exercised from the command line in section 2.1 and in this section it will be used from Python. It is a high-level, automatic audio and video player. You create a playbin object with:

import gi
gi.require_version('Gst', '1.0')
from gi.repository import Gst
Gst.init(None)
# ...
my_playbin = Gst.ElementFactory.make("playbin", None)
assert my_playbin
print my_playbin
<__main__.GstPlayBin object at 0x7f2604238aa0 (GstPlayBin at 0x17e6480)>

To get information about a playbin run:

gst-inspect-0.10 playbin

This figure shows how playbin is built internally. The "optional stuff" are things that could be platform specific or things that you may set with properties.

playbin-block.svg

The "uri" property should take any possible protocol supported by your GStreamer plugins. One nice feature is that you may switch the sinks out for your own bins as shown below. Playbin always tries to set up the best possible pipeline for your specific environment so if you don't need any special features that are not implemented in playbin, it should in most cases just work "out of the box". Ok, time for a few examples.

3.1 Audio with Playbin

This first example is just a simple audio player, insert a file with absolute path and it'll play. It's code is listed below. You can run it like:

python playbin-example-audio.py

It will open a small window with a text entry. Enter the full path to some audio file and click "Start".

playbin-example-audio.py
#!/usr/bin/env python

import os
import gi
gi.require_version('Gst', '1.0')
from gi.repository import Gst, GObject, Gtk

class GTK_Main(object):

    def __init__(self):
        window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
        window.set_title("Audio-Player")
        window.set_default_size(300, -1)
        window.connect("destroy", Gtk.main_quit, "WM destroy")
        vbox = Gtk.VBox()
        window.add(vbox)
        self.entry = Gtk.Entry()
        vbox.pack_start(self.entry, False, True, 0)
        self.button = Gtk.Button("Start")
        self.button.connect("clicked", self.start_stop)
        vbox.add(self.button)
        window.show_all()

        self.player = Gst.ElementFactory.make("playbin", "player")
        fakesink = Gst.ElementFactory.make("fakesink", "fakesink")
        self.player.set_property("video-sink", fakesink)
        bus = self.player.get_bus()
        bus.add_signal_watch()
        bus.connect("message", self.on_message)

    def start_stop(self, w):
        if self.button.get_label() == "Start":
            filepath = self.entry.get_text().strip()
            if os.path.isfile(filepath):
                filepath = os.path.realpath(filepath)
                self.button.set_label("Stop")
                self.player.set_property("uri", "file://" + filepath)
                self.player.set_state(Gst.State.PLAYING)
            else:
                self.player.set_state(Gst.State.NULL)
                self.button.set_label("Start")

    def on_message(self, bus, message):
        t = message.type
        if t == Gst.MessageType.EOS:
            self.player.set_state(Gst.State.NULL)
            self.button.set_label("Start")
        elif t == Gst.MessageType.ERROR:
            self.player.set_state(Gst.State.NULL)
            err, debug = message.parse_error()
            print "Error: %s" % err, debug
            self.button.set_label("Start")


Gst.init(None)
GTK_Main()
GObject.threads_init()
Gtk.main()

3.2 Adding Video

A playbin plugs both audio and video streams automagically and the videosink has been switched out to a fakesink element which is GStreamer's answer to directing output to /dev/null. If you want to enable video playback just comment out the following lines:

fakesink = Gst.ElementFactory.make("fakesink", "fakesink")
self.player.set_property("video-sink", fakesink)

If you want to show the video output in a specified window you'll have to use the enable_sync_message_emission() method on the bus. Here is an example with the video window embedded in the program.

playbin-example-video.py
#!/usr/bin/env python

import sys, os
import gi
gi.require_version('Gst', '1.0')
from gi.repository import Gst, GObject, Gtk

# Needed for window.get_xid(), xvimagesink.set_window_handle(), respectively:
from gi.repository import GdkX11, GstVideo

class GTK_Main(object):

    def __init__(self):
        window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
        window.set_title("Video-Player")
        window.set_default_size(500, 400)
        window.connect("destroy", Gtk.main_quit, "WM destroy")
        vbox = Gtk.VBox()
        window.add(vbox)
        hbox = Gtk.HBox()
        vbox.pack_start(hbox, False, False, 0)
        self.entry = Gtk.Entry()
        hbox.add(self.entry)
        self.button = Gtk.Button("Start")
        hbox.pack_start(self.button, False, False, 0)
        self.button.connect("clicked", self.start_stop)
        self.movie_window = Gtk.DrawingArea()
        vbox.add(self.movie_window)
        window.show_all()

        self.player = Gst.ElementFactory.make("playbin", "player")
        bus = self.player.get_bus()
        bus.add_signal_watch()
        bus.enable_sync_message_emission()
        bus.connect("message", self.on_message)
        bus.connect("sync-message::element", self.on_sync_message)

    def start_stop(self, w):
        if self.button.get_label() == "Start":
            filepath = self.entry.get_text().strip()
            if os.path.isfile(filepath):
                filepath = os.path.realpath(filepath)
                self.button.set_label("Stop")
                self.player.set_property("uri", "file://" + filepath)
                self.player.set_state(Gst.State.PLAYING)
            else:
                self.player.set_state(Gst.State.NULL)
                self.button.set_label("Start")

    def on_message(self, bus, message):
        t = message.type
        if t == Gst.MessageType.EOS:
            self.player.set_state(Gst.State.NULL)
            self.button.set_label("Start")
        elif t == Gst.MessageType.ERROR:
            self.player.set_state(Gst.State.NULL)
            err, debug = message.parse_error()
            print "Error: %s" % err, debug
            self.button.set_label("Start")

    def on_sync_message(self, bus, message):
        if message.get_structure().get_name() == 'prepare-window-handle':
            imagesink = message.src
            imagesink.set_property("force-aspect-ratio", True)
            imagesink.set_window_handle(self.movie_window.get_property('window').get_xid())


GObject.threads_init()
Gst.init(None)        
GTK_Main()
Gtk.main()

And just to make things a little more complicated you can switch the playbin's video sink to a Gst.Bin with a Gst.GhostPad on it. Here's an example with a timeoverlay.

bin = Gst.Bin.new("my-bin")
timeoverlay = Gst.ElementFactory.make("timeoverlay")
bin.add(timeoverlay)
pad = timeoverlay.get_static_pad("video_sink")
ghostpad = Gst.GhostPad.new("sink", pad)
bin.add_pad(ghostpad)
videosink = Gst.ElementFactory.make("autovideosink")
bin.add(videosink)
timeoverlay.link(videosink)
self.player.set_property("video-sink", bin)

Add that code to the example above and you'll get a timeoverlay too. We'll talk more about "ghost pads" later.

Here now adds a CLI example which plays music. It can be run it with:

python cliplayer.py /path/to/file1.mp3 /path/to/file2.ogg
playbin-example-cliplayer.py
#!/usr/bin/env python

import sys, os, time, thread
import gi
gi.require_version('Gst', '1.0')
from gi.repository import Gst, GLib, GObject

class CLI_Main(object):

    def __init__(self):
        self.player = Gst.ElementFactory.make("playbin", "player")
        fakesink = Gst.ElementFactory.make("fakesink", "fakesink")
        self.player.set_property("video-sink", fakesink)
        bus = self.player.get_bus()
        bus.add_signal_watch()
        bus.connect("message", self.on_message)

    def on_message(self, bus, message):
        t = message.type
        if t == Gst.MessageType.EOS:
            self.player.set_state(Gst.State.NULL)
            self.playmode = False
        elif t == Gst.MessageType.ERROR:
            self.player.set_state(Gst.State.NULL)
            err, debug = message.parse_error()
            print "Error: %s" % err, debug
            self.playmode = False

    def start(self):
        for filepath in sys.argv[1:]:
            if os.path.isfile(filepath):
                filepath = os.path.realpath(filepath)
                self.playmode = True
                self.player.set_property("uri", "file://" + filepath)
                self.player.set_state(Gst.State.PLAYING)
                while self.playmode:
                    time.sleep(1)
        time.sleep(1)
        loop.quit()

GObject.threads_init()
Gst.init(None)        
mainclass = CLI_Main()
thread.start_new_thread(mainclass.start, ())
loop = GLib.MainLoop()
loop.run()

A playbin implements a Gst.Pipeline element and that's what the next chapter is going to tell you more about.

4 Pipeline

A Gst.Pipeline is a top-level bin with its own bus and clock. If your program only contains one bin-like object, this is what you're looking for. You create a pipeline object with:

my_pipeline = Gst.Pipeline.new("my-pipeline")

A pipeline is a "container" where you can put other objects and when everything is in place and the file to play is specified you set the pipeline's state to Gst.State.PLAYING and there should be multimedia coming out of it.

4.1 Pipeline Audio Player

The first example here starts with the audio player from section 3 and switches the playbin for the mad decoding pipeline that is capable of handling MP3 streams. Before coding it in Python it can be tested using gst-launch.

gst-launch-1.0 filesrc location=file.mp3 ! mad ! audioconvert ! alsasink

Conceptually this pipeline looks like:

pipeline-block.svg

Done in Python this would look like pipeline-example.py:

pipeline-example.py
#!/usr/bin/env python

import sys, os
import gi
gi.require_version('Gst', '1.0')
from gi.repository import Gst, GObject, Gtk

class GTK_Main(object):

    def __init__(self):
        window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
        window.set_title("MP3-Player")
        window.set_default_size(400, 200)
        window.connect("destroy", Gtk.main_quit, "WM destroy")
        vbox = Gtk.VBox()
        window.add(vbox)
        self.entry = Gtk.Entry()
        vbox.pack_start(self.entry, False, True, 0)
        self.button = Gtk.Button("Start")
        self.button.connect("clicked", self.start_stop)
        vbox.add(self.button)
        window.show_all()

        self.player = Gst.Pipeline.new("player")
        source = Gst.ElementFactory.make("filesrc", "file-source")
        decoder = Gst.ElementFactory.make("mad", "mp3-decoder")
        conv = Gst.ElementFactory.make("audioconvert", "converter")
        sink = Gst.ElementFactory.make("alsasink", "alsa-output")

        self.player.add(source)
        self.player.add(decoder)
        self.player.add(conv)
        self.player.add(sink)
        source.link(decoder)
        decoder.link(conv)
        conv.link(sink)

        bus = self.player.get_bus()
        bus.add_signal_watch()
        bus.connect("message", self.on_message)

    def start_stop(self, w):
        if self.button.get_label() == "Start":
            filepath = self.entry.get_text().strip()
            if os.path.isfile(filepath):
                filepath = os.path.realpath(filepath)
                self.button.set_label("Stop")
                self.player.get_by_name("file-source").set_property("location", filepath)
                self.player.set_state(Gst.State.PLAYING)
            else:
                self.player.set_state(Gst.State.NULL)
                self.button.set_label("Start")

    def on_message(self, bus, message):
        t = message.type
        if t == Gst.MessageType.EOS:
            self.player.set_state(Gst.State.NULL)
            self.button.set_label("Start")
        elif t == Gst.MessageType.ERROR:
            self.player.set_state(Gst.State.NULL)
            self.button.set_label("Start")
            err, debug = message.parse_error()
            print "Error: %s" % err, debug

Gst.init(None)
GTK_Main()
GObject.threads_init()
Gtk.main()

4.2 Adding Video to the Pipeline

The next example is playing MPEG2 videos. Some demuxers, such as mpegdemux, uses dynamic pads which are created at runtime and therefor you can't link between the demuxer and the next element in the pipeline before the pad has been created at runtime. Watch out for the demuxer_callback() method below.

pipeline-branch-block.svg

pipeline-branch-example.py
#!/usr/bin/env python

import os
import gi
gi.require_version('Gst', '1.0')
from gi.repository import Gst, GObject, Gtk

class GTK_Main(object):

    def __init__(self):
        window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
        window.set_title("Mpeg2-Player")
        window.set_default_size(500, 400)
        window.connect("destroy", Gtk.main_quit, "WM destroy")
        vbox = Gtk.VBox()
        window.add(vbox)
        hbox = Gtk.HBox()
        vbox.pack_start(hbox, False, False, 0)
        self.entry = Gtk.Entry()
        hbox.add(self.entry)
        self.button = Gtk.Button("Start")
        hbox.pack_start(self.button, False, False, 0)
        self.button.connect("clicked", self.start_stop)
        self.movie_window = Gtk.DrawingArea()
        vbox.add(self.movie_window)
        window.show_all()

        self.player = Gst.Pipeline.new("player")
        source = Gst.ElementFactory.make("filesrc", "file-source")
        demuxer = Gst.ElementFactory.make("mpegpsdemux", "demuxer")
        demuxer.connect("pad-added", self.demuxer_callback)
        self.video_decoder = Gst.ElementFactory.make("mpeg2dec", "video-decoder")
        self.audio_decoder = Gst.ElementFactory.make("mad", "audio-decoder")
        audioconv = Gst.ElementFactory.make("audioconvert", "converter")
        audiosink = Gst.ElementFactory.make("autoaudiosink", "audio-output")
        videosink = Gst.ElementFactory.make("autovideosink", "video-output")
        self.queuea = Gst.ElementFactory.make("queue", "queuea")
        self.queuev = Gst.ElementFactory.make("queue", "queuev")
        colorspace = Gst.ElementFactory.make("videoconvert", "colorspace")

        self.player.add(source) 
        self.player.add(demuxer) 
        self.player.add(self.video_decoder) 
        self.player.add(self.audio_decoder) 
        self.player.add(audioconv) 
        self.player.add(audiosink) 
        self.player.add(videosink) 
        self.player.add(self.queuea) 
        self.player.add(self.queuev) 
        self.player.add(colorspace)

        source.link(demuxer)

        self.queuev.link(self.video_decoder)
        self.video_decoder.link(colorspace)
        colorspace.link(videosink)

        self.queuea.link(self.audio_decoder)
        self.audio_decoder.link(audioconv)
        audioconv.link(audiosink)

        bus = self.player.get_bus()
        bus.add_signal_watch()
        bus.enable_sync_message_emission()
        bus.connect("message", self.on_message)
        bus.connect("sync-message::element", self.on_sync_message)

    def start_stop(self, w):
        if self.button.get_label() == "Start":
            filepath = self.entry.get_text().strip()
            if os.path.isfile(filepath):
                filepath = os.path.realpath(filepath)
                self.button.set_label("Stop")
                self.player.get_by_name("file-source").set_property("location", filepath)
                self.player.set_state(Gst.State.PLAYING)
            else:
                self.player.set_state(Gst.State.NULL)
                self.button.set_label("Start")

    def on_message(self, bus, message):
        t = message.type
        if t == Gst.MessageType.EOS:
            self.player.set_state(Gst.State.NULL)
            self.button.set_label("Start")
        elif t == Gst.MessageType.ERROR:
            err, debug = message.parse_error()
            print "Error: %s" % err, debug
            self.player.set_state(Gst.State.NULL)
            self.button.set_label("Start")

    def on_sync_message(self, bus, message):
        if message.get_structure().get_name() == 'prepare-window-handle':
            imagesink = message.src
            imagesink.set_property("force-aspect-ratio", True)
            xid = self.movie_window.get_property('window').get_xid()
            imagesink.set_window_handle(xid)

    def demuxer_callback(self, demuxer, pad):
        if pad.get_property("template").name_template == "video_%02d":
            qv_pad = self.queuev.get_pad("sink")
            pad.link(qv_pad)
        elif pad.get_property("template").name_template == "audio_%02d":
            qa_pad = self.queuea.get_pad("sink")
            pad.link(qa_pad)


Gst.init(None)
GTK_Main()
GObject.threads_init()
Gtk.main()

The elements in a pipeline connects to each other with pads and that's what the next chapter will tell you more about.

5 Src, sink, pad … oh my!

As their names imply, a src is an object that is "sending" data and a sink is an object that is "receiving" data. These objects connect to each other with pads. Pads could be either src or sink. Most elements have both src and sink pads. For example, the mad MP3 decoder element looks something like the figure below:

sink-src-block.svg

And as always if you want to know more about highlevel elements gst-inspect is your friend:

gst-inspect-1.0 mad

In particular, the inheritance diagram shows that mad is an element:

GObject
 +----GInitiallyUnowned
       +----GstObject
             +----GstElement
                   +----GstAudioDecoder
                         +----GstMad

There are many different ways to link elements together. In pipeline-example.py we used the Gst.Pipeline.add() and the .link() method of the produced elements. You can also make a completely ready-to-go pipeline with the parse_launch() function. The many .add() calls in that example can be rewritten as:

mp3_pipeline = Gst.parse_launch("filesrc name=source ! mad name=decoder ! " \
                                "audioconvert name=conv ! alsasink name=sink")

The micro-language used in this function call is that of the gst-launch command line program.

When you do manually link pads with the .link() method make sure that you link a src-pad to a sink-pad. No rule though without exceptions. A Gst.GhostPad should be linked to a pad of the same kind as itself. We have already showed how a ghost pad works in the addition to example 2.2. A Gst.Bin can't link to other objects if you don't link a Gst.GhostPad to an element inside the bin. The playbin-example-video.py example in section 3 should look something like this:

playbin-pads-block.svg

And the ghostpad above should be created as type "sink"!!!

Some pads are not always available and are only created when they are in use. Such pads are called "dynamical pads". The next example will show how to use dynamically created pads with an oggdemux. The link between the demuxer and the decoder is created with the demuxer_callback() method, which is called whenever a pad is created in the demuxer using the "pad-added" signal.

dynamic-ghostpad-example.py
#!/usr/bin/env python

import sys, os
import gi
gi.require_version('Gst', '1.0')
from gi.repository import Gst, GObject, Gtk

class GTK_Main(object):

    def __init__(self):
        window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
        window.set_title("Vorbis-Player")
        window.set_default_size(500, 200)
        window.connect("destroy", Gtk.main_quit, "WM destroy")
        vbox = Gtk.VBox()
        window.add(vbox)
        self.entry = Gtk.Entry()
        vbox.pack_start(self.entry, False, False, 0)
        self.button = Gtk.Button("Start")
        vbox.add(self.button)
        self.button.connect("clicked", self.start_stop)
        window.show_all()

        self.player = Gst.Pipeline.new("player")
        source = Gst.ElementFactory.make("filesrc", "file-source")
        demuxer = Gst.ElementFactory.make("oggdemux", "demuxer")
        demuxer.connect("pad-added", self.demuxer_callback)
        self.audio_decoder = Gst.ElementFactory.make("vorbisdec", "vorbis-decoder")
        audioconv = Gst.ElementFactory.make("audioconvert", "converter")
        audiosink = Gst.ElementFactory.make("autoaudiosink", "audio-output")

        self.player.add(source)
        self.player.add(demuxer)
        self.player.add(self.audio_decoder)
        self.player.add(audioconv)
        self.player.add(audiosink)

        source.link(demuxer)
        self.audio_decoder.link(audioconv)
        audioconv.link(audiosink)

        bus = self.player.get_bus()
        bus.add_signal_watch()
        bus.connect("message", self.on_message)

    def start_stop(self, w):
        if self.button.get_label() == "Start":
            filepath = self.entry.get_text().strip()
            if os.path.isfile(filepath):
                filepath = os.path.realpath(filepath)
                self.button.set_label("Stop")
                self.player.get_by_name("file-source").set_property("location", filepath)
                self.player.set_state(Gst.State.PLAYING)
            else:
                self.player.set_state(Gst.State.NULL)
                self.button.set_label("Start")

    def on_message(self, bus, message):
        t = message.type
        if t == Gst.MessageType.EOS:
            self.player.set_state(Gst.State.NULL)
            self.button.set_label("Start")
        elif t == Gst.MessageType.ERROR:
            err, debug = message.parse_error()
            print "Error: %s" % err, debug
            self.player.set_state(Gst.State.NULL)
            self.button.set_label("Start")

    def demuxer_callback(self, demuxer, pad):
        adec_pad = self.audio_decoder.get_static_pad("sink")
        pad.link(adec_pad)


GObject.threads_init()
Gst.init(None)        
GTK_Main()
Gtk.main()

Now after reading through these four chapters you could need a break. Happy hacking and stay tuned for more interesting chapters to come.

6 Seeking

Seeking in GStreamer is done with the seek() and seek_simple() methods of Gst.Element. To be able to seek you will also need to tell GStreamer what kind of seek it should do. In the following example we will use a TIME value (of Gst.Format enum) format constant which will, as you may guess, request a time seek. We will also use the query_duration() and query_position() methods to get the file length and how long the file has currently played. GStreamer uses nanoseconds by default so you have to adjust to that.

In this next example we take the Vorbis-Player from example 4.1 and update it with some more stuff so it's able to seek and show duration and position.

seeking-example.py
#!/usr/bin/env python

import os, thread, time
import gi
gi.require_version("Gst", "1.0")
from gi.repository import Gst, GObject, Gtk, Gdk

class GTK_Main:

    def __init__(self):
        window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
        window.set_title("Vorbis-Player")
        window.set_default_size(500, -1)
        window.connect("destroy", Gtk.main_quit, "WM destroy")
        vbox = Gtk.VBox()
        window.add(vbox)
        self.entry = Gtk.Entry()
        vbox.pack_start(self.entry, False, False, 0)
        hbox = Gtk.HBox()
        vbox.add(hbox)
        buttonbox = Gtk.HButtonBox()
        hbox.pack_start(buttonbox, False, False, 0)
        rewind_button = Gtk.Button("Rewind")
        rewind_button.connect("clicked", self.rewind_callback)
        buttonbox.add(rewind_button)
        self.button = Gtk.Button("Start")
        self.button.connect("clicked", self.start_stop)
        buttonbox.add(self.button)
        forward_button = Gtk.Button("Forward")
        forward_button.connect("clicked", self.forward_callback)
        buttonbox.add(forward_button)
        self.time_label = Gtk.Label()
        self.time_label.set_text("00:00 / 00:00")
        hbox.add(self.time_label)
        window.show_all()

        self.player = Gst.Pipeline.new("player")
        source = Gst.ElementFactory.make("filesrc", "file-source")
        demuxer = Gst.ElementFactory.make("oggdemux", "demuxer")
        demuxer.connect("pad-added", self.demuxer_callback)
        self.audio_decoder = Gst.ElementFactory.make("vorbisdec", "vorbis-decoder")
        audioconv = Gst.ElementFactory.make("audioconvert", "converter")
        audiosink = Gst.ElementFactory.make("autoaudiosink", "audio-output")

        for ele in [source, demuxer, self.audio_decoder, audioconv, audiosink]:
            self.player.add(ele)
        source.link(demuxer)
        self.audio_decoder.link(audioconv)
        audioconv.link(audiosink)

        bus = self.player.get_bus()
        bus.add_signal_watch()
        bus.connect("message", self.on_message)

    def start_stop(self, w):
        if self.button.get_label() == "Start":
            filepath = self.entry.get_text().strip()
            if os.path.isfile(filepath):
                filepath = os.path.realpath(filepath)
                self.button.set_label("Stop")
                self.player.get_by_name("file-source").set_property("location", filepath)
                self.player.set_state(Gst.State.PLAYING)
                self.play_thread_id = thread.start_new_thread(self.play_thread, ())
            else:
                self.play_thread_id = None
                self.player.set_state(Gst.State.NULL)
                self.button.set_label("Start")
                self.time_label.set_text("00:00 / 00:00")

    def play_thread(self):
        play_thread_id = self.play_thread_id
        Gdk.threads_enter()
        self.time_label.set_text("00:00 / 00:00")
        Gdk.threads_leave()

        while play_thread_id == self.play_thread_id:
            try:
                time.sleep(0.2)
                dur_int = self.player.query_duration(Gst.Format.TIME, None)[0]
                if dur_int == -1:
                    continue
                dur_str = self.convert_ns(dur_int)
                Gdk.threads_enter()
                self.time_label.set_text("00:00 / " + dur_str)
                Gdk.threads_leave()
                break
            except:
                pass

        time.sleep(0.2)
        while play_thread_id == self.play_thread_id:
            pos_int = self.player.query_position(Gst.Format.TIME, None)[0]
            pos_str = self.convert_ns(pos_int)
            if play_thread_id == self.play_thread_id:
                Gdk.threads_enter()
                self.time_label.set_text(pos_str + " / " + dur_str)
                Gdk.threads_leave()
            time.sleep(1)

    def on_message(self, bus, message):
        t = message.type
        if t == Gst.MessageType.EOS:
            self.play_thread_id = None
            self.player.set_state(Gst.State.NULL)
            self.button.set_label("Start")
            self.time_label.set_text("00:00 / 00:00")
        elif t == Gst.MessageType.ERROR:
            err, debug = message.parse_error()
            print "Error: %s" % err, debug
            self.play_thread_id = None
            self.player.set_state(Gst.State.NULL)
            self.button.set_label("Start")
            self.time_label.set_text("00:00 / 00:00")

    def demuxer_callback(self, demuxer, pad):
        adec_pad = self.audio_decoder.get_static_pad("sink")
        pad.link(adec_pad)

    def rewind_callback(self, w):
        rc, pos_int = self.player.query_position(Gst.Format.TIME)
        seek_ns = pos_int - 10 * 1000000000
        if seek_ns < 0:
            seek_ns = 0
        print 'Backward: %d ns -> %d ns' % (pos_int, seek_ns)
        self.player.seek_simple(Gst.Format.TIME, Gst.SeekFlags.FLUSH, seek_ns)

    def forward_callback(self, w):
        rc, pos_int = self.player.query_position(Gst.Format.TIME)
        seek_ns = pos_int + 10 * 1000000000
        print 'Forward: %d ns -> %d ns' % (pos_int, seek_ns)
        self.player.seek_simple(Gst.Format.TIME, Gst.SeekFlags.FLUSH, seek_ns)

    def convert_ns(self, t):
        # This method was submitted by Sam Mason.
        # It's much shorter than the original one.
        s,ns = divmod(t, 1000000000)
        m,s = divmod(s, 60)

        if m < 60:
            return "%02i:%02i" %(m,s)
        else:
            h,m = divmod(m, 60)
            return "%i:%02i:%02i" %(h,m,s)

GObject.threads_init()
Gst.init(None)        
GTK_Main()
Gtk.main()

7 Capabilities

Capabilities, Gst.Caps, is a container where you may store information that you may pass on to a Gst.PadTemplate. When you set the pipeline state to either playing or paused the elements pads negotiates what caps to use for the stream. Now the following pipeline works perfectly:

gst-launch-1.0  videotestsrc ! video/x-raw, width=320, height=240 ! \
  xvimagesink

But if you try to switch out the xvimagesink for an ximagesink you will notice that it wouldn't work. That's because ximagesink can't handle video/x-raw so you must put in an element BEFORE in the pipeline that does.

gst-launch-1.0  videotestsrc ! video/x-raw, width=320, height=240 ! \
  videoconvert ! ximagesink

And as ximagesink does not support hardware scaling you have to throw in a videoscale element too if you want software scaling.

gst-launch-1.0  videotestsrc ! video/x-raw, width=320, height=240 ! \
  videoscale ! videoconvert ! ximagesink

To put the above examples in code you have to put the caps in a capsfilter element.

capabilities-example.py
#!/usr/bin/env python

import gi
gi.require_version("Gst", "1.0")
from gi.repository import Gst, GObject, Gtk

class GTK_Main:

    def __init__(self):
        window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
        window.set_title("Videotestsrc-Player")
        window.set_default_size(300, -1)
        window.connect("destroy", Gtk.main_quit, "WM destroy")
        vbox = Gtk.VBox()
        window.add(vbox)
        self.button = Gtk.Button("Start")
        self.button.connect("clicked", self.start_stop)
        vbox.add(self.button)
        window.show_all()
        self.player = Gst.Pipeline.new("player")
        source = Gst.ElementFactory.make("videotestsrc", "video-source")
        sink = Gst.ElementFactory.make("xvimagesink", "video-output")
        caps = Gst.Caps.from_string("video/x-raw, width=320, height=230")
        filter = Gst.ElementFactory.make("capsfilter", "filter")
        filter.set_property("caps", caps)
        self.player.add(source)
        self.player.add(filter)
        self.player.add(sink)
        source.link(filter)
        filter.link(sink)

    def start_stop(self, w):
        if self.button.get_label() == "Start":
            self.button.set_label("Stop")
            self.player.set_state(Gst.State.PLAYING)
        else:
            self.player.set_state(Gst.State.NULL)
            self.button.set_label("Start")

GObject.threads_init()
Gst.init(None)        
GTK_Main()
Gtk.main()

A frequently asked question is how to find out what resolution a file has and one way to do it is to check the caps on a decodebin element in paused state.

capabilities-resolution-example.py
#!/usr/bin/env python

import os
import gi
gi.require_version("Gst", "1.0")
from gi.repository import Gst, GObject, Gtk

class GTK_Main:
    def __init__(self):
        window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
        window.set_title("Resolutionchecker")
        window.set_default_size(300, -1)
        window.connect("destroy", Gtk.main_quit, "WM destroy")
        vbox = Gtk.VBox()
        window.add(vbox)
        self.entry = Gtk.Entry()
        vbox.pack_start(self.entry, False, True, 0)
        self.button = Gtk.Button("Check")
        self.button.connect("clicked", self.start_stop)
        vbox.add(self.button)
        window.show_all()
        self.player = Gst.Pipeline.new("player")
        source = Gst.ElementFactory.make("filesrc", "file-source")
        decoder = Gst.ElementFactory.make("decodebin", "decoder")
        decoder.connect("pad-added", self.decoder_callback)
        self.fakea = Gst.ElementFactory.make("fakesink", "fakea")
        self.fakev = Gst.ElementFactory.make("fakesink", "fakev")
        self.player.add(source)
        self.player.add(decoder)
        self.player.add(self.fakea)
        self.player.add(self.fakev)
        source.link(decoder)
        bus = self.player.get_bus()
        bus.add_signal_watch()
        bus.connect("message", self.on_message)

    def start_stop(self, w):
        filepath = self.entry.get_text().strip()
        if os.path.isfile(filepath):
            filepath = os.path.realpath(filepath)
            self.player.set_state(Gst.State.NULL)
            self.player.get_by_name("file-source").set_property("location", filepath)
            self.player.set_state(Gst.State.PAUSED)
    def on_message(self, bus, message):
        typ = message.type
        if typ == Gst.MessageType.STATE_CHANGED:
            if message.parse_state_changed()[1] == Gst.State.PAUSED:
                decoder = self.player.get_by_name("decoder")
                for pad in decoder.srcpads:
                    caps = pad.query_caps(None)
                    structure_name = caps.to_string()
                    width = caps.get_structure(0).get_int('width')
                    height = caps.get_structure(0).get_int('height')
                    if structure_name.startswith("video") and len(str(width)) < 6:
                        print "Width:%d, Height:%d" %(width, height)
                        self.player.set_state(Gst.State.NULL)
                        break
        elif typ == Gst.MessageType.ERROR:
            err, debug = message.parse_error()
            print "Error: %s" % err, debug
            self.player.set_state(Gst.State.NULL)

    def decoder_callback(self, decoder, pad):
        caps = pad.query_caps(None)
        structure_name = caps.to_string()
        if structure_name.startswith("video"):
            fv_pad = self.fakev.get_static_pad("sink")
            pad.link(fv_pad)
        elif structure_name.startswith("audio"):
            fa_pad = self.fakea.get_static_pad("sink")
            pad.link(fa_pad)

GObject.threads_init()
Gst.init(None)        
GTK_Main()
Gtk.main()

In the next example we will use the playbin from section 3.2 and switch its video-sink out for our own homemade bin filled with some elements. Now, let's say that you run a tv-station and you want to have your logo in the top right corner of the screen. For that you can use a textoverlay but for the fonts to be the exact same size on the screen no matter what kind of resolution the source has you have to specify a width so everything is scaled according to that.

capabilities-playbin-example.py
#!/usr/bin/env python

import os
import gi
gi.require_version("Gst", "1.0")
from gi.repository import Gst, GObject, Gtk

class GTK_Main:
    def __init__(self):
        window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
        window.set_title("Video-Player")
        window.set_default_size(500, 400)
        window.connect("destroy", Gtk.main_quit, "WM destroy")
        vbox = Gtk.VBox()
        window.add(vbox)
        hbox = Gtk.HBox()
        vbox.pack_start(hbox, False, False, 0)
        self.entry = Gtk.Entry()
        hbox.add(self.entry)
        self.button = Gtk.Button("Start")
        hbox.pack_start(self.button, False, False, 0)
        self.button.connect("clicked", self.start_stop)
        self.movie_window = Gtk.DrawingArea()
        vbox.add(self.movie_window)
        window.show_all()
        self.player = Gst.ElementFactory.make("playbin", "player")
        self.bin = Gst.Bin.new("my-bin")
        videoscale = Gst.ElementFactory.make("videoscale")
        videoscale.set_property("method", 1)
        pad = videoscale.get_static_pad("sink")
        ghostpad = Gst.GhostPad.new("sink", pad)
        self.bin.add_pad(ghostpad)
        caps = Gst.Caps.from_string("video/x-raw, width=720")
        filter = Gst.ElementFactory.make("capsfilter", "filter")
        filter.set_property("caps", caps)
        textoverlay = Gst.ElementFactory.make('textoverlay')
        textoverlay.set_property("text", "GNUTV")
        textoverlay.set_property("font-desc", "normal 14")
        # TypeError: object of type `GstTextOverlay' does not have property `halign'
        #textoverlay.set_property("halign", "right")
        # TypeError: object of type `GstTextOverlay' does not have property `valign'
        #textoverlay.set_property("valign", "top")
        conv = Gst.ElementFactory.make ("videoconvert", "conv")
        videosink = Gst.ElementFactory.make("autovideosink")

        self.bin.add(videoscale) 
        self.bin.add(filter) 
        self.bin.add(textoverlay)
        self.bin.add(conv)
        self.bin.add(videosink)

        videoscale.link(filter)
        filter.link(textoverlay)
        textoverlay.link(conv)
        conv.link(videosink)

        self.player.set_property("video-sink", self.bin)
        bus = self.player.get_bus()
        bus.add_signal_watch()
        bus.enable_sync_message_emission()
        bus.connect("message", self.on_message)
        bus.connect("sync-message::element", self.on_sync_message)

    def start_stop(self, w):
        if self.button.get_label() == "Start":
            filepath = self.entry.get_text().strip()
            if os.path.exists(filepath):
                filepath = os.path.realpath(filepath)
                self.button.set_label("Stop")
                self.player.set_property("uri", "file://" + filepath)
                self.player.set_state(Gst.State.PLAYING)
        else:
            self.player.set_state(Gst.State.NULL)
            self.button.set_label("Start")

    def on_message(self, bus, message):
        typ = message.type
        if typ == Gst.MessageType.EOS:
            self.player.set_state(Gst.State.NULL)
            self.button.set_label("Start")
        elif typ == Gst.MessageType.ERROR:
            self.player.set_state(Gst.State.NULL)
            self.button.set_label("Start")
            err, debug = message.parse_error()
            print "Error: %s" % err, debug

    def on_sync_message(self, bus, message):
        if message.structure is None:
            return
        message_name = message.structure.get_name()
        if message_name == "prepare-xwindow-id":
            imagesink = message.src
            imagesink.set_property("force-aspect-ratio", True)
            imagesink.set_xwindow_id(self.movie_window.window.xid)

GObject.threads_init()
Gst.init(None)        
GTK_Main()
Gtk.main()

8 Videomixer

The videomixer element makes it possible to mix different video streams together. Here is a CLI example:

gst-launch-1.0 filesrc location=tvlogo.png ! pngdec ! alphacolor ! \
  videoconvert ! videobox border-alpha=0 alpha=0.5 top=-20 left=-200 ! \
  videomixer name=mix ! videoconvert ! autovideosink videotestsrc ! \
  video/x-raw, width=320, height=240 ! mix.

Fixme: this fails with:

Setting pipeline to PAUSED ...
Pipeline is PREROLLING ...
ERROR: from element /GstPipeline:pipeline0/GstFileSrc:filesrc0: Internal data flow error.
Additional debug info:
gstbasesrc.c(2865): gst_base_src_loop (): /GstPipeline:pipeline0/GstFileSrc:filesrc0:
streaming task paused, reason not-negotiated (-4)
ERROR: pipeline doesn't want to preroll.
Setting pipeline to NULL ...
Freeing pipeline ...

You have to make a tvlogo.png image (100x100 px) to be able to run it. With the videobox element you can move the image around and add more alpha channels.

In the next example we take the now working Mpeg2-Player from section 4 and add the elements shown above.

videomixer-example.py
#!/usr/bin/env python

import os
import gi
gi.require_version("Gst", "1.0")
from gi.repository import Gst, GObject, Gtk

class GTK_Main:
    def __init__(self):
        window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
        window.set_title("Mpeg2-Player")
        window.set_default_size(500, 400)
        window.connect("destroy", Gtk.main_quit, "WM destroy")
        vbox = Gtk.VBox()
        window.add(vbox)
        hbox = Gtk.HBox()
        vbox.pack_start(hbox, False, False, 0)
        self.entry = Gtk.Entry()
        hbox.add(self.entry)
        self.button = Gtk.Button("Start")
        hbox.pack_start(self.button, False, False, 0)
        self.button.connect("clicked", self.start_stop)
        self.movie_window = Gtk.DrawingArea()
        vbox.add(self.movie_window)
        window.show_all()
        self.player = Gst.Pipeline.new("player")
        source = Gst.ElementFactory.make("filesrc", "file-source")
        demuxer = Gst.ElementFactory.make("mpegpsdemux", "demuxer")
        demuxer.connect("pad-added", self.demuxer_callback)
        self.video_decoder = Gst.ElementFactory.make("mpeg2dec", "video-decoder")
        png_decoder = Gst.ElementFactory.make("pngdec", "png-decoder")
        png_source = Gst.ElementFactory.make("filesrc", "png-source")
        png_source.set_property("location", os.path.realpath("tvlogo.png"))
        mixer = Gst.ElementFactory.make("videomixer", "mixer")
        self.audio_decoder = Gst.ElementFactory.make("mad", "audio-decoder")
        audioconv = Gst.ElementFactory.make("audioconvert", "converter")
        audiosink = Gst.ElementFactory.make("autoaudiosink", "audio-output")
        videosink = Gst.ElementFactory.make("autovideosink", "video-output")
        self.queuea = Gst.ElementFactory.make("queue", "queuea")
        self.queuev = Gst.ElementFactory.make("queue", "queuev")
        ffmpeg1 = Gst.ElementFactory.make("videoconvert", "ffmpeg1")
        ffmpeg2 = Gst.ElementFactory.make("videoconvert", "ffmpeg2")
        ffmpeg3 = Gst.ElementFactory.make("videoconvert", "ffmpeg3")
        videobox = Gst.ElementFactory.make("videobox", "videobox")
        alphacolor = Gst.ElementFactory.make("alphacolor", "alphacolor")
        for ele in (source, demuxer, self.video_decoder, png_decoder, png_source, mixer,
                    self.audio_decoder, audioconv, audiosink, videosink, self.queuea, 
                    self.queuev, ffmpeg1, ffmpeg2, ffmpeg3, videobox, alphacolor):
            self.player.add(ele)

        source.link(demuxer)

        self.queuev.link(self.video_decoder)
        self.video_decoder.link(ffmpeg1)
        ffmpeg1.link(mixer)
        mixer.link(ffmpeg2)
        ffmpeg2.link(videosink)

        png_source.link(png_decoder)
        png_decoder.link(alphacolor)
        alphacolor.link(ffmpeg3)
        ffmpeg3.link(videobox)
        videobox.link(mixer)

        self.queuea.link(self.audio_decoder)
        self.audio_decoder.link(audioconv)
        audioconv.link(audiosink)

        bus = self.player.get_bus()
        bus.add_signal_watch()
        bus.enable_sync_message_emission()
        bus.connect("message", self.on_message)
        bus.connect("sync-message::element", self.on_sync_message)
        videobox.set_property("border-alpha", 0)
        videobox.set_property("alpha", 0.5)
        videobox.set_property("left", -10)
        videobox.set_property("top", -10)

    def start_stop(self, w):
        if self.button.get_label() == "Start":
            filepath = self.entry.get_text().strip()
            if os.path.isfile(filepath):
                filepath = os.path.realpath(filepath)
                self.button.set_label("Stop")
                self.player.get_by_name("file-source").set_property("location", filepath)
                self.player.set_state(Gst.State.PLAYING)
            else:
                self.player.set_state(Gst.State.NULL)
                self.button.set_label("Start")

    def on_message(self, bus, message):
        t = message.type
        if t == Gst.MessageType.EOS:
            self.player.set_state(Gst.State.NULL)
            self.button.set_label("Start")
        elif t == Gst.MessageType.ERROR:
            err, debug = message.parse_error()
            print "Error: %s" % err, debug
            self.player.set_state(Gst.State.NULL)
            self.button.set_label("Start")

    def on_sync_message(self, bus, message):
        if message.structure is None:
            return
        message_name = message.structure.get_name()
        if message_name == "prepare-xwindow-id":
            imagesink = message.src
            imagesink.set_property("force-aspect-ratio", True)
            imagesink.set_xwindow_id(self.movie_window.window.xid)

    def demuxer_callback(self, demuxer, pad):
        tpl_property = pad.get_property("template")
        tpl_name = tpl_property.name_template
        print 'demuxer_callback: template name template: "%s"' % tpl_name
        if tpl_name == "video_%02d":
            queuev_pad = self.queuev.get_pad("sink")
            pad.link(queuev_pad)
        elif tpl_name == "audio_%02d":
            queuea_pad = self.queuea.get_pad("sink")
            pad.link(queuea_pad)

GObject.threads_init()
Gst.init(None)        
GTK_Main()
Gtk.main()

Fixme: This fails with:

Error: Internal data flow error. gstbasesrc.c(2865): gst_base_src_loop (): /GstPipeline:player/GstFileSrc:png-source:
streaming task paused, reason not-negotiated (-4)

9 Webcam Viewer

Remember to peal the tape off your web cam lens before testing this.

webcam-example.py
#!/usr/bin/env python

import sys, os
import gi
gi.require_version('Gst', '1.0')
from gi.repository import Gst, GObject, Gtk

class GTK_Main:
    def __init__(self):
        window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
        window.set_title("Webcam-Viewer")
        window.set_default_size(500, 400)
        window.connect("destroy", Gtk.main_quit, "WM destroy")
        vbox = Gtk.VBox()
        window.add(vbox)
        self.movie_window = Gtk.DrawingArea()
        vbox.add(self.movie_window)
        hbox = Gtk.HBox()
        vbox.pack_start(hbox, False, False, 0)
        hbox.set_border_width(10)
        hbox.pack_start(Gtk.Label(), False, False, 0)
        self.button = Gtk.Button("Start")
        self.button.connect("clicked", self.start_stop)
        hbox.pack_start(self.button, False, False, 0)
        self.button2 = Gtk.Button("Quit")
        self.button2.connect("clicked", self.exit)
        hbox.pack_start(self.button2, False, False, 0)
        hbox.add(Gtk.Label())
        window.show_all()

        # Set up the gstreamer pipeline
        self.player = Gst.parse_launch ("v4l2src ! autovideosink")
        bus = self.player.get_bus()
        bus.add_signal_watch()
        bus.enable_sync_message_emission()
        bus.connect("message", self.on_message)
        bus.connect("sync-message::element", self.on_sync_message)

    def start_stop(self, w):
        if self.button.get_label() == "Start":
            self.button.set_label("Stop")
            self.player.set_state(Gst.State.PLAYING)
        else:
            self.player.set_state(Gst.State.NULL)
            self.button.set_label("Start")

    def exit(self, widget, data=None):
        Gtk.main_quit()

    def on_message(self, bus, message):
        t = message.type
        if t == Gst.MessageType.EOS:
            self.player.set_state(Gst.State.NULL)
            self.button.set_label("Start")
        elif t == Gst.MessageType.ERROR:
            err, debug = message.parse_error()
            print "Error: %s" % err, debug
            self.player.set_state(Gst.State.NULL)
            self.button.set_label("Start")

    def on_sync_message(self, bus, message):
        struct = message.get_structure()
        if not struct:
            return
        message_name = struct.get_name()
        if message_name == "prepare-xwindow-id":
            # Assign the viewport
            imagesink = message.src
            imagesink.set_property("force-aspect-ratio", True)
            imagesink.set_xwindow_id(self.movie_window.window.xid)

Gst.init(None)
GTK_Main()
GObject.threads_init()
Gtk.main()

Author: Jens Persson and Ruben Gonzalez and Brett Viren

Created: 2015-01-04 Sun 15:43

Emacs 24.3.1 (Org mode 8.2.5a)

Validate