├── .gitignore ├── basic-tutorial-1.py ├── basic-tutorial-2-ex-vertigo.py ├── basic-tutorial-2.py ├── basic-tutorial-3-ex-video.py ├── basic-tutorial-3.py ├── basic-tutorial-4.py ├── basic-tutorial-5.py ├── basic-tutorial-6.py ├── basic-tutorial-7.py └── helper.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.webm 3 | 4 | -------------------------------------------------------------------------------- /basic-tutorial-1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # http://docs.gstreamer.com/pages/viewpage.action?pageId=327735 4 | 5 | import gi 6 | gi.require_version('Gst', '1.0') 7 | from gi.repository import Gst, GObject, GLib 8 | 9 | pipeline = None 10 | bus = None 11 | message = None 12 | 13 | # initialize GStreamer 14 | Gst.init(None) 15 | 16 | # build the pipeline 17 | pipeline = Gst.parse_launch( 18 | "playbin uri=http://docs.gstreamer.com/media/sintel_trailer-480p.webm" 19 | ) 20 | 21 | # start playing 22 | pipeline.set_state(Gst.State.PLAYING) 23 | 24 | # wait until EOS or error 25 | bus = pipeline.get_bus() 26 | msg = bus.timed_pop_filtered( 27 | Gst.CLOCK_TIME_NONE, 28 | Gst.MessageType.ERROR | Gst.MessageType.EOS 29 | ) 30 | 31 | # free resources 32 | pipeline.set_state(Gst.State.NULL) 33 | -------------------------------------------------------------------------------- /basic-tutorial-2-ex-vertigo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # http://docs.gstreamer.com/display/GstSDK/Basic+tutorial+2%3A+GStreamer+concepts 4 | 5 | import sys 6 | import gi 7 | gi.require_version('Gst', '1.0') 8 | from gi.repository import Gst, GLib, GObject 9 | 10 | # initialize GStreamer 11 | Gst.init(None) 12 | 13 | # create the elements 14 | source = Gst.ElementFactory.make("videotestsrc", "source") 15 | filter_vertigo = Gst.ElementFactory.make("vertigotv", "vertigo-filter") 16 | videoconvert = Gst.ElementFactory.make("videoconvert", "video-convert") 17 | sink = Gst.ElementFactory.make("autovideosink", "sink") 18 | 19 | # create the empty pipeline 20 | pipeline = Gst.Pipeline.new("test-pipeline") 21 | 22 | if not pipeline or not source or not filter_vertigo or not videoconvert or not sink: 23 | print("ERROR: Not all elements could be created") 24 | sys.exit(1) 25 | 26 | # build the pipeline 27 | pipeline.add(source, filter_vertigo, videoconvert, sink) 28 | if not source.link(filter_vertigo): 29 | print("ERROR: Could not link source to filter-vertigo") 30 | sys.exit(1) 31 | 32 | if not filter_vertigo.link(videoconvert): 33 | print("ERROR: Could not link filter-vertigo to videoconvert") 34 | sys.exit(1) 35 | 36 | if not videoconvert.link(sink): 37 | print("ERROR: Could not link videoconvert to sink") 38 | sys.exit(1) 39 | 40 | # modify the source's properties 41 | source.set_property("pattern", 0) 42 | 43 | # start playing 44 | ret = pipeline.set_state(Gst.State.PLAYING) 45 | if ret == Gst.StateChangeReturn.FAILURE: 46 | print("ERROR: Unable to set the pipeline to the playing state") 47 | 48 | # wait for EOS or error 49 | bus = pipeline.get_bus() 50 | msg = bus.timed_pop_filtered( 51 | Gst.CLOCK_TIME_NONE, 52 | Gst.MessageType.ERROR | Gst.MessageType.EOS 53 | ) 54 | 55 | if msg: 56 | t = msg.type 57 | if t == Gst.MessageType.ERROR: 58 | err, dbg = msg.parse_error() 59 | print("ERROR:", msg.src.get_name(), ":", err.message) 60 | if dbg: 61 | print("debugging info:", dbg) 62 | elif t == Gst.MessageType.EOS: 63 | print("End-Of-Stream reached") 64 | else: 65 | # this should not happen. we only asked for ERROR and EOS 66 | print("ERROR: Unexpected message received.") 67 | 68 | pipeline.set_state(Gst.State.NULL) 69 | -------------------------------------------------------------------------------- /basic-tutorial-2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # http://docs.gstreamer.com/display/GstSDK/Basic+tutorial+2%3A+GStreamer+concepts 4 | 5 | import sys 6 | import gi 7 | gi.require_version('Gst', '1.0') 8 | from gi.repository import Gst, GLib, GObject 9 | 10 | # initialize GStreamer 11 | Gst.init(None) 12 | 13 | # create the elements 14 | source = Gst.ElementFactory.make("videotestsrc", "source") 15 | sink = Gst.ElementFactory.make("autovideosink", "sink") 16 | 17 | # create the empty pipeline 18 | pipeline = Gst.Pipeline.new("test-pipeline") 19 | 20 | if not pipeline or not source or not sink: 21 | print("ERROR: Not all elements could be created") 22 | sys.exit(1) 23 | 24 | # build the pipeline 25 | pipeline.add(source, sink) 26 | if not source.link(sink): 27 | print("ERROR: Could not link source to sink") 28 | sys.exit(1) 29 | 30 | # modify the source's properties 31 | source.set_property("pattern", 0) 32 | 33 | # start playing 34 | ret = pipeline.set_state(Gst.State.PLAYING) 35 | if ret == Gst.StateChangeReturn.FAILURE: 36 | print("ERROR: Unable to set the pipeline to the playing state") 37 | 38 | # wait for EOS or error 39 | bus = pipeline.get_bus() 40 | msg = bus.timed_pop_filtered( 41 | Gst.CLOCK_TIME_NONE, 42 | Gst.MessageType.ERROR | Gst.MessageType.EOS 43 | ) 44 | 45 | if msg: 46 | t = msg.type 47 | if t == Gst.MessageType.ERROR: 48 | err, dbg = msg.parse_error() 49 | print("ERROR:", msg.src.get_name(), " ", err.message) 50 | if dbg: 51 | print("debugging info:", dbg) 52 | elif t == Gst.MessageType.EOS: 53 | print("End-Of-Stream reached") 54 | else: 55 | # this should not happen. we only asked for ERROR and EOS 56 | print("ERROR: Unexpected message received.") 57 | 58 | pipeline.set_state(Gst.State.NULL) 59 | -------------------------------------------------------------------------------- /basic-tutorial-3-ex-video.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import gi 5 | gi.require_version('Gst', '1.0') 6 | from gi.repository import Gst 7 | 8 | # http://docs.gstreamer.com/display/GstSDK/Basic+tutorial+3%3A+Dynamic+pipelines 9 | 10 | 11 | class Player(object): 12 | 13 | def __init__(self): 14 | # initialize GStreamer 15 | Gst.init(None) 16 | 17 | # create the elements 18 | self.source = Gst.ElementFactory.make("uridecodebin", "source") 19 | self.audio_convert = Gst.ElementFactory.make( 20 | "audioconvert", "audioconvert") 21 | self.audio_sink = Gst.ElementFactory.make("autoaudiosink", "audiosink") 22 | self.video_convert = Gst.ElementFactory.make( 23 | "videoconvert", "videoconvert") 24 | self.video_sink = Gst.ElementFactory.make("autovideosink", "videosink") 25 | 26 | # create empty pipeline 27 | self.pipeline = Gst.Pipeline.new("test-pipeline") 28 | 29 | if (not self.pipeline or not self.source or not self.audio_convert 30 | or not self.audio_sink or not self.video_convert or not self.video_sink): 31 | print("ERROR: Could not create all elements") 32 | sys.exit(1) 33 | 34 | # build the pipeline. we are NOT linking the source at this point. 35 | # will do it later 36 | self.pipeline.add(self.source, self.audio_convert, self.audio_sink, 37 | self.video_convert, self.video_sink) 38 | if not self.audio_convert.link(self.audio_sink): 39 | print("ERROR: Could not link 'audioconvert' to 'audiosink'") 40 | sys.exit(1) 41 | 42 | if not self.video_convert.link(self.video_sink): 43 | print("ERROR: Could not link 'videoconvert' to 'videosink'") 44 | sys.exit(1) 45 | 46 | # set the URI to play 47 | self.source.set_property( 48 | "uri", "http://docs.gstreamer.com/media/sintel_trailer-480p.webm") 49 | 50 | # connect to the pad-added signal 51 | self.source.connect("pad-added", self.on_pad_added) 52 | 53 | # start playing 54 | ret = self.pipeline.set_state(Gst.State.PLAYING) 55 | if ret == Gst.StateChangeReturn.FAILURE: 56 | print("ERROR: Unable to set the pipeline to the playing state") 57 | sys.exit(1) 58 | 59 | # listen to the bus 60 | bus = self.pipeline.get_bus() 61 | terminate = False 62 | while True: 63 | msg = bus.timed_pop_filtered( 64 | Gst.CLOCK_TIME_NONE, 65 | Gst.MessageType.STATE_CHANGED | Gst.MessageType.EOS | Gst.MessageType.ERROR) 66 | 67 | if not msg: 68 | continue 69 | 70 | t = msg.type 71 | if t == Gst.MessageType.ERROR: 72 | err, dbg = msg.parse_error() 73 | print("ERROR:", msg.src.get_name(), " ", err.message) 74 | if dbg: 75 | print("debugging info:", dbg) 76 | terminate = True 77 | elif t == Gst.MessageType.EOS: 78 | print("End-Of-Stream reached") 79 | terminate = True 80 | elif t == Gst.MessageType.STATE_CHANGED: 81 | # we are only interested in STATE_CHANGED messages from 82 | # the pipeline 83 | if msg.src == self.pipeline: 84 | old_state, new_state, pending_state = msg.parse_state_changed() 85 | print("Pipeline state changed from {0:s} to {1:s}".format( 86 | Gst.Element.state_get_name(old_state), 87 | Gst.Element.state_get_name(new_state))) 88 | else: 89 | # should not get here 90 | print("ERROR: Unexpected message received") 91 | break 92 | 93 | if terminate: 94 | break 95 | 96 | self.pipeline.set_state(Gst.State.NULL) 97 | 98 | # handler for the pad-added signal 99 | def on_pad_added(self, src, new_pad): 100 | print( 101 | "Received new pad '{0:s}' from '{1:s}'".format( 102 | new_pad.get_name(), 103 | src.get_name())) 104 | 105 | # check the new pad's type 106 | new_pad_caps = new_pad.get_current_caps() 107 | new_pad_struct = new_pad_caps.get_structure(0) 108 | new_pad_type = new_pad_struct.get_name() 109 | 110 | if new_pad_type.startswith("audio/x-raw"): 111 | sink_pad = self.audio_convert.get_static_pad("sink") 112 | elif new_pad_type.startswith("video/x-raw"): 113 | sink_pad = self.video_convert.get_static_pad("sink") 114 | else: 115 | print( 116 | "It has type '{0:s}' which is not raw audio/video. Ignoring.".format(new_pad_type)) 117 | return 118 | 119 | # if our converter is already linked, we have nothing to do here 120 | if(sink_pad.is_linked()): 121 | print("We are already linked. Ignoring.") 122 | return 123 | 124 | # attempt the link 125 | ret = new_pad.link(sink_pad) 126 | if not ret == Gst.PadLinkReturn.OK: 127 | print("Type is '{0:s}}' but link failed".format(new_pad_type)) 128 | else: 129 | print("Link succeeded (type '{0:s}')".format(new_pad_type)) 130 | 131 | return 132 | 133 | if __name__ == '__main__': 134 | p = Player() 135 | -------------------------------------------------------------------------------- /basic-tutorial-3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import gi 5 | gi.require_version('Gst', '1.0') 6 | from gi.repository import Gst 7 | 8 | # http://docs.gstreamer.com/display/GstSDK/Basic+tutorial+3%3A+Dynamic+pipelines 9 | 10 | 11 | class Player(object): 12 | 13 | def __init__(self): 14 | # initialize GStreamer 15 | Gst.init(None) 16 | 17 | # create the elements 18 | self.source = Gst.ElementFactory.make("uridecodebin", "source") 19 | self.convert = Gst.ElementFactory.make("audioconvert", "convert") 20 | self.sink = Gst.ElementFactory.make("autoaudiosink", "sink") 21 | 22 | # create empty pipeline 23 | self.pipeline = Gst.Pipeline.new("test-pipeline") 24 | 25 | if not self.pipeline or not self.source or not self.convert or not self.sink: 26 | print("ERROR: Could not create all elements") 27 | sys.exit(1) 28 | 29 | # build the pipeline. we are NOT linking the source at this point. 30 | # will do it later 31 | self.pipeline.add(self.source, self.convert, self.sink) 32 | if not self.convert.link(self.sink): 33 | print("ERROR: Could not link 'convert' to 'sink'") 34 | sys.exit(1) 35 | 36 | # set the URI to play 37 | self.source.set_property( 38 | "uri", "http://docs.gstreamer.com/media/sintel_trailer-480p.webm") 39 | 40 | # connect to the pad-added signal 41 | self.source.connect("pad-added", self.on_pad_added) 42 | 43 | # start playing 44 | ret = self.pipeline.set_state(Gst.State.PLAYING) 45 | if ret == Gst.StateChangeReturn.FAILURE: 46 | print("ERROR: Unable to set the pipeline to the playing state") 47 | sys.exit(1) 48 | 49 | # listen to the bus 50 | bus = self.pipeline.get_bus() 51 | terminate = False 52 | while True: 53 | msg = bus.timed_pop_filtered( 54 | Gst.CLOCK_TIME_NONE, 55 | Gst.MessageType.STATE_CHANGED | Gst.MessageType.EOS | Gst.MessageType.ERROR) 56 | 57 | if not msg: 58 | continue 59 | 60 | t = msg.type 61 | if t == Gst.MessageType.ERROR: 62 | err, dbg = msg.parse_error() 63 | print("ERROR:", msg.src.get_name(), " ", err.message) 64 | if dbg: 65 | print("debugging info:", dbg) 66 | terminate = True 67 | elif t == Gst.MessageType.EOS: 68 | print("End-Of-Stream reached") 69 | terminate = True 70 | elif t == Gst.MessageType.STATE_CHANGED: 71 | # we are only interested in STATE_CHANGED messages from 72 | # the pipeline 73 | if msg.src == self.pipeline: 74 | old_state, new_state, pending_state = msg.parse_state_changed() 75 | print("Pipeline state changed from {0:s} to {1:s}".format( 76 | Gst.Element.state_get_name(old_state), 77 | Gst.Element.state_get_name(new_state))) 78 | else: 79 | # should not get here 80 | print("ERROR: Unexpected message received") 81 | break 82 | 83 | if terminate: 84 | break 85 | 86 | self.pipeline.set_state(Gst.State.NULL) 87 | 88 | # handler for the pad-added signal 89 | def on_pad_added(self, src, new_pad): 90 | sink_pad = self.convert.get_static_pad("sink") 91 | print( 92 | "Received new pad '{0:s}' from '{1:s}'".format( 93 | new_pad.get_name(), 94 | src.get_name())) 95 | 96 | # if our converter is already linked, we have nothing to do here 97 | if(sink_pad.is_linked()): 98 | print("We are already linked. Ignoring.") 99 | return 100 | 101 | # check the new pad's type 102 | new_pad_caps = new_pad.get_current_caps() 103 | new_pad_struct = new_pad_caps.get_structure(0) 104 | new_pad_type = new_pad_struct.get_name() 105 | 106 | if not new_pad_type.startswith("audio/x-raw"): 107 | print("It has type '{0:s}' which is not raw audio. Ignoring.".format( 108 | new_pad_type)) 109 | return 110 | 111 | # attempt the link 112 | ret = new_pad.link(sink_pad) 113 | if not ret == Gst.PadLinkReturn.OK: 114 | print("Type is '{0:s}}' but link failed".format(new_pad_type)) 115 | else: 116 | print("Link succeeded (type '{0:s}')".format(new_pad_type)) 117 | 118 | return 119 | 120 | if __name__ == '__main__': 121 | p = Player() 122 | -------------------------------------------------------------------------------- /basic-tutorial-4.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import gi 5 | gi.require_version('Gst', '1.0') 6 | from gi.repository import Gst 7 | 8 | from helper import format_ns 9 | 10 | # http://docs.gstreamer.com/display/GstSDK/Basic+tutorial+4%3A+Time+management 11 | 12 | 13 | class Player(object): 14 | 15 | def __init__(self): 16 | # are we playing? 17 | self.playing = False 18 | # should we terminate execution? 19 | self.terminate = False 20 | # is seeking enabled for this media? 21 | self.seek_enabled = False 22 | # have we performed the seek already? 23 | self.seek_done = False 24 | # media duration (ns) 25 | self.duration = Gst.CLOCK_TIME_NONE 26 | 27 | # initialize GStreamer 28 | Gst.init(None) 29 | 30 | # create the elements 31 | self.playbin = Gst.ElementFactory.make("playbin", "playbin") 32 | if not self.playbin: 33 | print("ERROR: Could not create 'playbin' element") 34 | sys.exit(1) 35 | 36 | # set the uri to play 37 | self.playbin.set_property( 38 | "uri", "http://docs.gstreamer.com/media/sintel_trailer-480p.webm") 39 | 40 | def play(self): 41 | # dont start again if we are already playing 42 | if self.playing: 43 | return 44 | 45 | # start playing 46 | ret = self.playbin.set_state(Gst.State.PLAYING) 47 | if ret == Gst.StateChangeReturn.FAILURE: 48 | print("ERROR: Unable to set the pipeline to the playing state") 49 | sys.exit(1) 50 | 51 | try: 52 | # listen to the bus 53 | bus = self.playbin.get_bus() 54 | while True: 55 | msg = bus.timed_pop_filtered( 56 | 100 * Gst.MSECOND, 57 | (Gst.MessageType.STATE_CHANGED | Gst.MessageType.ERROR 58 | | Gst.MessageType.EOS | Gst.MessageType.DURATION_CHANGED) 59 | ) 60 | 61 | # parse message 62 | if msg: 63 | self.handle_message(msg) 64 | else: 65 | # we got no message. this means the timeout expired 66 | if self.playing: 67 | current = -1 68 | # query the current position of the stream 69 | ret, current = self.playbin.query_position( 70 | Gst.Format.TIME) 71 | if not ret: 72 | print("ERROR: Could not query current position") 73 | 74 | # if we don't know it yet, query the stream duration 75 | if self.duration == Gst.CLOCK_TIME_NONE: 76 | (ret, self.duration) = self.playbin.query_duration( 77 | Gst.Format.TIME) 78 | if not ret: 79 | print("ERROR: Could not query stream duration") 80 | 81 | # print current position and total duration 82 | print( 83 | "Position {0} / {1}".format(format_ns(current), format_ns(self.duration))) 84 | 85 | # if seeking is enabled, we have not done it yet and the time is right, 86 | # seek 87 | if self.seek_enabled and not self.seek_done and current > 10 * Gst.SECOND: 88 | print("Reached 10s, performing seek...") 89 | self.playbin.seek_simple( 90 | Gst.Format.TIME, Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT, 30 * Gst.SECOND) 91 | 92 | self.seek_done = True 93 | if self.terminate: 94 | break 95 | finally: 96 | self.playbin.set_state(Gst.State.NULL) 97 | 98 | def handle_message(self, msg): 99 | t = msg.type 100 | if t == Gst.MessageType.ERROR: 101 | err, dbg = msg.parse_error() 102 | print("ERROR:", msg.src.get_name(), ":", err) 103 | if dbg: 104 | print("Debug info:", dbg) 105 | self.terminate = True 106 | elif t == Gst.MessageType.EOS: 107 | print("End-Of-Stream reached") 108 | self.terminate = True 109 | elif t == Gst.MessageType.DURATION_CHANGED: 110 | # the duration has changed, invalidate the current one 111 | self.duration = Gst.CLOCK_TIME_NONE 112 | elif t == Gst.MessageType.STATE_CHANGED: 113 | old_state, new_state, pending_state = msg.parse_state_changed() 114 | if msg.src == self.playbin: 115 | print("Pipeline state changed from '{0:s}' to '{1:s}'".format( 116 | Gst.Element.state_get_name(old_state), 117 | Gst.Element.state_get_name(new_state))) 118 | 119 | # remember whether we are in the playing state or not 120 | self.playing = new_state == Gst.State.PLAYING 121 | 122 | if self.playing: 123 | # we just moved to the playing state 124 | query = Gst.Query.new_seeking(Gst.Format.TIME) 125 | if self.playbin.query(query): 126 | fmt, self.seek_enabled, start, end = query.parse_seeking() 127 | 128 | if self.seek_enabled: 129 | print( 130 | "Seeking is ENABLED (from {0} to {1})".format( 131 | format_ns(start), format_ns(end))) 132 | else: 133 | print("Seeking is DISABLED for this stream") 134 | else: 135 | print("ERROR: Seeking query failed") 136 | 137 | else: 138 | print("ERROR: Unexpected message received") 139 | 140 | if __name__ == '__main__': 141 | p = Player() 142 | p.play() 143 | -------------------------------------------------------------------------------- /basic-tutorial-5.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import gi 5 | gi.require_version('Gst', '1.0') 6 | gi.require_version('Gtk', '3.0') 7 | gi.require_version('GdkX11', '3.0') 8 | gi.require_version('GstVideo', '1.0') 9 | from gi.repository import Gst, Gtk, GLib, GdkX11, GstVideo 10 | 11 | # http://docs.gstreamer.com/display/GstSDK/Basic+tutorial+5%3A+GUI+toolkit+integration 12 | 13 | 14 | class Player(object): 15 | 16 | def __init__(self): 17 | # initialize GTK 18 | Gtk.init(sys.argv) 19 | 20 | # initialize GStreamer 21 | Gst.init(sys.argv) 22 | 23 | self.state = Gst.State.NULL 24 | self.duration = Gst.CLOCK_TIME_NONE 25 | self.playbin = Gst.ElementFactory.make("playbin", "playbin") 26 | if not self.playbin: 27 | print("ERROR: Could not create playbin.") 28 | sys.exit(1) 29 | 30 | # set up URI 31 | self.playbin.set_property( 32 | "uri", "http://docs.gstreamer.com/media/sintel_trailer-480p.webm") 33 | 34 | # connect to interesting signals in playbin 35 | self.playbin.connect("video-tags-changed", self.on_tags_changed) 36 | self.playbin.connect("audio-tags-changed", self.on_tags_changed) 37 | self.playbin.connect("text-tags-changed", self.on_tags_changed) 38 | 39 | # create the GUI 40 | self.build_ui() 41 | 42 | # instruct the bus to emit signals for each received message 43 | # and connect to the interesting signals 44 | bus = self.playbin.get_bus() 45 | bus.add_signal_watch() 46 | bus.connect("message::error", self.on_error) 47 | bus.connect("message::eos", self.on_eos) 48 | bus.connect("message::state-changed", self.on_state_changed) 49 | bus.connect("message::application", self.on_application_message) 50 | 51 | # set the playbin to PLAYING (start playback), register refresh callback 52 | # and start the GTK main loop 53 | def start(self): 54 | # start playing 55 | ret = self.playbin.set_state(Gst.State.PLAYING) 56 | if ret == Gst.StateChangeReturn.FAILURE: 57 | print("ERROR: Unable to set the pipeline to the playing state") 58 | sys.exit(1) 59 | 60 | # register a function that GLib will call every second 61 | GLib.timeout_add_seconds(1, self.refresh_ui) 62 | 63 | # start the GTK main loop. we will not regain control until 64 | # Gtk.main_quit() is called 65 | Gtk.main() 66 | 67 | # free resources 68 | self.cleanup() 69 | 70 | # set the playbin state to NULL and remove the reference to it 71 | def cleanup(self): 72 | if self.playbin: 73 | self.playbin.set_state(Gst.State.NULL) 74 | self.playbin = None 75 | 76 | def build_ui(self): 77 | main_window = Gtk.Window.new(Gtk.WindowType.TOPLEVEL) 78 | main_window.connect("delete-event", self.on_delete_event) 79 | 80 | video_window = Gtk.DrawingArea.new() 81 | video_window.set_double_buffered(False) 82 | video_window.connect("realize", self.on_realize) 83 | video_window.connect("draw", self.on_draw) 84 | 85 | play_button = Gtk.Button.new_from_stock(Gtk.STOCK_MEDIA_PLAY) 86 | play_button.connect("clicked", self.on_play) 87 | 88 | pause_button = Gtk.Button.new_from_stock(Gtk.STOCK_MEDIA_PAUSE) 89 | pause_button.connect("clicked", self.on_pause) 90 | 91 | stop_button = Gtk.Button.new_from_stock(Gtk.STOCK_MEDIA_STOP) 92 | stop_button.connect("clicked", self.on_stop) 93 | 94 | self.slider = Gtk.HScale.new_with_range(0, 100, 1) 95 | self.slider.set_draw_value(False) 96 | self.slider_update_signal_id = self.slider.connect( 97 | "value-changed", self.on_slider_changed) 98 | 99 | self.streams_list = Gtk.TextView.new() 100 | self.streams_list.set_editable(False) 101 | 102 | controls = Gtk.HBox.new(False, 0) 103 | controls.pack_start(play_button, False, False, 2) 104 | controls.pack_start(pause_button, False, False, 2) 105 | controls.pack_start(stop_button, False, False, 2) 106 | controls.pack_start(self.slider, True, True, 0) 107 | 108 | main_hbox = Gtk.HBox.new(False, 0) 109 | main_hbox.pack_start(video_window, True, True, 0) 110 | main_hbox.pack_start(self.streams_list, False, False, 2) 111 | 112 | main_box = Gtk.VBox.new(False, 0) 113 | main_box.pack_start(main_hbox, True, True, 0) 114 | main_box.pack_start(controls, False, False, 0) 115 | 116 | main_window.add(main_box) 117 | main_window.set_default_size(640, 480) 118 | main_window.show_all() 119 | 120 | # this function is called when the GUI toolkit creates the physical window 121 | # that will hold the video 122 | # at this point we can retrieve its handler and pass it to GStreamer 123 | # through the XOverlay interface 124 | def on_realize(self, widget): 125 | window = widget.get_window() 126 | window_handle = window.get_xid() 127 | 128 | # pass it to playbin, which implements XOverlay and will forward 129 | # it to the video sink 130 | self.playbin.set_window_handle(window_handle) 131 | # self.playbin.set_xwindow_id(window_handle) 132 | 133 | # this function is called when the PLAY button is clicked 134 | def on_play(self, button): 135 | self.playbin.set_state(Gst.State.PLAYING) 136 | pass 137 | 138 | # this function is called when the PAUSE button is clicked 139 | def on_pause(self, button): 140 | self.playbin.set_state(Gst.State.PAUSED) 141 | pass 142 | 143 | # this function is called when the STOP button is clicked 144 | def on_stop(self, button): 145 | self.playbin.set_state(Gst.State.READY) 146 | pass 147 | 148 | # this function is called when the main window is closed 149 | def on_delete_event(self, widget, event): 150 | self.on_stop(None) 151 | Gtk.main_quit() 152 | 153 | # this function is called every time the video window needs to be 154 | # redrawn. GStreamer takes care of this in the PAUSED and PLAYING states. 155 | # in the other states we simply draw a black rectangle to avoid 156 | # any garbage showing up 157 | def on_draw(self, widget, cr): 158 | if self.state < Gst.State.PAUSED: 159 | allocation = widget.get_allocation() 160 | 161 | cr.set_source_rgb(0, 0, 0) 162 | cr.rectangle(0, 0, allocation.width, allocation.height) 163 | cr.fill() 164 | 165 | return False 166 | 167 | # this function is called when the slider changes its position. 168 | # we perform a seek to the new position here 169 | def on_slider_changed(self, range): 170 | value = self.slider.get_value() 171 | self.playbin.seek_simple(Gst.Format.TIME, 172 | Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT, 173 | value * Gst.SECOND) 174 | 175 | # this function is called periodically to refresh the GUI 176 | def refresh_ui(self): 177 | current = -1 178 | 179 | # we do not want to update anything unless we are in the PAUSED 180 | # or PLAYING states 181 | if self.state < Gst.State.PAUSED: 182 | return True 183 | 184 | # if we don't know it yet, query the stream duration 185 | if self.duration == Gst.CLOCK_TIME_NONE: 186 | ret, self.duration = self.playbin.query_duration(Gst.Format.TIME) 187 | if not ret: 188 | print("ERROR: Could not query current duration") 189 | else: 190 | # set the range of the slider to the clip duration (in seconds) 191 | self.slider.set_range(0, self.duration / Gst.SECOND) 192 | 193 | ret, current = self.playbin.query_position(Gst.Format.TIME) 194 | if ret: 195 | # block the "value-changed" signal, so the on_slider_changed 196 | # callback is not called (which would trigger a seek the user 197 | # has not requested) 198 | self.slider.handler_block(self.slider_update_signal_id) 199 | 200 | # set the position of the slider to the current pipeline position 201 | # (in seconds) 202 | self.slider.set_value(current / Gst.SECOND) 203 | 204 | # enable the signal again 205 | self.slider.handler_unblock(self.slider_update_signal_id) 206 | 207 | return True 208 | 209 | # this function is called when new metadata is discovered in the stream 210 | def on_tags_changed(self, playbin, stream): 211 | # we are possibly in a GStreamer working thread, so we notify 212 | # the main thread of this event through a message in the bus 213 | self.playbin.post_message( 214 | Gst.Message.new_application( 215 | self.playbin, 216 | Gst.Structure.new_empty("tags-changed"))) 217 | 218 | # this function is called when an error message is posted on the bus 219 | def on_error(self, bus, msg): 220 | err, dbg = msg.parse_error() 221 | print("ERROR:", msg.src.get_name(), ":", err.message) 222 | if dbg: 223 | print("Debug info:", dbg) 224 | 225 | # this function is called when an End-Of-Stream message is posted on the bus 226 | # we just set the pipeline to READY (which stops playback) 227 | def on_eos(self, bus, msg): 228 | print("End-Of-Stream reached") 229 | self.playbin.set_state(Gst.State.READY) 230 | 231 | # this function is called when the pipeline changes states. 232 | # we use it to keep track of the current state 233 | def on_state_changed(self, bus, msg): 234 | old, new, pending = msg.parse_state_changed() 235 | if not msg.src == self.playbin: 236 | # not from the playbin, ignore 237 | return 238 | 239 | self.state = new 240 | print("State changed from {0} to {1}".format( 241 | Gst.Element.state_get_name(old), Gst.Element.state_get_name(new))) 242 | 243 | if old == Gst.State.READY and new == Gst.State.PAUSED: 244 | # for extra responsiveness we refresh the GUI as soons as 245 | # we reach the PAUSED state 246 | self.refresh_ui() 247 | 248 | # extract metadata from all the streams and write it to the text widget 249 | # in the GUI 250 | def analyze_streams(self): 251 | # clear current contents of the widget 252 | buffer = self.streams_list.get_buffer() 253 | buffer.set_text("") 254 | 255 | # read some properties 256 | nr_video = self.playbin.get_property("n-video") 257 | nr_audio = self.playbin.get_property("n-audio") 258 | nr_text = self.playbin.get_property("n-text") 259 | 260 | for i in range(nr_video): 261 | tags = None 262 | # retrieve the stream's video tags 263 | tags = self.playbin.emit("get-video-tags", i) 264 | if tags: 265 | buffer.insert_at_cursor("video stream {0}\n".format(i)) 266 | _, str = tags.get_string(Gst.TAG_VIDEO_CODEC) 267 | buffer.insert_at_cursor( 268 | " codec: {0}\n".format( 269 | str or "unknown")) 270 | 271 | for i in range(nr_audio): 272 | tags = None 273 | # retrieve the stream's audio tags 274 | tags = self.playbin.emit("get-audio-tags", i) 275 | if tags: 276 | buffer.insert_at_cursor("\naudio stream {0}\n".format(i)) 277 | ret, str = tags.get_string(Gst.TAG_AUDIO_CODEC) 278 | if ret: 279 | buffer.insert_at_cursor( 280 | " codec: {0}\n".format( 281 | str or "unknown")) 282 | 283 | ret, str = tags.get_string(Gst.TAG_LANGUAGE_CODE) 284 | if ret: 285 | buffer.insert_at_cursor( 286 | " language: {0}\n".format( 287 | str or "unknown")) 288 | 289 | ret, str = tags.get_uint(Gst.TAG_BITRATE) 290 | if ret: 291 | buffer.insert_at_cursor( 292 | " bitrate: {0}\n".format( 293 | str or "unknown")) 294 | 295 | for i in range(nr_text): 296 | tags = None 297 | # retrieve the stream's subtitle tags 298 | tags = self.playbin.emit("get-text-tags", i) 299 | if tags: 300 | buffer.insert_at_cursor("\nsubtitle stream {0}\n".format(i)) 301 | ret, str = tags.get_string(Gst.TAG_LANGUAGE_CODE) 302 | if ret: 303 | buffer.insert_at_cursor( 304 | " language: {0}\n".format( 305 | str or "unknown")) 306 | 307 | # this function is called when an "application" message is posted on the bus 308 | # here we retrieve the message posted by the on_tags_changed callback 309 | def on_application_message(self, bus, msg): 310 | if msg.get_structure().get_name() == "tags-changed": 311 | # if the message is the "tags-changed", update the stream info in 312 | # the GUI 313 | self.analyze_streams() 314 | 315 | if __name__ == '__main__': 316 | p = Player() 317 | p.start() 318 | -------------------------------------------------------------------------------- /basic-tutorial-6.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import gi 5 | gi.require_version('Gst', '1.0') 6 | from gi.repository import Gst, GLib 7 | 8 | # http://docs.gstreamer.com/display/GstSDK/Basic+tutorial+6%3A+Media+formats+and+Pad+Capabilities 9 | 10 | # the functions below print the capabilities in a human-friendly format 11 | 12 | 13 | def print_field(field, value, pfx): 14 | str = Gst.value_serialize(value) 15 | print("{0:s} {1:15s}: {2:s}".format( 16 | pfx, GLib.quark_to_string(field), str)) 17 | return True 18 | 19 | 20 | def print_caps(caps, pfx): 21 | if not caps: 22 | return 23 | 24 | if caps.is_any(): 25 | print("{0:s}ANY".format(pfx)) 26 | return 27 | 28 | if caps.is_empty(): 29 | print("{0:s}EMPTY".format(pfx)) 30 | return 31 | 32 | for i in range(caps.get_size()): 33 | structure = caps.get_structure(i) 34 | print("{0:s}{1:s}".format(pfx, structure.get_name())) 35 | structure.foreach(print_field, pfx) 36 | 37 | # prints information about a pad template (including its capabilities) 38 | 39 | 40 | def print_pad_templates_information(factory): 41 | print("Pad templates for {0:s}".format(factory.get_name())) 42 | if factory.get_num_pad_templates() == 0: 43 | print(" none") 44 | return 45 | 46 | pads = factory.get_static_pad_templates() 47 | for pad in pads: 48 | padtemplate = pad.get() 49 | 50 | if pad.direction == Gst.PadDirection.SRC: 51 | print(" SRC template:", padtemplate.name_template) 52 | elif pad.direction == Gst.PadDirection.SINK: 53 | print(" SINK template:", padtemplate.name_template) 54 | else: 55 | print(" UNKNOWN template:", padtemplate.name_template) 56 | 57 | if padtemplate.presence == Gst.PadPresence.ALWAYS: 58 | print(" Availability: Always") 59 | elif padtemplate.presence == Gst.PadPresence.SOMETIMES: 60 | print(" Availability: Sometimes") 61 | elif padtemplate.presence == Gst.PadPresence.REQUEST: 62 | print(" Availability: On request") 63 | else: 64 | print(" Availability: UNKNOWN") 65 | 66 | if padtemplate.get_caps(): 67 | print(" Capabilities:") 68 | print_caps(padtemplate.get_caps(), " ") 69 | 70 | print("") 71 | 72 | # shows the current capabilities of the requested pad in the given element 73 | 74 | 75 | def print_pad_capabilities(element, pad_name): 76 | # retrieve pad 77 | pad = element.get_static_pad(pad_name) 78 | if not pad: 79 | print("ERROR: Could not retrieve pad '{0:s}'".format(pad_name)) 80 | return 81 | 82 | # retrieve negotiated caps (or acceptable caps if negotiation is not 83 | # yet finished) 84 | caps = pad.get_current_caps() 85 | if not caps: 86 | caps = pad.get_allowed_caps() 87 | 88 | # print 89 | print("Caps for the {0:s} pad:".format(pad_name)) 90 | print_caps(caps, " ") 91 | 92 | 93 | def main(): 94 | # initialize GStreamer 95 | Gst.init(sys.argv) 96 | 97 | # create the element factories 98 | source_factory = Gst.ElementFactory.find("audiotestsrc") 99 | sink_factory = Gst.ElementFactory.find("autoaudiosink") 100 | if not source_factory or not sink_factory: 101 | print("ERROR: Not all element factories could be created") 102 | return -1 103 | 104 | # print information about the pad templates of these factories 105 | print_pad_templates_information(source_factory) 106 | print_pad_templates_information(sink_factory) 107 | 108 | # ask the factories to instantiate the actual elements 109 | source = source_factory.create("source") 110 | sink = sink_factory.create("sink") 111 | 112 | # create the empty pipeline 113 | pipeline = Gst.Pipeline.new("test-pipeline") 114 | if not pipeline or not source or not sink: 115 | print("ERROR: Not all elements could be created") 116 | return -1 117 | 118 | # build the pipeline 119 | pipeline.add(source, sink) 120 | if not source.link(sink): 121 | print("ERROR: Could not link source to sink") 122 | return -1 123 | 124 | # print initial negotiated caps (in NULL state) 125 | print("In NULL state:") 126 | print_pad_capabilities(sink, "sink") 127 | 128 | # start playing 129 | ret = pipeline.set_state(Gst.State.PLAYING) 130 | if ret == Gst.StateChangeReturn.FAILURE: 131 | print("ERROR: Unable to set the pipeline to the playing state") 132 | 133 | # wait until error, EOS or State-Change 134 | terminate = False 135 | bus = pipeline.get_bus() 136 | while True: 137 | try: 138 | msg = bus.timed_pop_filtered( 139 | 0.5 * Gst.SECOND, 140 | Gst.MessageType.ERROR | Gst.MessageType.EOS | Gst.MessageType.STATE_CHANGED) 141 | 142 | if msg: 143 | t = msg.type 144 | if t == Gst.MessageType.ERROR: 145 | err, dbg = msg.parse_error() 146 | print("ERROR:", msg.src.get_name(), ":", err.message) 147 | if dbg: 148 | print("Debug information:", dbg) 149 | terminate = True 150 | elif t == Gst.MessageType.EOS: 151 | print("End-Of-Stream reached") 152 | terminate = True 153 | elif t == Gst.MessageType.STATE_CHANGED: 154 | # we are only interested in state-changed messages from the 155 | # pieline 156 | if msg.src == pipeline: 157 | old, new, pending = msg.parse_state_changed() 158 | print( 159 | "Pipeline state changed from", 160 | Gst.Element.state_get_name(old), 161 | "to", 162 | Gst.Element.state_get_name(new), 163 | ":") 164 | 165 | # print the current capabilities of the sink 166 | print_pad_capabilities(sink, "sink") 167 | else: 168 | # should not get here 169 | print("ERROR: unexpected message received") 170 | except KeyboardInterrupt: 171 | terminate = True 172 | 173 | if terminate: 174 | break 175 | 176 | pipeline.set_state(Gst.State.NULL) 177 | 178 | if __name__ == '__main__': 179 | main() 180 | -------------------------------------------------------------------------------- /basic-tutorial-7.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import sys 4 | import gi 5 | gi.require_version('Gst', '1.0') 6 | from gi.repository import Gst 7 | 8 | # http://docs.gstreamer.com/display/GstSDK/Basic+tutorial+7%3A+Multithreading+and+Pad+Availability 9 | 10 | 11 | def main(): 12 | # initialize GStreamer 13 | Gst.init(sys.argv) 14 | 15 | # create the elements 16 | audio_source = Gst.ElementFactory.make("audiotestsrc", "audio_source") 17 | tee = Gst.ElementFactory.make("tee", "tee") 18 | audio_queue = Gst.ElementFactory.make("queue", "audio_queue") 19 | audio_convert = Gst.ElementFactory.make("audioconvert", "audio_convert") 20 | audio_resample = Gst.ElementFactory.make("audioresample", "audio_resample") 21 | audio_sink = Gst.ElementFactory.make("autoaudiosink", "audio_sink") 22 | video_queue = Gst.ElementFactory.make("queue", "video_queue") 23 | visual = Gst.ElementFactory.make("wavescope", "visual") 24 | video_convert = Gst.ElementFactory.make("videoconvert", "video_convert") 25 | video_sink = Gst.ElementFactory.make("autovideosink", "video_sink") 26 | 27 | # create the empty pipeline 28 | pipeline = Gst.Pipeline.new("test-pipeline") 29 | 30 | if (not pipeline or not audio_source or not tee or not audio_queue 31 | or not audio_convert or not audio_resample or not audio_sink 32 | or not video_queue or not visual or not video_convert 33 | or not video_sink): 34 | print("ERROR: Not all elements could be created.") 35 | sys.exit(1) 36 | 37 | # configure elements 38 | audio_source.set_property("freq", 215.0) 39 | visual.set_property("shader", 0) 40 | visual.set_property("style", 1) 41 | 42 | # link all elements that can be automatically linked because they have 43 | # always pads 44 | pipeline.add(audio_source, tee, audio_queue, audio_convert, audio_resample, 45 | audio_sink, video_queue, visual, video_convert, video_sink) 46 | 47 | ret = audio_source.link(tee) 48 | ret = ret and audio_queue.link(audio_convert) 49 | ret = ret and audio_convert.link(audio_resample) 50 | ret = ret and audio_resample.link(audio_sink) 51 | ret = ret and video_queue.link(visual) 52 | ret = ret and visual.link(video_convert) 53 | ret = ret and video_convert.link(video_sink) 54 | 55 | if not ret: 56 | print("ERROR: Elements could not be linked") 57 | sys.exit(1) 58 | 59 | # manually link the tee, which has "Request" pads 60 | tee_src_pad_template = tee.get_pad_template("src_%u") 61 | tee_audio_pad = tee.request_pad(tee_src_pad_template, None, None) 62 | print( 63 | "Obtained request pad {0} for audio branch".format( 64 | tee_audio_pad.get_name())) 65 | audio_queue_pad = audio_queue.get_static_pad("sink") 66 | tee_video_pad = tee.request_pad(tee_src_pad_template, None, None) 67 | print( 68 | "Obtained request pad {0} for video branch".format( 69 | tee_video_pad.get_name())) 70 | video_queue_pad = video_queue.get_static_pad("sink") 71 | 72 | if (tee_audio_pad.link(audio_queue_pad) != Gst.PadLinkReturn.OK 73 | or tee_video_pad.link(video_queue_pad) != Gst.PadLinkReturn.OK): 74 | print("ERROR: Tee could not be linked") 75 | sys.exit(1) 76 | 77 | # one could use link() to link elements with"Reuest" pads automatically 78 | # instead of manually (see above), as it will internally 79 | # request the pads 80 | # tee.link(audio_queue) 81 | # tee.link(video_queue) 82 | 83 | # start playing 84 | pipeline.set_state(Gst.State.PLAYING) 85 | 86 | # wait until error or EOS 87 | terminate = False 88 | bus = pipeline.get_bus() 89 | while True: 90 | try: 91 | msg = bus.timed_pop_filtered( 92 | 0.5 * Gst.SECOND, 93 | Gst.MessageType.ERROR | Gst.MessageType.EOS) 94 | if msg: 95 | terminate = True 96 | except KeyboardInterrupt: 97 | terminate = True 98 | 99 | if terminate: 100 | break 101 | 102 | pipeline.set_state(Gst.State.NULL) 103 | 104 | if __name__ == '__main__': 105 | main() 106 | -------------------------------------------------------------------------------- /helper.py: -------------------------------------------------------------------------------- 1 | def format_ns(ns): 2 | s, ns = divmod(ns, 1000000000) 3 | m, s = divmod(s, 60) 4 | h, m = divmod(m, 60) 5 | 6 | return "%u:%02u:%02u.%09u" % (h, m, s, ns) 7 | --------------------------------------------------------------------------------