├── .gitignore ├── README.html ├── README.org ├── capabilities-example.py ├── capabilities-playbin-example.py ├── capabilities-resolution-example.py ├── dynamic-ghostpad-example.py ├── functions.js ├── gst-inspect-playbin.txt ├── index.html ├── index.org ├── pipeline-block.svg ├── pipeline-branch-block.svg ├── pipeline-branch-example.py ├── pipeline-example.py ├── playbin-block.svg ├── playbin-example-audio.py ├── playbin-example-cliplayer.py ├── playbin-example-video.py ├── playbin-pads-block.svg ├── pygst-tutorial.html ├── pygst-tutorial.org ├── pygst-tutorial.pdf ├── seeking-example.py ├── setup.org ├── sink-src-block.svg ├── style.css ├── tvlogo.png ├── videomixer-example.py ├── videotestsrc-frame.jpg └── webcam-example.py /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.pyc 3 | pygst-tutorial.tex 4 | pipeline-block.pdf 5 | pipeline-branch-block.pdf 6 | playbin-block.pdf 7 | playbin-pads-block.pdf 8 | sink-src-block.pdf 9 | *.ogg 10 | *.mp3 11 | *.mp4 12 | test.* 13 | -------------------------------------------------------------------------------- /README.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | Tutorial on using GStreamer Python Bindings (org-mode version) 7 | 8 | 9 | 10 | 11 | 91 | 92 | 93 | 94 | 95 | 125 | 126 | 166 | 212 | 213 | 214 |
215 | UP 216 | | 217 | HOME 218 |
219 |

Tutorial on using GStreamer Python Bindings (org-mode version)

220 |

221 | This repository holds a tutorial on using the Python bindings for GStreamer 1.0 (re)written in Emacs org-mode. 222 |

223 | 224 |

225 | Note: this was mostly an exercise for me to learn the topic. There is hardly any novel work here. 226 |

227 | 228 |

229 | The main document is ./pygst-tutorial.html. An HTML export of this document is also committed but may not always be up to date. You can read it online at: 230 |

231 | 232 |
233 |

234 | http://brettviren.github.io/pygst-tutorial-org/pygst-tutorial.html 235 |

236 |
237 | 238 |

239 | Or you can download the document as a PDF. 240 |

241 | 242 |

243 | The starting point for this document was Ruben Gonzalez's rescue from Google's cache of the original by Jens Persson. This document differs in: 244 |

245 | 246 | 256 | 257 |

258 | Other useful sources of info on using GStreamer 1.0 Python bindings: 259 |

260 | 261 | 269 |
270 |
271 |

Author: Brett Viren

272 |

Created: 2015-01-04 Sun 15:53

273 |

Emacs 24.3.1 (Org mode 8.2.5a)

274 |

Validate

275 |
276 | 277 | 278 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | #+TITLE: Tutorial on using GStreamer Python Bindings (org-mode version) 2 | #+SETUPFILE: setup.org 3 | 4 | This repository holds a tutorial on using the Python bindings for GStreamer 1.0 (re)written in Emacs org-mode. 5 | 6 | Note: this was mostly an exercise for me to learn the topic. There is hardly any novel work here. 7 | 8 | The main document is [[./pygst-tutorial.org]]. An HTML export of this document is also committed but may not always be up to date. You can read it online at: 9 | 10 | #+BEGIN_QUOTE 11 | http://brettviren.github.io/pygst-tutorial-org/pygst-tutorial.html 12 | #+END_QUOTE 13 | 14 | Or you can download the document as a [[http://brettviren.github.io/pygst-tutorial-org/pygst-tutorial.pdf][PDF]]. 15 | 16 | The starting point for this document was [[https://github.com/rubenrua/GstreamerCodeSnippets/tree/master/1.0/Python/pygst-tutorial][Ruben Gonzalez's]] rescue from Google's cache of the original by Jens Persson. This document differs in: 17 | 18 | - Ruben's markdown converted to org-mode 19 | - Command line and Python examples tested and some fixed for GStreamer 1.0 (problems may remain) 20 | - Change the ASCII art into [[http://ditaa.sourceforge.net/][ditaa]] diagrams 21 | - Text edited for minor language things 22 | 23 | Other useful sources of info on using GStreamer 1.0 Python bindings: 24 | 25 | - Ruben's [[https://github.com/rubenrua/GstreamerCodeSnippets][GstreamerCodeSnippets]] 26 | - [[https://lazka.github.io/pgi-docs/#Gst-1.0][API Reference]] 27 | - [[https://wiki.ubuntu.com/Novacut/GStreamer1.0][Novacut's porting guide]] 28 | -------------------------------------------------------------------------------- /capabilities-example.py: -------------------------------------------------------------------------------- 1 | 2 | #!/usr/bin/env python 3 | 4 | import gi 5 | gi.require_version("Gst", "1.0") 6 | from gi.repository import Gst, GObject, Gtk 7 | 8 | class GTK_Main: 9 | 10 | def __init__(self): 11 | window = Gtk.Window(Gtk.WindowType.TOPLEVEL) 12 | window.set_title("Videotestsrc-Player") 13 | window.set_default_size(300, -1) 14 | window.connect("destroy", Gtk.main_quit, "WM destroy") 15 | vbox = Gtk.VBox() 16 | window.add(vbox) 17 | self.button = Gtk.Button("Start") 18 | self.button.connect("clicked", self.start_stop) 19 | vbox.add(self.button) 20 | window.show_all() 21 | self.player = Gst.Pipeline.new("player") 22 | source = Gst.ElementFactory.make("videotestsrc", "video-source") 23 | sink = Gst.ElementFactory.make("xvimagesink", "video-output") 24 | caps = Gst.Caps.from_string("video/x-raw, width=320, height=230") 25 | filter = Gst.ElementFactory.make("capsfilter", "filter") 26 | filter.set_property("caps", caps) 27 | self.player.add(source) 28 | self.player.add(filter) 29 | self.player.add(sink) 30 | source.link(filter) 31 | filter.link(sink) 32 | 33 | def start_stop(self, w): 34 | if self.button.get_label() == "Start": 35 | self.button.set_label("Stop") 36 | self.player.set_state(Gst.State.PLAYING) 37 | else: 38 | self.player.set_state(Gst.State.NULL) 39 | self.button.set_label("Start") 40 | 41 | GObject.threads_init() 42 | Gst.init(None) 43 | GTK_Main() 44 | Gtk.main() 45 | -------------------------------------------------------------------------------- /capabilities-playbin-example.py: -------------------------------------------------------------------------------- 1 | 2 | #!/usr/bin/env python 3 | 4 | import os 5 | import gi 6 | gi.require_version("Gst", "1.0") 7 | from gi.repository import Gst, GObject, Gtk 8 | 9 | class GTK_Main: 10 | def __init__(self): 11 | window = Gtk.Window(Gtk.WindowType.TOPLEVEL) 12 | window.set_title("Video-Player") 13 | window.set_default_size(500, 400) 14 | window.connect("destroy", Gtk.main_quit, "WM destroy") 15 | vbox = Gtk.VBox() 16 | window.add(vbox) 17 | hbox = Gtk.HBox() 18 | vbox.pack_start(hbox, False, False, 0) 19 | self.entry = Gtk.Entry() 20 | hbox.add(self.entry) 21 | self.button = Gtk.Button("Start") 22 | hbox.pack_start(self.button, False, False, 0) 23 | self.button.connect("clicked", self.start_stop) 24 | self.movie_window = Gtk.DrawingArea() 25 | vbox.add(self.movie_window) 26 | window.show_all() 27 | self.player = Gst.ElementFactory.make("playbin", "player") 28 | self.bin = Gst.Bin.new("my-bin") 29 | videoscale = Gst.ElementFactory.make("videoscale") 30 | videoscale.set_property("method", 1) 31 | pad = videoscale.get_static_pad("sink") 32 | ghostpad = Gst.GhostPad.new("sink", pad) 33 | ghostpad.set_active(True) 34 | self.bin.add_pad(ghostpad) 35 | caps = Gst.Caps.from_string("video/x-raw, width=720") 36 | filter = Gst.ElementFactory.make("capsfilter", "filter") 37 | filter.set_property("caps", caps) 38 | textoverlay = Gst.ElementFactory.make('textoverlay') 39 | textoverlay.set_property("text", "GNUTV") 40 | textoverlay.set_property("font-desc", "normal 14") 41 | # TypeError: object of type `GstTextOverlay' does not have property `halign' 42 | #textoverlay.set_property("halign", "right") 43 | # TypeError: object of type `GstTextOverlay' does not have property `valign' 44 | #textoverlay.set_property("valign", "top") 45 | conv = Gst.ElementFactory.make ("videoconvert", "conv") 46 | videosink = Gst.ElementFactory.make("autovideosink") 47 | 48 | self.bin.add(videoscale) 49 | self.bin.add(filter) 50 | self.bin.add(textoverlay) 51 | self.bin.add(conv) 52 | self.bin.add(videosink) 53 | 54 | videoscale.link(filter) 55 | filter.link(textoverlay) 56 | textoverlay.link(conv) 57 | conv.link(videosink) 58 | 59 | self.player.set_property("video-sink", self.bin) 60 | bus = self.player.get_bus() 61 | bus.add_signal_watch() 62 | bus.enable_sync_message_emission() 63 | bus.connect("message", self.on_message) 64 | bus.connect("sync-message::element", self.on_sync_message) 65 | 66 | def start_stop(self, w): 67 | if self.button.get_label() == "Start": 68 | filepath = self.entry.get_text().strip() 69 | if os.path.exists(filepath): 70 | filepath = os.path.realpath(filepath) 71 | self.button.set_label("Stop") 72 | self.player.set_property("uri", "file://" + filepath) 73 | self.player.set_state(Gst.State.PLAYING) 74 | else: 75 | self.player.set_state(Gst.State.NULL) 76 | self.button.set_label("Start") 77 | 78 | def on_message(self, bus, message): 79 | typ = message.type 80 | if typ == Gst.MessageType.EOS: 81 | self.player.set_state(Gst.State.NULL) 82 | self.button.set_label("Start") 83 | elif typ == Gst.MessageType.ERROR: 84 | self.player.set_state(Gst.State.NULL) 85 | self.button.set_label("Start") 86 | err, debug = message.parse_error() 87 | print("Error: %s" % err, debug) 88 | 89 | def on_sync_message(self, bus, message): 90 | if message.structure is None: 91 | return 92 | message_name = message.structure.get_name() 93 | if message_name == "prepare-xwindow-id": 94 | imagesink = message.src 95 | imagesink.set_property("force-aspect-ratio", True) 96 | imagesink.set_xwindow_id(self.movie_window.window.xid) 97 | 98 | GObject.threads_init() 99 | Gst.init(None) 100 | GTK_Main() 101 | Gtk.main() 102 | -------------------------------------------------------------------------------- /capabilities-resolution-example.py: -------------------------------------------------------------------------------- 1 | 2 | #!/usr/bin/env python 3 | 4 | import os 5 | import gi 6 | gi.require_version("Gst", "1.0") 7 | from gi.repository import Gst, GObject, Gtk 8 | 9 | class GTK_Main: 10 | def __init__(self): 11 | window = Gtk.Window(Gtk.WindowType.TOPLEVEL) 12 | window.set_title("Resolutionchecker") 13 | window.set_default_size(300, -1) 14 | window.connect("destroy", Gtk.main_quit, "WM destroy") 15 | vbox = Gtk.VBox() 16 | window.add(vbox) 17 | self.entry = Gtk.Entry() 18 | vbox.pack_start(self.entry, False, True, 0) 19 | self.button = Gtk.Button("Check") 20 | self.button.connect("clicked", self.start_stop) 21 | vbox.add(self.button) 22 | window.show_all() 23 | self.player = Gst.Pipeline.new("player") 24 | source = Gst.ElementFactory.make("filesrc", "file-source") 25 | decoder = Gst.ElementFactory.make("decodebin", "decoder") 26 | decoder.connect("pad-added", self.decoder_callback) 27 | self.fakea = Gst.ElementFactory.make("fakesink", "fakea") 28 | self.fakev = Gst.ElementFactory.make("fakesink", "fakev") 29 | self.player.add(source) 30 | self.player.add(decoder) 31 | self.player.add(self.fakea) 32 | self.player.add(self.fakev) 33 | source.link(decoder) 34 | bus = self.player.get_bus() 35 | bus.add_signal_watch() 36 | bus.connect("message", self.on_message) 37 | 38 | def start_stop(self, w): 39 | filepath = self.entry.get_text().strip() 40 | if os.path.isfile(filepath): 41 | filepath = os.path.realpath(filepath) 42 | self.player.set_state(Gst.State.NULL) 43 | self.player.get_by_name("file-source").set_property("location", filepath) 44 | self.player.set_state(Gst.State.PAUSED) 45 | def on_message(self, bus, message): 46 | typ = message.type 47 | if typ == Gst.MessageType.STATE_CHANGED: 48 | if message.parse_state_changed()[1] == Gst.State.PAUSED: 49 | decoder = self.player.get_by_name("decoder") 50 | for pad in decoder.srcpads: 51 | caps = pad.query_caps(None) 52 | structure_name = caps.to_string() 53 | width = caps.get_structure(0).get_int('width') 54 | height = caps.get_structure(0).get_int('height') 55 | if structure_name.startswith("video") and len(str(width)) < 6: 56 | print "Width:%d, Height:%d" %(width, height) 57 | self.player.set_state(Gst.State.NULL) 58 | break 59 | elif typ == Gst.MessageType.ERROR: 60 | err, debug = message.parse_error() 61 | print("Error: %s" % err, debug) 62 | self.player.set_state(Gst.State.NULL) 63 | 64 | def decoder_callback(self, decoder, pad): 65 | caps = pad.query_caps(None) 66 | structure_name = caps.to_string() 67 | if structure_name.startswith("video"): 68 | fv_pad = self.fakev.get_static_pad("sink") 69 | pad.link(fv_pad) 70 | elif structure_name.startswith("audio"): 71 | fa_pad = self.fakea.get_static_pad("sink") 72 | pad.link(fa_pad) 73 | 74 | GObject.threads_init() 75 | Gst.init(None) 76 | GTK_Main() 77 | Gtk.main() 78 | -------------------------------------------------------------------------------- /dynamic-ghostpad-example.py: -------------------------------------------------------------------------------- 1 | 2 | #!/usr/bin/env python 3 | 4 | import sys, os 5 | import gi 6 | gi.require_version('Gst', '1.0') 7 | from gi.repository import Gst, GObject, Gtk 8 | 9 | class GTK_Main(object): 10 | 11 | def __init__(self): 12 | window = Gtk.Window(Gtk.WindowType.TOPLEVEL) 13 | window.set_title("Vorbis-Player") 14 | window.set_default_size(500, 200) 15 | window.connect("destroy", Gtk.main_quit, "WM destroy") 16 | vbox = Gtk.VBox() 17 | window.add(vbox) 18 | self.entry = Gtk.Entry() 19 | vbox.pack_start(self.entry, False, False, 0) 20 | self.button = Gtk.Button("Start") 21 | vbox.add(self.button) 22 | self.button.connect("clicked", self.start_stop) 23 | window.show_all() 24 | 25 | self.player = Gst.Pipeline.new("player") 26 | source = Gst.ElementFactory.make("filesrc", "file-source") 27 | demuxer = Gst.ElementFactory.make("oggdemux", "demuxer") 28 | demuxer.connect("pad-added", self.demuxer_callback) 29 | self.audio_decoder = Gst.ElementFactory.make("vorbisdec", "vorbis-decoder") 30 | audioconv = Gst.ElementFactory.make("audioconvert", "converter") 31 | audiosink = Gst.ElementFactory.make("autoaudiosink", "audio-output") 32 | 33 | self.player.add(source) 34 | self.player.add(demuxer) 35 | self.player.add(self.audio_decoder) 36 | self.player.add(audioconv) 37 | self.player.add(audiosink) 38 | 39 | source.link(demuxer) 40 | self.audio_decoder.link(audioconv) 41 | audioconv.link(audiosink) 42 | 43 | bus = self.player.get_bus() 44 | bus.add_signal_watch() 45 | bus.connect("message", self.on_message) 46 | 47 | def start_stop(self, w): 48 | if self.button.get_label() == "Start": 49 | filepath = self.entry.get_text().strip() 50 | if os.path.isfile(filepath): 51 | filepath = os.path.realpath(filepath) 52 | self.button.set_label("Stop") 53 | self.player.get_by_name("file-source").set_property("location", filepath) 54 | self.player.set_state(Gst.State.PLAYING) 55 | else: 56 | self.player.set_state(Gst.State.NULL) 57 | self.button.set_label("Start") 58 | 59 | def on_message(self, bus, message): 60 | t = message.type 61 | if t == Gst.MessageType.EOS: 62 | self.player.set_state(Gst.State.NULL) 63 | self.button.set_label("Start") 64 | elif t == Gst.MessageType.ERROR: 65 | err, debug = message.parse_error() 66 | print("Error: %s" % err, debug) 67 | self.player.set_state(Gst.State.NULL) 68 | self.button.set_label("Start") 69 | 70 | def demuxer_callback(self, demuxer, pad): 71 | adec_pad = self.audio_decoder.get_static_pad("sink") 72 | pad.link(adec_pad) 73 | 74 | 75 | GObject.threads_init() 76 | Gst.init(None) 77 | GTK_Main() 78 | Gtk.main() 79 | -------------------------------------------------------------------------------- /functions.js: -------------------------------------------------------------------------------- 1 | $( document ).ready (function () { 2 | $("pre.src-sh").each(function () { 3 | var lines = $( this ).text().split('\n'); 4 | for (var ind = 0 ; ind < lines.length ; ind++) { 5 | if ( ! lines[ind] ) { 6 | continue; 7 | } 8 | lines[ind] = '' + lines[ind]; 9 | } 10 | $( this ).html(lines.join("\n")); 11 | }); 12 | 13 | 14 | // broken way to do things for now. 15 | $( "div.status" ).prepend( $('
org source') ) 16 | $( "div.status" ).prepend( $('
') ) 17 | 18 | }); 19 | 20 | -------------------------------------------------------------------------------- /gst-inspect-playbin.txt: -------------------------------------------------------------------------------- 1 | Factory Details: 2 | Rank none (0) 3 | Long-name Player Bin 2 4 | Klass Generic/Bin/Player 5 | Description Autoplug and play media from an uri 6 | Author Wim Taymans 7 | 8 | Plugin Details: 9 | Name playback 10 | Description various playback elements 11 | Filename /usr/lib/x86_64-linux-gnu/gstreamer-1.0/libgstplayback.so 12 | Version 1.2.4 13 | License LGPL 14 | Source module gst-plugins-base 15 | Source release date 2014-04-18 16 | Binary package GStreamer Base Plugins (Ubuntu) 17 | Origin URL https://launchpad.net/distros/ubuntu/+source/gst-plugins-base1.0 18 | 19 | GObject 20 | +----GInitiallyUnowned 21 | +----GstObject 22 | +----GstElement 23 | +----GstBin 24 | +----GstPipeline 25 | +----GstPlayBin 26 | 27 | Implemented Interfaces: 28 | GstChildProxy 29 | GstStreamVolume 30 | GstVideoOverlay 31 | GstNavigation 32 | GstColorBalance 33 | 34 | Pad Templates: 35 | none 36 | 37 | Element Flags: 38 | no flags set 39 | 40 | Bin Flags: 41 | no flags set 42 | 43 | Element Implementation: 44 | Has change_state() function: gst_play_bin_change_state 45 | 46 | Element has no clocking capabilities. 47 | Element has no indexing capabilities. 48 | Element has no URI handling capabilities. 49 | 50 | Pads: 51 | none 52 | 53 | Element Properties: 54 | name : The name of the object 55 | flags: readable, writable 56 | String. Default: "playbin0" 57 | parent : The parent of the object 58 | flags: readable, writable 59 | Object of type "GstObject" 60 | async-handling : The bin will handle Asynchronous state changes 61 | flags: readable, writable 62 | Boolean. Default: false 63 | message-forward : Forwards all children messages 64 | flags: readable, writable 65 | Boolean. Default: false 66 | delay : Expected delay needed for elements to spin up to PLAYING in nanoseconds 67 | flags: readable, writable 68 | Unsigned Integer64. Range: 0 - 18446744073709551615 Default: 0 69 | auto-flush-bus : Whether to automatically flush the pipeline's bus when going from READY into NULL state 70 | flags: readable, writable 71 | Boolean. Default: true 72 | uri : URI of the media to play 73 | flags: readable, writable 74 | String. Default: null 75 | current-uri : The currently playing URI 76 | flags: readable 77 | String. Default: null 78 | suburi : Optional URI of a subtitle 79 | flags: readable, writable 80 | String. Default: null 81 | current-suburi : The currently playing URI of a subtitle 82 | flags: readable 83 | String. Default: null 84 | source : Source element 85 | flags: readable 86 | Object of type "GstElement" 87 | flags : Flags to control behaviour 88 | flags: readable, writable 89 | Flags "GstPlayFlags" Default: 0x00000617, "soft-colorbalance+deinterlace+soft-volume+text+audio+video" 90 | (0x00000001): video - Render the video stream 91 | (0x00000002): audio - Render the audio stream 92 | (0x00000004): text - Render subtitles 93 | (0x00000008): vis - Render visualisation when no video is present 94 | (0x00000010): soft-volume - Use software volume 95 | (0x00000020): native-audio - Only use native audio formats 96 | (0x00000040): native-video - Only use native video formats 97 | (0x00000080): download - Attempt progressive download buffering 98 | (0x00000100): buffering - Buffer demuxed/parsed data 99 | (0x00000200): deinterlace - Deinterlace video if necessary 100 | (0x00000400): soft-colorbalance - Use software color balance 101 | n-video : Total number of video streams 102 | flags: readable 103 | Integer. Range: 0 - 2147483647 Default: 0 104 | current-video : Currently playing video stream (-1 = auto) 105 | flags: readable, writable 106 | Integer. Range: -1 - 2147483647 Default: -1 107 | n-audio : Total number of audio streams 108 | flags: readable 109 | Integer. Range: 0 - 2147483647 Default: 0 110 | current-audio : Currently playing audio stream (-1 = auto) 111 | flags: readable, writable 112 | Integer. Range: -1 - 2147483647 Default: -1 113 | n-text : Total number of text streams 114 | flags: readable 115 | Integer. Range: 0 - 2147483647 Default: 0 116 | current-text : Currently playing text stream (-1 = auto) 117 | flags: readable, writable 118 | Integer. Range: -1 - 2147483647 Default: -1 119 | subtitle-encoding : Encoding to assume if input subtitles are not in UTF-8 encoding. If not set, the GST_SUBTITLE_ENCODING environment variable will be checked for an encoding to use. If that is not set either, ISO-8859-15 will be assumed. 120 | flags: readable, writable 121 | String. Default: null 122 | audio-sink : the audio output element to use (NULL = default sink) 123 | flags: readable, writable 124 | Object of type "GstElement" 125 | video-sink : the video output element to use (NULL = default sink) 126 | flags: readable, writable 127 | Object of type "GstElement" 128 | vis-plugin : the visualization element to use (NULL = default) 129 | flags: readable, writable 130 | Object of type "GstElement" 131 | text-sink : the text output element to use (NULL = default subtitleoverlay) 132 | flags: readable, writable 133 | Object of type "GstElement" 134 | video-stream-combiner: Current video stream combiner (NULL = input-selector) 135 | flags: readable, writable 136 | Object of type "GstElement" 137 | audio-stream-combiner: Current audio stream combiner (NULL = input-selector) 138 | flags: readable, writable 139 | Object of type "GstElement" 140 | text-stream-combiner: Current text stream combiner (NULL = input-selector) 141 | flags: readable, writable 142 | Object of type "GstElement" 143 | volume : The audio volume, 1.0=100% 144 | flags: readable, writable 145 | Double. Range: 0 - 10 Default: 1 146 | mute : Mute the audio channel without changing the volume 147 | flags: readable, writable 148 | Boolean. Default: false 149 | sample : The last sample (NULL = no video available) 150 | flags: readable 151 | Boxed pointer of type "GstSample" 152 | subtitle-font-desc : Pango font description of font to be used for subtitle rendering 153 | flags: writable 154 | String. Default: null Write only 155 | connection-speed : Network connection speed in kbps (0 = unknown) 156 | flags: readable, writable 157 | Unsigned Integer64. Range: 0 - 18446744073709551 Default: 0 158 | buffer-size : Buffer size when buffering network streams 159 | flags: readable, writable 160 | Integer. Range: -1 - 2147483647 Default: -1 161 | buffer-duration : Buffer duration when buffering network streams 162 | flags: readable, writable 163 | Integer64. Range: -1 - 9223372036854775807 Default: -1 164 | av-offset : The synchronisation offset between audio and video in nanoseconds 165 | flags: readable, writable 166 | Integer64. Range: -9223372036854775808 - 9223372036854775807 Default: 0 167 | ring-buffer-max-size: Max. amount of data in the ring buffer (bytes, 0 = ring buffer disabled) 168 | flags: readable, writable 169 | Unsigned Integer64. Range: 0 - 4294967295 Default: 0 170 | force-aspect-ratio : When enabled, scaling will respect original aspect ratio 171 | flags: readable, writable 172 | Boolean. Default: true 173 | 174 | Element Signals: 175 | "about-to-finish" : void user_function (GstElement* object, 176 | gpointer user_data); 177 | "video-changed" : void user_function (GstElement* object, 178 | gpointer user_data); 179 | "audio-changed" : void user_function (GstElement* object, 180 | gpointer user_data); 181 | "text-changed" : void user_function (GstElement* object, 182 | gpointer user_data); 183 | "video-tags-changed" : void user_function (GstElement* object, 184 | gint arg0, 185 | gpointer user_data); 186 | "audio-tags-changed" : void user_function (GstElement* object, 187 | gint arg0, 188 | gpointer user_data); 189 | "text-tags-changed" : void user_function (GstElement* object, 190 | gint arg0, 191 | gpointer user_data); 192 | "source-setup" : void user_function (GstElement* object, 193 | GstElement* arg0, 194 | gpointer user_data); 195 | 196 | Element Actions: 197 | "get-video-tags" : GstTagList * user_function (GstElement* object, 198 | gint arg0); 199 | "get-audio-tags" : GstTagList * user_function (GstElement* object, 200 | gint arg0); 201 | "get-text-tags" : GstTagList * user_function (GstElement* object, 202 | gint arg0); 203 | "convert-sample" : GstSample * user_function (GstElement* object, 204 | GstCaps* arg0); 205 | "get-video-pad" : GstPad * user_function (GstElement* object, 206 | gint arg0); 207 | "get-audio-pad" : GstPad * user_function (GstElement* object, 208 | gint arg0); 209 | "get-text-pad" : GstPad * user_function (GstElement* object, 210 | gint arg0); 211 | 212 | Children: 213 | playsink 214 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | Tutorial on using GStreamer Python Bindings in org-mode 7 | 8 | 9 | 10 | 11 | 91 | 137 | 138 | 139 |
140 |

Tutorial on using GStreamer Python Bindings in org-mode

141 | 155 | 156 |

157 | Note, the PDF and HTML are generated from the org file and may not always be fully in sync. 158 |

159 |
160 |
161 |

Author: Brett Viren

162 |

Created: 2015-01-04 Sun 15:55

163 |

Emacs 24.3.1 (Org mode 8.2.5h)

164 |

Validate

165 |
166 | 167 | 168 | -------------------------------------------------------------------------------- /index.org: -------------------------------------------------------------------------------- 1 | #+TITLE: Tutorial on using GStreamer Python Bindings in org-mode 2 | 3 | - [[./README.org][README]] has some additional meta-info 4 | - The Tutorial: 5 | - [[./pygst-tutorial.org][org]] 6 | - [[http://brettviren.github.io/pygst-tutorial-org/pygst-tutorial.pdf][PDF]] 7 | - [[http://brettviren.github.io/pygst-tutorial-org/pygst-tutorial.html][HTML]] 8 | 9 | Note, the PDF and HTML are generated from the org file and may not always be fully in sync. 10 | 11 | -------------------------------------------------------------------------------- /pipeline-block.svg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brettviren/pygst-tutorial-org/a9c543f1e2b26e5036cede51e868f7e7ef4a64c6/pipeline-block.svg -------------------------------------------------------------------------------- /pipeline-branch-block.svg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brettviren/pygst-tutorial-org/a9c543f1e2b26e5036cede51e868f7e7ef4a64c6/pipeline-branch-block.svg -------------------------------------------------------------------------------- /pipeline-branch-example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import gi 5 | gi.require_version('Gst', '1.0') 6 | from gi.repository import Gst, GObject, Gtk 7 | 8 | class GTK_Main(object): 9 | 10 | def __init__(self): 11 | window = Gtk.Window(Gtk.WindowType.TOPLEVEL) 12 | window.set_title("Mpeg2-Player") 13 | window.set_default_size(500, 400) 14 | window.connect("destroy", Gtk.main_quit, "WM destroy") 15 | vbox = Gtk.VBox() 16 | window.add(vbox) 17 | hbox = Gtk.HBox() 18 | vbox.pack_start(hbox, False, False, 0) 19 | self.entry = Gtk.Entry() 20 | hbox.add(self.entry) 21 | self.button = Gtk.Button("Start") 22 | hbox.pack_start(self.button, False, False, 0) 23 | self.button.connect("clicked", self.start_stop) 24 | self.movie_window = Gtk.DrawingArea() 25 | vbox.add(self.movie_window) 26 | window.show_all() 27 | 28 | self.player = Gst.Pipeline.new("player") 29 | source = Gst.ElementFactory.make("filesrc", "file-source") 30 | demuxer = Gst.ElementFactory.make("mpegpsdemux", "demuxer") 31 | demuxer.connect("pad-added", self.demuxer_callback) 32 | self.video_decoder = Gst.ElementFactory.make("mpeg2dec", "video-decoder") 33 | self.audio_decoder = Gst.ElementFactory.make("mad", "audio-decoder") 34 | audioconv = Gst.ElementFactory.make("audioconvert", "converter") 35 | audiosink = Gst.ElementFactory.make("autoaudiosink", "audio-output") 36 | videosink = Gst.ElementFactory.make("autovideosink", "video-output") 37 | self.queuea = Gst.ElementFactory.make("queue", "queuea") 38 | self.queuev = Gst.ElementFactory.make("queue", "queuev") 39 | colorspace = Gst.ElementFactory.make("videoconvert", "colorspace") 40 | 41 | self.player.add(source) 42 | self.player.add(demuxer) 43 | self.player.add(self.video_decoder) 44 | self.player.add(self.audio_decoder) 45 | self.player.add(audioconv) 46 | self.player.add(audiosink) 47 | self.player.add(videosink) 48 | self.player.add(self.queuea) 49 | self.player.add(self.queuev) 50 | self.player.add(colorspace) 51 | 52 | source.link(demuxer) 53 | 54 | self.queuev.link(self.video_decoder) 55 | self.video_decoder.link(colorspace) 56 | colorspace.link(videosink) 57 | 58 | self.queuea.link(self.audio_decoder) 59 | self.audio_decoder.link(audioconv) 60 | audioconv.link(audiosink) 61 | 62 | bus = self.player.get_bus() 63 | bus.add_signal_watch() 64 | bus.enable_sync_message_emission() 65 | bus.connect("message", self.on_message) 66 | bus.connect("sync-message::element", self.on_sync_message) 67 | 68 | def start_stop(self, w): 69 | if self.button.get_label() == "Start": 70 | filepath = self.entry.get_text().strip() 71 | if os.path.isfile(filepath): 72 | filepath = os.path.realpath(filepath) 73 | self.button.set_label("Stop") 74 | self.player.get_by_name("file-source").set_property("location", filepath) 75 | self.player.set_state(Gst.State.PLAYING) 76 | else: 77 | self.player.set_state(Gst.State.NULL) 78 | self.button.set_label("Start") 79 | 80 | def on_message(self, bus, message): 81 | t = message.type 82 | if t == Gst.MessageType.EOS: 83 | self.player.set_state(Gst.State.NULL) 84 | self.button.set_label("Start") 85 | elif t == Gst.MessageType.ERROR: 86 | err, debug = message.parse_error() 87 | print("Error: %s" % err, debug) 88 | self.player.set_state(Gst.State.NULL) 89 | self.button.set_label("Start") 90 | 91 | def on_sync_message(self, bus, message): 92 | if message.get_structure().get_name() == 'prepare-window-handle': 93 | imagesink = message.src 94 | imagesink.set_property("force-aspect-ratio", True) 95 | xid = self.movie_window.get_property('window').get_xid() 96 | imagesink.set_window_handle(xid) 97 | 98 | def demuxer_callback(self, demuxer, pad): 99 | if pad.get_property("template").name_template == "video_%02d": 100 | qv_pad = self.queuev.get_pad("sink") 101 | pad.link(qv_pad) 102 | elif pad.get_property("template").name_template == "audio_%02d": 103 | qa_pad = self.queuea.get_pad("sink") 104 | pad.link(qa_pad) 105 | 106 | 107 | Gst.init(None) 108 | GTK_Main() 109 | GObject.threads_init() 110 | Gtk.main() 111 | -------------------------------------------------------------------------------- /pipeline-example.py: -------------------------------------------------------------------------------- 1 | 2 | #!/usr/bin/env python 3 | 4 | import sys, os 5 | import gi 6 | gi.require_version('Gst', '1.0') 7 | from gi.repository import Gst, GObject, Gtk 8 | 9 | class GTK_Main(object): 10 | 11 | def __init__(self): 12 | window = Gtk.Window(Gtk.WindowType.TOPLEVEL) 13 | window.set_title("MP3-Player") 14 | window.set_default_size(400, 200) 15 | window.connect("destroy", Gtk.main_quit, "WM destroy") 16 | vbox = Gtk.VBox() 17 | window.add(vbox) 18 | self.entry = Gtk.Entry() 19 | vbox.pack_start(self.entry, False, True, 0) 20 | self.button = Gtk.Button("Start") 21 | self.button.connect("clicked", self.start_stop) 22 | vbox.add(self.button) 23 | window.show_all() 24 | 25 | self.player = Gst.Pipeline.new("player") 26 | source = Gst.ElementFactory.make("filesrc", "file-source") 27 | decoder = Gst.ElementFactory.make("mad", "mp3-decoder") 28 | conv = Gst.ElementFactory.make("audioconvert", "converter") 29 | sink = Gst.ElementFactory.make("alsasink", "alsa-output") 30 | 31 | self.player.add(source) 32 | self.player.add(decoder) 33 | self.player.add(conv) 34 | self.player.add(sink) 35 | source.link(decoder) 36 | decoder.link(conv) 37 | conv.link(sink) 38 | 39 | bus = self.player.get_bus() 40 | bus.add_signal_watch() 41 | bus.connect("message", self.on_message) 42 | 43 | def start_stop(self, w): 44 | if self.button.get_label() == "Start": 45 | filepath = self.entry.get_text().strip() 46 | if os.path.isfile(filepath): 47 | filepath = os.path.realpath(filepath) 48 | self.button.set_label("Stop") 49 | self.player.get_by_name("file-source").set_property("location", filepath) 50 | self.player.set_state(Gst.State.PLAYING) 51 | else: 52 | self.player.set_state(Gst.State.NULL) 53 | self.button.set_label("Start") 54 | 55 | def on_message(self, bus, message): 56 | t = message.type 57 | if t == Gst.MessageType.EOS: 58 | self.player.set_state(Gst.State.NULL) 59 | self.button.set_label("Start") 60 | elif t == Gst.MessageType.ERROR: 61 | self.player.set_state(Gst.State.NULL) 62 | self.button.set_label("Start") 63 | err, debug = message.parse_error() 64 | print("Error: %s" % err, debug) 65 | 66 | Gst.init(None) 67 | GTK_Main() 68 | GObject.threads_init() 69 | Gtk.main() 70 | -------------------------------------------------------------------------------- /playbin-block.svg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brettviren/pygst-tutorial-org/a9c543f1e2b26e5036cede51e868f7e7ef4a64c6/playbin-block.svg -------------------------------------------------------------------------------- /playbin-example-audio.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import gi 5 | gi.require_version('Gst', '1.0') 6 | from gi.repository import Gst, GObject, Gtk 7 | 8 | class GTK_Main(object): 9 | 10 | def __init__(self): 11 | window = Gtk.Window(Gtk.WindowType.TOPLEVEL) 12 | window.set_title("Audio-Player") 13 | window.set_default_size(300, -1) 14 | window.connect("destroy", Gtk.main_quit, "WM destroy") 15 | vbox = Gtk.VBox() 16 | window.add(vbox) 17 | self.entry = Gtk.Entry() 18 | vbox.pack_start(self.entry, False, True, 0) 19 | self.button = Gtk.Button("Start") 20 | self.button.connect("clicked", self.start_stop) 21 | vbox.add(self.button) 22 | window.show_all() 23 | 24 | self.player = Gst.ElementFactory.make("playbin", "player") 25 | fakesink = Gst.ElementFactory.make("fakesink", "fakesink") 26 | self.player.set_property("video-sink", fakesink) 27 | bus = self.player.get_bus() 28 | bus.add_signal_watch() 29 | bus.connect("message", self.on_message) 30 | 31 | def start_stop(self, w): 32 | if self.button.get_label() == "Start": 33 | filepath = self.entry.get_text().strip() 34 | if os.path.isfile(filepath): 35 | filepath = os.path.realpath(filepath) 36 | self.button.set_label("Stop") 37 | self.player.set_property("uri", "file://" + filepath) 38 | self.player.set_state(Gst.State.PLAYING) 39 | else: 40 | self.player.set_state(Gst.State.NULL) 41 | self.button.set_label("Start") 42 | 43 | def on_message(self, bus, message): 44 | t = message.type 45 | if t == Gst.MessageType.EOS: 46 | self.player.set_state(Gst.State.NULL) 47 | self.button.set_label("Start") 48 | elif t == Gst.MessageType.ERROR: 49 | self.player.set_state(Gst.State.NULL) 50 | err, debug = message.parse_error() 51 | print("Error: %s" % err, debug) 52 | self.button.set_label("Start") 53 | 54 | 55 | Gst.init(None) 56 | GTK_Main() 57 | GObject.threads_init() 58 | Gtk.main() 59 | -------------------------------------------------------------------------------- /playbin-example-cliplayer.py: -------------------------------------------------------------------------------- 1 | 2 | #!/usr/bin/env python 3 | 4 | import sys, os, time, thread 5 | import gi 6 | gi.require_version('Gst', '1.0') 7 | from gi.repository import Gst, GLib, GObject 8 | 9 | class CLI_Main(object): 10 | 11 | def __init__(self): 12 | self.player = Gst.ElementFactory.make("playbin", "player") 13 | fakesink = Gst.ElementFactory.make("fakesink", "fakesink") 14 | self.player.set_property("video-sink", fakesink) 15 | bus = self.player.get_bus() 16 | bus.add_signal_watch() 17 | bus.connect("message", self.on_message) 18 | 19 | def on_message(self, bus, message): 20 | t = message.type 21 | if t == Gst.MessageType.EOS: 22 | self.player.set_state(Gst.State.NULL) 23 | self.playmode = False 24 | elif t == Gst.MessageType.ERROR: 25 | self.player.set_state(Gst.State.NULL) 26 | err, debug = message.parse_error() 27 | print("Error: %s" % err, debug) 28 | self.playmode = False 29 | 30 | def start(self): 31 | for filepath in sys.argv[1:]: 32 | if os.path.isfile(filepath): 33 | filepath = os.path.realpath(filepath) 34 | self.playmode = True 35 | self.player.set_property("uri", "file://" + filepath) 36 | self.player.set_state(Gst.State.PLAYING) 37 | while self.playmode: 38 | time.sleep(1) 39 | time.sleep(1) 40 | loop.quit() 41 | 42 | GObject.threads_init() 43 | Gst.init(None) 44 | mainclass = CLI_Main() 45 | thread.start_new_thread(mainclass.start, ()) 46 | loop = GLib.MainLoop() 47 | loop.run() 48 | -------------------------------------------------------------------------------- /playbin-example-video.py: -------------------------------------------------------------------------------- 1 | 2 | #!/usr/bin/env python 3 | 4 | import sys, os 5 | import gi 6 | gi.require_version('Gst', '1.0') 7 | from gi.repository import Gst, GObject, Gtk 8 | 9 | # Needed for window.get_xid(), xvimagesink.set_window_handle(), respectively: 10 | from gi.repository import GdkX11, GstVideo 11 | 12 | class GTK_Main(object): 13 | 14 | def __init__(self): 15 | window = Gtk.Window(Gtk.WindowType.TOPLEVEL) 16 | window.set_title("Video-Player") 17 | window.set_default_size(500, 400) 18 | window.connect("destroy", Gtk.main_quit, "WM destroy") 19 | vbox = Gtk.VBox() 20 | window.add(vbox) 21 | hbox = Gtk.HBox() 22 | vbox.pack_start(hbox, False, False, 0) 23 | self.entry = Gtk.Entry() 24 | hbox.add(self.entry) 25 | self.button = Gtk.Button("Start") 26 | hbox.pack_start(self.button, False, False, 0) 27 | self.button.connect("clicked", self.start_stop) 28 | self.movie_window = Gtk.DrawingArea() 29 | vbox.add(self.movie_window) 30 | window.show_all() 31 | 32 | self.player = Gst.ElementFactory.make("playbin", "player") 33 | bus = self.player.get_bus() 34 | bus.add_signal_watch() 35 | bus.enable_sync_message_emission() 36 | bus.connect("message", self.on_message) 37 | bus.connect("sync-message::element", self.on_sync_message) 38 | 39 | def start_stop(self, w): 40 | if self.button.get_label() == "Start": 41 | filepath = self.entry.get_text().strip() 42 | if os.path.isfile(filepath): 43 | filepath = os.path.realpath(filepath) 44 | self.button.set_label("Stop") 45 | self.player.set_property("uri", "file://" + filepath) 46 | self.player.set_state(Gst.State.PLAYING) 47 | else: 48 | self.player.set_state(Gst.State.NULL) 49 | self.button.set_label("Start") 50 | 51 | def on_message(self, bus, message): 52 | t = message.type 53 | if t == Gst.MessageType.EOS: 54 | self.player.set_state(Gst.State.NULL) 55 | self.button.set_label("Start") 56 | elif t == Gst.MessageType.ERROR: 57 | self.player.set_state(Gst.State.NULL) 58 | err, debug = message.parse_error() 59 | print("Error: %s" % err, debug) 60 | self.button.set_label("Start") 61 | 62 | def on_sync_message(self, bus, message): 63 | if message.get_structure().get_name() == 'prepare-window-handle': 64 | imagesink = message.src 65 | imagesink.set_property("force-aspect-ratio", True) 66 | imagesink.set_window_handle(self.movie_window.get_property('window').get_xid()) 67 | 68 | 69 | GObject.threads_init() 70 | Gst.init(None) 71 | GTK_Main() 72 | Gtk.main() 73 | -------------------------------------------------------------------------------- /playbin-pads-block.svg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brettviren/pygst-tutorial-org/a9c543f1e2b26e5036cede51e868f7e7ef4a64c6/playbin-pads-block.svg -------------------------------------------------------------------------------- /pygst-tutorial.org: -------------------------------------------------------------------------------- 1 | #+TITLE: Python GStreamer Tutorial 2 | #+AUTHOR: Jens Persson and Ruben Gonzalez and Brett Viren 3 | #+VERSION: 1.0 4 | #+DESCRIPTION: Rescued by from Internet death by [[https://github.com/rubenrua/GstreamerCodeSnippets][rubenrua]] 5 | #+SETUPFILE: setup.org 6 | 7 | This tutorial aims at giving a brief introduction to the GStreamer 1.0 multimedia framework using its Python bindings. 8 | 9 | * Meta 10 | <> 11 | 12 | A GStreamer application is built as a directed, acyclic graph. In the figures these graphs are illustrated with the convention: 13 | 14 | - right solid line rectangles indicate basic GStreamer /elements/ 15 | - rounded solid line rectangles to indicate GStreamer /bins/ (and /pipelines/) subclasses of /elements/. 16 | - rounded dashed line rectangles to indicate /pads/ 17 | 18 | #+BEGIN_HTML 19 | 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. 20 | #+END_HTML 21 | 22 | * Introduction 23 | <> 24 | 25 | 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 [[https://lazka.github.io/pgi-docs/#GLib-2.0/structs/MainLoop.html][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 [[http://python.org/doc][Online Python docs]]. 26 | 27 | There are also some example coding distributed with the PyGST source which you may browse at the [[https://gitlab.freedesktop.org/gstreamer/gst-python/-/tree/master/examples][gst-python git repository]]. Reference documents for GStreamer and the rest of the ecosystem it relies on are available at [[https://lazka.github.io/pgi-docs/#Gst-1.0][lazka's GitHub site]]. The main [[http://gstreamer.freedesktop.org/][GStreamer]] site has [[http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gstreamer/html/][Reference Manual]], [[https://gstreamer.freedesktop.org/documentation/frequently-asked-questions/index.html][FAQ]], [[https://gstreamer.freedesktop.org/documentation/application-development/index.html][Applications Development Manual]] and [[https://gstreamer.freedesktop.org/documentation/plugin-development/index.html][Plugin Writer's Guide]]. This tutorial targets the GStreamer 1.0 API which all v1.x releases should follow. The Novacut project has a [[https://wiki.ubuntu.com/Novacut/GStreamer1.0][guide to porting]] Python applications from the prior 0.1 API to 1.0. 28 | Finally, GStreamer provides the [[http://docs.gstreamer.com/display/GstSDK/][GstSDK documentation]] which includes substantial C programming tutorials. 29 | 30 | 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 [[https://github.com/brettviren/pygst-tutorial-org/][clone the repository on GitHub]] and issue a pull request. Note, the original source of this document was [[http://developer.berlios.de/projects/pygstdocs/][here]] but is now dead. 31 | 32 | ** Command Line 33 | <> 34 | 35 | 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 [[http://docs.gstreamer.com/display/GstSDK/gst-inspect][=gst-inspect=]] you can track highlevel elements which are shipped with the various plugins packages. 36 | 37 | #+BEGIN_SRC sh :eval no 38 | man gst-inspect-1.0 39 | #+END_SRC 40 | 41 | 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: 42 | 43 | #+BEGIN_SRC sh :results output text :exports both 44 | gst-inspect-1.0 | grep mp3 | sort | head -3 45 | #+END_SRC 46 | 47 | #+RESULTS: 48 | : flump3dec: flump3dec: Fluendo MP3 Decoder (liboil build) 49 | : lame: lamemp3enc: L.A.M.E. mp3 encoder 50 | : libav: avdec_mp3adufloat: libav ADU (Application Data Unit) MP3 (MPEG audio layer 3) decoder 51 | 52 | The [[http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-base-plugins/html/gst-plugins-base-plugins-playbin.html][=playbin=]] element is an autoplugger which usually plays anything you throw at it, if you have the appropriate plugins installed. 53 | 54 | #+BEGIN_SRC sh :results none :exports code 55 | gst-inspect-1.0 playbin > gst-inspect-playbin.txt 56 | #+END_SRC 57 | 58 | Browse example output [[./gst-inspect-playbin.txt][here]]. 59 | 60 | You can also run pipelines directly in a terminal with [[http://docs.gstreamer.com/display/GstSDK/gst-launch][=gst-launch=]]: 61 | 62 | #+BEGIN_SRC sh :eval no 63 | man gst-launch-1.0 64 | #+END_SRC 65 | 66 | For playing a file with playbin: 67 | 68 | #+BEGIN_SRC sh :eval no 69 | gst-launch-1.0 playbin \ 70 | uri=http://docs.gstreamer.com/media/sintel_trailer-480p.webm 71 | #+END_SRC 72 | 73 | It's also possible to link elements together with "!": 74 | 75 | #+BEGIN_SRC sh :eval no 76 | gst-launch-1.0 audiotestsrc ! alsasink 77 | #+END_SRC 78 | 79 | You may also make different streams in the pipeline: 80 | 81 | #+BEGIN_SRC sh :eval no 82 | gst-launch-1.0 audiotestsrc ! alsasink videotestsrc ! xvimagesink 83 | #+END_SRC 84 | 85 | Or, you can make a single frame JPEG 86 | 87 | #+BEGIN_SRC sh :results none :exports code 88 | gst-launch-1.0 videotestsrc num-buffers=1 ! jpegenc ! \ 89 | filesink location=videotestsrc-frame.jpg 90 | #+END_SRC 91 | 92 | [[./videotestsrc-frame.jpg]] 93 | 94 | 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. 95 | 96 | #+BEGIN_SRC sh :eval no 97 | gst-launch-1.0 audiotestsrc ! vorbisenc ! oggmux name=mux ! \ 98 | filesink location=file.ogg videotestsrc ! theoraenc ! mux. 99 | #+END_SRC 100 | 101 | In the next chapter we will show you more examples with Playbin. 102 | 103 | * Playbin 104 | <> 105 | 106 | The [[http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-base-plugins/html/gst-plugins-base-plugins-playbin.html][=playbin=]] element was exercised from the command line in section [[subsec:cli]] 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: 107 | 108 | #+BEGIN_SRC python :results output :exports both 109 | import gi 110 | gi.require_version('Gst', '1.0') 111 | from gi.repository import Gst 112 | Gst.init(None) 113 | # ... 114 | my_playbin = Gst.ElementFactory.make("playbin", None) 115 | assert my_playbin 116 | print my_playbin 117 | #+END_SRC 118 | 119 | #+RESULTS: 120 | : <__main__.GstPlayBin object at 0x7fd8e88e6aa0 (GstPlayBin at 0x1c0cf00)> 121 | 122 | To get information about a playbin run: 123 | 124 | #+BEGIN_SRC sh :eval no 125 | gst-inspect-0.10 playbin 126 | #+END_SRC 127 | 128 | 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. 129 | 130 | 131 | #+header: :file (by-backend (latex "playbin-block.pdf") (t "playbin-block.svg")) 132 | #+BEGIN_SRC ditaa 133 | 134 | /--------------------------------------------------------------------\ 135 | | +----------------+ /---------------\ | 136 | | | | | | | 137 | | +->-+ optional stuff +->-+ autoaudiosink +----->- Audio Output 138 | | /----------------\ | | | | | | 139 | | | +--+ +----------------+ \---------------/ | 140 | uri ----->-+ uridecodebin | | 141 | | | +--+ +----------------+ /---------------\ | 142 | | \----------------/ | | | | | | 143 | | +->-+ optional stuff +->-+ autovideosink +----->- Video Output 144 | | | | | | | 145 | | playbin +----------------+ \---------------/ | 146 | \--------------------------------------------------------------------/ 147 | #+END_SRC 148 | 149 | #+ATTR_HTML: :width 100% 150 | #+RESULTS: 151 | [[file:playbin-block.svg]] 152 | 153 | 154 | 155 | 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. 156 | 157 | ** Audio with Playbin 158 | <> 159 | 160 | 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: 161 | 162 | #+BEGIN_SRC sh :eval no 163 | python playbin-example-audio.py 164 | #+END_SRC 165 | 166 | It will open a small window with a text entry. Enter the full path to some audio file and click "Start". 167 | 168 | #+HTML: playbin-example-audio.py 169 | #+INCLUDE: playbin-example-audio.py src python 170 | 171 | ** Adding Video 172 | <> 173 | 174 | 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: 175 | 176 | #+BEGIN_SRC python :eval no 177 | fakesink = Gst.ElementFactory.make("fakesink", "fakesink") 178 | self.player.set_property("video-sink", fakesink) 179 | #+END_SRC 180 | 181 | If you want to show the video output in a specified window you'll have to use the [[https://lazka.github.io/pgi-docs/#Gst-1.0/classes/Bus.html#Gst.Bus.enable_sync_message_emission][=enable_sync_message_emission()=]] method on the bus. Here is an example with the video window embedded in the program. 182 | 183 | #+HTML: playbin-example-video.py 184 | #+INCLUDE: playbin-example-video.py src python 185 | 186 | And just to make things a little more complicated you can switch the =playbin='s video sink to a {{{gstclass(Bin)}}} with a {{{gstclass(GhostPad)}}} on it. Here's an example with a [[http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-base-plugins/html/gst-plugins-base-plugins-timeoverlay.html][=timeoverlay=]]. 187 | 188 | #+BEGIN_SRC python :eval no 189 | bin = Gst.Bin.new("my-bin") 190 | timeoverlay = Gst.ElementFactory.make("timeoverlay") 191 | bin.add(timeoverlay) 192 | pad = timeoverlay.get_static_pad("video_sink") 193 | ghostpad = Gst.GhostPad.new("sink", pad) 194 | bin.add_pad(ghostpad) 195 | videosink = Gst.ElementFactory.make("autovideosink") 196 | bin.add(videosink) 197 | timeoverlay.link(videosink) 198 | self.player.set_property("video-sink", bin) 199 | #+END_SRC 200 | 201 | Add that code to the example above and you'll get a =timeoverlay= too. We'll talk more about [[http://gstreamer.freedesktop.org/data/doc/gstreamer/head/manual/html/section-pads-ghost.html]["ghost pads"]] later. 202 | 203 | Here now adds a CLI example which plays music. It can be run it with: 204 | 205 | #+BEGIN_SRC sh :eval no 206 | python cliplayer.py /path/to/file1.mp3 /path/to/file2.ogg 207 | #+END_SRC 208 | 209 | #+HTML: playbin-example-cliplayer.py 210 | #+INCLUDE: playbin-example-cliplayer.py src python 211 | 212 | A =playbin= implements a {{{gstclass(Pipeline)}}} element and that's what the next chapter is going to tell you more about. 213 | 214 | * Pipeline 215 | <> 216 | 217 | A {{{gstclass(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: 218 | 219 | #+BEGIN_SRC python :eval no 220 | my_pipeline = Gst.Pipeline.new("my-pipeline") 221 | #+END_SRC 222 | 223 | 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 [[http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gstreamer/html/GstBin.html#GstBin.notes][=Gst.State.PLAYING=]] and there should be multimedia coming out of it. 224 | 225 | ** Pipeline Audio Player 226 | 227 | The first example here starts with the audio player from section [[sec:playbin]] 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=. 228 | 229 | #+BEGIN_SRC sh :eval no 230 | gst-launch-1.0 filesrc location=file.mp3 ! mad ! audioconvert ! alsasink 231 | #+END_SRC 232 | 233 | Conceptually this pipeline looks like: 234 | 235 | #+header: :file (by-backend (latex "pipeline-block.pdf") (t "pipeline-block.svg")) 236 | #+BEGIN_SRC ditaa 237 | 238 | Example Gst.Pipeline 239 | 240 | /---------------------------------------------------------------------\ 241 | | +---------+ +-------+ +--------------+ +----------+ | 242 | | | | | | | | | | | 243 | file.mp3--->-| filesrc +-->--+ mad +-->--+ audioconvert +-->--+ alsasink +---->-Audio Output 244 | | | | | | | | | | | 245 | | +---------+ +-------+ +--------------+ +----------+ | 246 | \---------------------------------------------------------------------/ 247 | #+END_SRC 248 | 249 | #+ATTR_HTML: :width 100% 250 | #+RESULTS: 251 | [[file:pipeline-block.svg]] 252 | 253 | Done in Python this would look like =pipeline-example.py=: 254 | 255 | #+HTML: pipeline-example.py 256 | #+INCLUDE: pipeline-example.py src python 257 | 258 | ** Adding Video to the Pipeline 259 | 260 | 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. 261 | 262 | #+header: :file (by-backend (latex "pipeline-branch-block.pdf") (t "pipeline-branch-block.svg")) 263 | #+BEGIN_SRC ditaa 264 | Gst.Pipeline 265 | +----------------------------------------------------------------------------------------------------------+ 266 | | | 267 | | +-------+ +-------+ +----------------+ +---------------+ | 268 | | | | | | | | | | | 269 | | +->-+ queue +->-+ mad +->-+ audioconvert +->-+ autoaudiosink +--------->-Audio Output 270 | | +---------+ +-----------+ | | | | | | | | | | 271 | | | | | +->-+ +-------+ +-------+ +----------------+ +---------------+ | 272 | file.mpg--->-| filesrc |->-| mpegdemux | | 273 | | | | | +->-+ +-------+ +----------+ +------------------+ +---------------+ | 274 | | +---------+ +-----------+ | | | | | | | | | | 275 | | +->-+ queue +->-+ mpeg2dec +->-+ ffmpegcolorspace +->-+ autovideosink +---->-Video Output 276 | | | | | | | | | | | 277 | | +-------+ +----------+ +------------------+ +---------------+ | 278 | | | 279 | +----------------------------------------------------------------------------------------------------------+ 280 | #+END_SRC 281 | 282 | #+ATTR_HTML: :width 100% 283 | #+RESULTS: 284 | [[file:pipeline-branch-block.png]] 285 | 286 | 287 | #+HTML: pipeline-branch-example.py 288 | #+INCLUDE: pipeline-branch-example.py src python 289 | 290 | The elements in a pipeline connects to each other with pads and that's what the next chapter will tell you more about. 291 | 292 | * Src, sink, pad ... oh my! 293 | <> 294 | 295 | 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: 296 | 297 | 298 | #+header: :file (by-backend (latex "sink-src-block.pdf") (t "sink-src-block.svg")) 299 | #+BEGIN_SRC ditaa 300 | mad element 301 | +---------------------------------------------------------+ 302 | | | 303 | | /------------\ +----------------+ /-----------\ | 304 | | : : | | : : | 305 | ->---+ pad (sink) +-->--+ internal stuff +-->--+ pad (src) +-->- 306 | | : : | | : : | 307 | | \------------/ +----------------+ \-----------/ | 308 | | | 309 | +---------------------------------------------------------+ 310 | #+END_SRC 311 | 312 | #+ATTR_HTML: :width 100% 313 | #+RESULTS: 314 | [[file:sink-src-block.png]] 315 | 316 | And as always if you want to know more about highlevel elements gst-inspect is your friend: 317 | 318 | #+BEGIN_SRC sh :eval no 319 | gst-inspect-1.0 mad 320 | #+END_SRC 321 | 322 | In particular, the inheritance diagram shows that =mad= is an element: 323 | 324 | #+BEGIN_EXAMPLE 325 | GObject 326 | +----GInitiallyUnowned 327 | +----GstObject 328 | +----GstElement 329 | +----GstAudioDecoder 330 | +----GstMad 331 | #+END_EXAMPLE 332 | 333 | 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: 334 | 335 | #+BEGIN_SRC python :eval no 336 | mp3_pipeline = Gst.parse_launch("filesrc name=source ! mad name=decoder ! " \ 337 | "audioconvert name=conv ! alsasink name=sink") 338 | #+END_SRC 339 | 340 | The micro-language used in this function call is that of the =gst-launch= command line program. 341 | 342 | 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 {{{gstclass(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 [[http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gstreamer/html/GstBin.html][=Gst.Bin=]] can't link to other objects if you don't link a [[http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gstreamer/html/GstGhostPad.html][=Gst.GhostPad=]] to an element inside the bin. The =playbin-example-video.py= example in section [[sec:playbin]] should look something like this: 343 | 344 | #+header: :file (by-backend (latex "playbin-pads-block.pdf") (t "playbin-pads-block.svg")) 345 | #+BEGIN_SRC ditaa 346 | 347 | Gst.Bin 348 | /---------------------------------------------------------------------------- - - 349 | | 350 | | timeoverlay 351 | | +----------------------------------------------------------+ 352 | | /----------\ | /------------\ +---------------------+ /-----------\ | 353 | | : | | : | | | : | | 354 | ->---+ ghostpad |->---+ pad (sink) |->-| internal stuff here |->-+ pad (src) +-->-- 355 | | : | | : | | | : | | 356 | | \----------/ | \------------/ +---------------------+ \-----------/ | 357 | | +----------------------------------------------------------+ 358 | | 359 | \---------------------------------------------------------------------------- - - 360 | 361 | #+END_SRC 362 | 363 | #+ATTR_HTML: :width 100% 364 | #+RESULTS: 365 | [[file:playbin-pads-block.svg]] 366 | 367 | And the ghostpad above should be created as type "sink"!!! 368 | 369 | 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 [[http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-base-plugins/html/gst-plugins-base-plugins-oggdemux.html][=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. 370 | 371 | #+HTML: dynamic-ghostpad-example.py 372 | #+INCLUDE: dynamic-ghostpad-example.py src python 373 | 374 | Now after reading through these four chapters you could need a break. Happy hacking and stay tuned for more interesting chapters to come. 375 | 376 | * Seeking 377 | <> 378 | 379 | Seeking in GStreamer is done with the [[http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gstreamer/html/GstElement.html#gst-element-seek][=seek()=]] and [[http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gstreamer/html/GstElement.html#gst-element-seek-simple][=seek_simple()=]] methods of {{{gstclass(Element)}}}. 380 | 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 [[http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gstreamer/html/gstreamer-GstInfo.html#GST-TIME-FORMAT:CAPS][=TIME= value]] (of {{{gstenum(Format)}}} enum) format constant which will, as you may guess, request a time seek. We will also use the [[http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gstreamer/html/GstElement.html#gst-element-query-duration][=query_duration()=]] and [[http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gstreamer/html/GstElement.html#gst-element-query-position][=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. 381 | 382 | 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. 383 | 384 | #+HTML: seeking-example.py 385 | #+INCLUDE: seeking-example.py src python 386 | 387 | * Capabilities 388 | <> 389 | 390 | Capabilities, {{{gststruct(Caps)}}}, is a container where you may store information that you may pass on to a {{{gstclass(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: 391 | 392 | #+BEGIN_SRC sh :eval no 393 | gst-launch-1.0 videotestsrc ! video/x-raw, width=320, height=240 ! \ 394 | xvimagesink 395 | #+END_SRC 396 | 397 | But if you try to switch out the [[http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-base-plugins/html/gst-plugins-base-plugins-xvimagesink.html][=xvimagesink=]] for an [[http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-base-plugins/html/gst-plugins-base-plugins-ximagesink.html][=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. 398 | 399 | #+BEGIN_SRC sh :eval no 400 | gst-launch-1.0 videotestsrc ! video/x-raw, width=320, height=240 ! \ 401 | videoconvert ! ximagesink 402 | #+END_SRC 403 | 404 | And as =ximagesink= does not support hardware scaling you have to throw in a =videoscale= element too if you want software scaling. 405 | 406 | #+BEGIN_SRC sh :eval no 407 | gst-launch-1.0 videotestsrc ! video/x-raw, width=320, height=240 ! \ 408 | videoscale ! videoconvert ! ximagesink 409 | #+END_SRC 410 | 411 | To put the above examples in code you have to put the caps in a capsfilter element. 412 | 413 | #+HTML: capabilities-example.py 414 | #+INCLUDE: capabilities-example.py src python 415 | 416 | 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. 417 | 418 | #+HTML: capabilities-resolution-example.py 419 | #+INCLUDE: capabilities-resolution-example.py src python 420 | 421 | In the next example we will use the =playbin= from section [[subsec:playbinvideo]] 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. 422 | 423 | #+HTML: capabilities-playbin-example.py 424 | #+INCLUDE: capabilities-playbin-example.py src python 425 | 426 | * Videomixer 427 | <> 428 | 429 | The =videomixer= element makes it possible to mix different video streams together. Here is a CLI example: 430 | 431 | #+BEGIN_SRC sh :eval no 432 | gst-launch-1.0 filesrc location=tvlogo.png ! pngdec ! alphacolor ! \ 433 | videoconvert ! videobox border-alpha=0 alpha=0.5 top=-20 left=-200 ! \ 434 | videomixer name=mix ! videoconvert ! autovideosink videotestsrc ! \ 435 | video/x-raw, width=320, height=240 ! mix. 436 | #+END_SRC 437 | 438 | Fixme: this fails with: 439 | 440 | #+BEGIN_EXAMPLE 441 | Setting pipeline to PAUSED ... 442 | Pipeline is PREROLLING ... 443 | ERROR: from element /GstPipeline:pipeline0/GstFileSrc:filesrc0: Internal data flow error. 444 | Additional debug info: 445 | gstbasesrc.c(2865): gst_base_src_loop (): /GstPipeline:pipeline0/GstFileSrc:filesrc0: 446 | streaming task paused, reason not-negotiated (-4) 447 | ERROR: pipeline doesn't want to preroll. 448 | Setting pipeline to NULL ... 449 | Freeing pipeline ... 450 | #+END_EXAMPLE 451 | 452 | You have to make a [[./tvlogo.png][=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. 453 | 454 | In the next example we take the now working Mpeg2-Player from section [[sec:pipeline]] and add the elements shown above. 455 | 456 | #+HTML: videomixer-example.py 457 | #+INCLUDE: videomixer-example.py src python 458 | 459 | Fixme: This fails with: 460 | 461 | #+BEGIN_EXAMPLE 462 | Error: Internal data flow error. gstbasesrc.c(2865): gst_base_src_loop (): /GstPipeline:player/GstFileSrc:png-source: 463 | streaming task paused, reason not-negotiated (-4) 464 | #+END_EXAMPLE 465 | 466 | * Webcam Viewer 467 | <> 468 | 469 | Remember to peal the tape off your web cam lens before testing this. 470 | 471 | #+HTML: webcam-example.py 472 | #+INCLUDE: webcam-example.py src python 473 | 474 | 475 | 476 | -------------------------------------------------------------------------------- /pygst-tutorial.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brettviren/pygst-tutorial-org/a9c543f1e2b26e5036cede51e868f7e7ef4a64c6/pygst-tutorial.pdf -------------------------------------------------------------------------------- /seeking-example.py: -------------------------------------------------------------------------------- 1 | 2 | #!/usr/bin/env python 3 | 4 | import os, threading, time 5 | import gi 6 | gi.require_version("Gst", "1.0") 7 | from gi.repository import Gst, GObject, Gtk, Gdk 8 | 9 | class GTK_Main: 10 | 11 | def __init__(self): 12 | window = Gtk.Window(Gtk.WindowType.TOPLEVEL) 13 | window.set_title("Vorbis-Player") 14 | window.set_default_size(500, -1) 15 | window.connect("destroy", Gtk.main_quit, "WM destroy") 16 | vbox = Gtk.VBox() 17 | window.add(vbox) 18 | self.entry = Gtk.Entry() 19 | vbox.pack_start(self.entry, False, False, 0) 20 | hbox = Gtk.HBox() 21 | vbox.add(hbox) 22 | buttonbox = Gtk.HButtonBox() 23 | hbox.pack_start(buttonbox, False, False, 0) 24 | rewind_button = Gtk.Button("Rewind") 25 | rewind_button.connect("clicked", self.rewind_callback) 26 | buttonbox.add(rewind_button) 27 | self.button = Gtk.Button("Start") 28 | self.button.connect("clicked", self.start_stop) 29 | buttonbox.add(self.button) 30 | forward_button = Gtk.Button("Forward") 31 | forward_button.connect("clicked", self.forward_callback) 32 | buttonbox.add(forward_button) 33 | self.time_label = Gtk.Label() 34 | self.time_label.set_text("00:00 / 00:00") 35 | hbox.add(self.time_label) 36 | window.show_all() 37 | 38 | self.player = Gst.Pipeline.new("player") 39 | source = Gst.ElementFactory.make("filesrc", "file-source") 40 | demuxer = Gst.ElementFactory.make("oggdemux", "demuxer") 41 | demuxer.connect("pad-added", self.demuxer_callback) 42 | self.audio_decoder = Gst.ElementFactory.make("vorbisdec", "vorbis-decoder") 43 | audioconv = Gst.ElementFactory.make("audioconvert", "converter") 44 | audiosink = Gst.ElementFactory.make("autoaudiosink", "audio-output") 45 | 46 | for ele in [source, demuxer, self.audio_decoder, audioconv, audiosink]: 47 | self.player.add(ele) 48 | source.link(demuxer) 49 | self.audio_decoder.link(audioconv) 50 | audioconv.link(audiosink) 51 | 52 | bus = self.player.get_bus() 53 | bus.add_signal_watch() 54 | bus.connect("message", self.on_message) 55 | 56 | def start_stop(self, w): 57 | if self.button.get_label() == "Start": 58 | filepath = self.entry.get_text().strip() 59 | if os.path.isfile(filepath): 60 | filepath = os.path.realpath(filepath) 61 | self.button.set_label("Stop") 62 | self.player.get_by_name("file-source").set_property("location", filepath) 63 | self.player.set_state(Gst.State.PLAYING) 64 | self.play_thread_id = thread.start_new_thread(self.play_thread, ()) 65 | else: 66 | self.play_thread_id = None 67 | self.player.set_state(Gst.State.NULL) 68 | self.button.set_label("Start") 69 | self.time_label.set_text("00:00 / 00:00") 70 | 71 | def play_thread(self): 72 | play_thread_id = self.play_thread_id 73 | Gdk.threads_enter() 74 | self.time_label.set_text("00:00 / 00:00") 75 | Gdk.threads_leave() 76 | 77 | while play_thread_id == self.play_thread_id: 78 | try: 79 | time.sleep(0.2) 80 | dur_int = self.player.query_duration(Gst.Format.TIME, None)[0] 81 | if dur_int == -1: 82 | continue 83 | dur_str = self.convert_ns(dur_int) 84 | Gdk.threads_enter() 85 | self.time_label.set_text("00:00 / " + dur_str) 86 | Gdk.threads_leave() 87 | break 88 | except: 89 | pass 90 | 91 | time.sleep(0.2) 92 | while play_thread_id == self.play_thread_id: 93 | pos_int = self.player.query_position(Gst.Format.TIME, None)[0] 94 | pos_str = self.convert_ns(pos_int) 95 | if play_thread_id == self.play_thread_id: 96 | Gdk.threads_enter() 97 | self.time_label.set_text(pos_str + " / " + dur_str) 98 | Gdk.threads_leave() 99 | time.sleep(1) 100 | 101 | def on_message(self, bus, message): 102 | t = message.type 103 | if t == Gst.MessageType.EOS: 104 | self.play_thread_id = None 105 | self.player.set_state(Gst.State.NULL) 106 | self.button.set_label("Start") 107 | self.time_label.set_text("00:00 / 00:00") 108 | elif t == Gst.MessageType.ERROR: 109 | err, debug = message.parse_error() 110 | print("Error: %s" % err, debug) 111 | self.play_thread_id = None 112 | self.player.set_state(Gst.State.NULL) 113 | self.button.set_label("Start") 114 | self.time_label.set_text("00:00 / 00:00") 115 | 116 | def demuxer_callback(self, demuxer, pad): 117 | adec_pad = self.audio_decoder.get_static_pad("sink") 118 | pad.link(adec_pad) 119 | 120 | def rewind_callback(self, w): 121 | rc, pos_int = self.player.query_position(Gst.Format.TIME) 122 | seek_ns = pos_int - 10 * 1000000000 123 | if seek_ns < 0: 124 | seek_ns = 0 125 | print('Backward: %d ns -> %d ns' % (pos_int, seek_ns)) 126 | self.player.seek_simple(Gst.Format.TIME, Gst.SeekFlags.FLUSH, seek_ns) 127 | 128 | def forward_callback(self, w): 129 | rc, pos_int = self.player.query_position(Gst.Format.TIME) 130 | seek_ns = pos_int + 10 * 1000000000 131 | print('Forward: %d ns -> %d ns' % (pos_int, seek_ns)) 132 | self.player.seek_simple(Gst.Format.TIME, Gst.SeekFlags.FLUSH, seek_ns) 133 | 134 | def convert_ns(self, t): 135 | # This method was submitted by Sam Mason. 136 | # It's much shorter than the original one. 137 | s,ns = divmod(t, 1000000000) 138 | m,s = divmod(s, 60) 139 | 140 | if m < 60: 141 | return "%02i:%02i" %(m,s) 142 | else: 143 | h,m = divmod(m, 60) 144 | return "%i:%02i:%02i" %(h,m,s) 145 | 146 | GObject.threads_init() 147 | Gst.init(None) 148 | GTK_Main() 149 | Gtk.main() 150 | -------------------------------------------------------------------------------- /setup.org: -------------------------------------------------------------------------------- 1 | #+HTML_HEAD: 2 | #+HTML_HEAD: 3 | #+HTML_HEAD: 4 | #+OPTIONS: TOC:2 5 | #+OPTIONS: html-link-use-abs-url:nil html-postamble:auto 6 | #+OPTIONS: html-preamble:t html-scripts:t html-style:t 7 | #+OPTIONS: html5-fancy:t tex:t 8 | #+CREATOR: Emacs 24.3.1 (Org mode 8.2.5a) 9 | #+HTML_CONTAINER: div 10 | #+HTML_DOCTYPE: xhtml-strict 11 | #+HTML_HEAD: 12 | #+HTML_HEAD_EXTRA: 13 | #+HTML_LINK_HOME: https://github.com/brettviren/pygst-tutorial-org 14 | #+HTML_LINK_UP: . 15 | #+HTML_MATHJAX: 16 | #+INFOJS_OPT: toc:t ltoc:above view:showall mouse:#cccccc buttons:nil 17 | #+LaTeX_HEADER: \usepackage[margin=0.75in]{geometry} 18 | 19 | #+MACRO: gstfunc [[https://lazka.github.io/pgi-docs/#Gst-1.0/functions.html#Gst.$1][=Gst.$1()=]] 20 | #+MACRO: gstclass [[https://lazka.github.io/pgi-docs/#Gst-1.0/classes/$1.html][=Gst.$1=]] 21 | #+MACRO: gststruct [[https://lazka.github.io/pgi-docs/#Gst-1.0/structs/$1.html][=Gst.$1=]] 22 | #+MACRO: gstflag [[https://lazka.github.io/pgi-docs/#Gst-1.0/flags.html#Gst.$1][=Gst.$1=]] 23 | #+MACRO: gstenum [[https://lazka.github.io/pgi-docs/#Gst-1.0/enums.html#Gst.$1][=Gst.$1=]] 24 | #+MACRO: gstconst [[https://lazka.github.io/pgi-docs/#Gst-1.0/constants.html#Gst.$1][=Gst.$1=]] 25 | 26 | * COMMENT setup 27 | 28 | Need to exec =C-c C-c= this block 29 | 30 | 31 | #+begin_src emacs-lisp :results silent 32 | ; From the org-mode mailing list, I think: 33 | (setq org-babel-latex-htlatex "htlatex") 34 | (defmacro by-backend (&rest body) 35 | `(case (if (boundp 'backend) (org-export-backend-name backend) nil) ,@body)) 36 | ; http://joat-programmer.blogspot.com/2013/07/org-mode-version-8-and-pdf-export-with.html 37 | (require 'ox-latex) 38 | (add-to-list 'org-latex-packages-alist '("" "minted")) 39 | (setq org-latex-listings 'minted) 40 | (setq org-latex-pdf-process 41 | '( 42 | "pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f" 43 | "pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f" 44 | )) 45 | #+END_SRC 46 | -------------------------------------------------------------------------------- /sink-src-block.svg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brettviren/pygst-tutorial-org/a9c543f1e2b26e5036cede51e868f7e7ef4a64c6/sink-src-block.svg -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | .shell-line:before { 2 | content: "$ "; 3 | } 4 | .src-sh { 5 | color: red; 6 | background: #faf8f0; 7 | font-family: "Courier New", Courier, monospace, sans-serif; 8 | text-align: left; 9 | } 10 | .example { 11 | color: black; 12 | background: #daf8f0; 13 | font-family: "Courier New", Courier, monospace, sans-serif; 14 | text-align: left; 15 | } 16 | -------------------------------------------------------------------------------- /tvlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brettviren/pygst-tutorial-org/a9c543f1e2b26e5036cede51e868f7e7ef4a64c6/tvlogo.png -------------------------------------------------------------------------------- /videomixer-example.py: -------------------------------------------------------------------------------- 1 | 2 | #!/usr/bin/env python 3 | 4 | import os 5 | import gi 6 | gi.require_version("Gst", "1.0") 7 | from gi.repository import Gst, GObject, Gtk 8 | 9 | class GTK_Main: 10 | def __init__(self): 11 | window = Gtk.Window(Gtk.WindowType.TOPLEVEL) 12 | window.set_title("Mpeg2-Player") 13 | window.set_default_size(500, 400) 14 | window.connect("destroy", Gtk.main_quit, "WM destroy") 15 | vbox = Gtk.VBox() 16 | window.add(vbox) 17 | hbox = Gtk.HBox() 18 | vbox.pack_start(hbox, False, False, 0) 19 | self.entry = Gtk.Entry() 20 | hbox.add(self.entry) 21 | self.button = Gtk.Button("Start") 22 | hbox.pack_start(self.button, False, False, 0) 23 | self.button.connect("clicked", self.start_stop) 24 | self.movie_window = Gtk.DrawingArea() 25 | vbox.add(self.movie_window) 26 | window.show_all() 27 | self.player = Gst.Pipeline.new("player") 28 | source = Gst.ElementFactory.make("filesrc", "file-source") 29 | demuxer = Gst.ElementFactory.make("mpegpsdemux", "demuxer") 30 | demuxer.connect("pad-added", self.demuxer_callback) 31 | self.video_decoder = Gst.ElementFactory.make("mpeg2dec", "video-decoder") 32 | png_decoder = Gst.ElementFactory.make("pngdec", "png-decoder") 33 | png_source = Gst.ElementFactory.make("filesrc", "png-source") 34 | png_source.set_property("location", os.path.realpath("tvlogo.png")) 35 | mixer = Gst.ElementFactory.make("videomixer", "mixer") 36 | self.audio_decoder = Gst.ElementFactory.make("mad", "audio-decoder") 37 | audioconv = Gst.ElementFactory.make("audioconvert", "converter") 38 | audiosink = Gst.ElementFactory.make("autoaudiosink", "audio-output") 39 | videosink = Gst.ElementFactory.make("autovideosink", "video-output") 40 | self.queuea = Gst.ElementFactory.make("queue", "queuea") 41 | self.queuev = Gst.ElementFactory.make("queue", "queuev") 42 | ffmpeg1 = Gst.ElementFactory.make("videoconvert", "ffmpeg1") 43 | ffmpeg2 = Gst.ElementFactory.make("videoconvert", "ffmpeg2") 44 | ffmpeg3 = Gst.ElementFactory.make("videoconvert", "ffmpeg3") 45 | videobox = Gst.ElementFactory.make("videobox", "videobox") 46 | alphacolor = Gst.ElementFactory.make("alphacolor", "alphacolor") 47 | for ele in (source, demuxer, self.video_decoder, png_decoder, png_source, mixer, 48 | self.audio_decoder, audioconv, audiosink, videosink, self.queuea, 49 | self.queuev, ffmpeg1, ffmpeg2, ffmpeg3, videobox, alphacolor): 50 | self.player.add(ele) 51 | 52 | source.link(demuxer) 53 | 54 | self.queuev.link(self.video_decoder) 55 | self.video_decoder.link(ffmpeg1) 56 | ffmpeg1.link(mixer) 57 | mixer.link(ffmpeg2) 58 | ffmpeg2.link(videosink) 59 | 60 | png_source.link(png_decoder) 61 | png_decoder.link(alphacolor) 62 | alphacolor.link(ffmpeg3) 63 | ffmpeg3.link(videobox) 64 | videobox.link(mixer) 65 | 66 | self.queuea.link(self.audio_decoder) 67 | self.audio_decoder.link(audioconv) 68 | audioconv.link(audiosink) 69 | 70 | bus = self.player.get_bus() 71 | bus.add_signal_watch() 72 | bus.enable_sync_message_emission() 73 | bus.connect("message", self.on_message) 74 | bus.connect("sync-message::element", self.on_sync_message) 75 | videobox.set_property("border-alpha", 0) 76 | videobox.set_property("alpha", 0.5) 77 | videobox.set_property("left", -10) 78 | videobox.set_property("top", -10) 79 | 80 | def start_stop(self, w): 81 | if self.button.get_label() == "Start": 82 | filepath = self.entry.get_text().strip() 83 | if os.path.isfile(filepath): 84 | filepath = os.path.realpath(filepath) 85 | self.button.set_label("Stop") 86 | self.player.get_by_name("file-source").set_property("location", filepath) 87 | self.player.set_state(Gst.State.PLAYING) 88 | else: 89 | self.player.set_state(Gst.State.NULL) 90 | self.button.set_label("Start") 91 | 92 | def on_message(self, bus, message): 93 | t = message.type 94 | if t == Gst.MessageType.EOS: 95 | self.player.set_state(Gst.State.NULL) 96 | self.button.set_label("Start") 97 | elif t == Gst.MessageType.ERROR: 98 | err, debug = message.parse_error() 99 | print("Error: %s" % err, debug) 100 | self.player.set_state(Gst.State.NULL) 101 | self.button.set_label("Start") 102 | 103 | def on_sync_message(self, bus, message): 104 | if message.structure is None: 105 | return 106 | message_name = message.structure.get_name() 107 | if message_name == "prepare-xwindow-id": 108 | imagesink = message.src 109 | imagesink.set_property("force-aspect-ratio", True) 110 | imagesink.set_xwindow_id(self.movie_window.window.xid) 111 | 112 | def demuxer_callback(self, demuxer, pad): 113 | tpl_property = pad.get_property("template") 114 | tpl_name = tpl_property.name_template 115 | print 'demuxer_callback: template name template: "%s"' % tpl_name 116 | if tpl_name == "video_%02d": 117 | queuev_pad = self.queuev.get_pad("sink") 118 | pad.link(queuev_pad) 119 | elif tpl_name == "audio_%02d": 120 | queuea_pad = self.queuea.get_pad("sink") 121 | pad.link(queuea_pad) 122 | 123 | GObject.threads_init() 124 | Gst.init(None) 125 | GTK_Main() 126 | Gtk.main() 127 | -------------------------------------------------------------------------------- /videotestsrc-frame.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brettviren/pygst-tutorial-org/a9c543f1e2b26e5036cede51e868f7e7ef4a64c6/videotestsrc-frame.jpg -------------------------------------------------------------------------------- /webcam-example.py: -------------------------------------------------------------------------------- 1 | 2 | #!/usr/bin/env python 3 | 4 | import sys, os 5 | import gi 6 | gi.require_version('Gst', '1.0') 7 | from gi.repository import Gst, GObject, Gtk 8 | 9 | class GTK_Main: 10 | def __init__(self): 11 | window = Gtk.Window(Gtk.WindowType.TOPLEVEL) 12 | window.set_title("Webcam-Viewer") 13 | window.set_default_size(500, 400) 14 | window.connect("destroy", Gtk.main_quit, "WM destroy") 15 | vbox = Gtk.VBox() 16 | window.add(vbox) 17 | self.movie_window = Gtk.DrawingArea() 18 | vbox.add(self.movie_window) 19 | hbox = Gtk.HBox() 20 | vbox.pack_start(hbox, False, False, 0) 21 | hbox.set_border_width(10) 22 | hbox.pack_start(Gtk.Label(), False, False, 0) 23 | self.button = Gtk.Button("Start") 24 | self.button.connect("clicked", self.start_stop) 25 | hbox.pack_start(self.button, False, False, 0) 26 | self.button2 = Gtk.Button("Quit") 27 | self.button2.connect("clicked", self.exit) 28 | hbox.pack_start(self.button2, False, False, 0) 29 | hbox.add(Gtk.Label()) 30 | window.show_all() 31 | 32 | # Set up the gstreamer pipeline 33 | self.player = Gst.parse_launch ("v4l2src ! autovideosink") 34 | bus = self.player.get_bus() 35 | bus.add_signal_watch() 36 | bus.enable_sync_message_emission() 37 | bus.connect("message", self.on_message) 38 | bus.connect("sync-message::element", self.on_sync_message) 39 | 40 | def start_stop(self, w): 41 | if self.button.get_label() == "Start": 42 | self.button.set_label("Stop") 43 | self.player.set_state(Gst.State.PLAYING) 44 | else: 45 | self.player.set_state(Gst.State.NULL) 46 | self.button.set_label("Start") 47 | 48 | def exit(self, widget, data=None): 49 | Gtk.main_quit() 50 | 51 | def on_message(self, bus, message): 52 | t = message.type 53 | if t == Gst.MessageType.EOS: 54 | self.player.set_state(Gst.State.NULL) 55 | self.button.set_label("Start") 56 | elif t == Gst.MessageType.ERROR: 57 | err, debug = message.parse_error() 58 | print("Error: %s" % err, debug) 59 | self.player.set_state(Gst.State.NULL) 60 | self.button.set_label("Start") 61 | 62 | def on_sync_message(self, bus, message): 63 | struct = message.get_structure() 64 | if not struct: 65 | return 66 | message_name = struct.get_name() 67 | if message_name == "prepare-xwindow-id": 68 | # Assign the viewport 69 | imagesink = message.src 70 | imagesink.set_property("force-aspect-ratio", True) 71 | imagesink.set_xwindow_id(self.movie_window.window.xid) 72 | 73 | Gst.init(None) 74 | GTK_Main() 75 | GObject.threads_init() 76 | Gtk.main() 77 | --------------------------------------------------------------------------------