-
Notifications
You must be signed in to change notification settings - Fork 42
/
Copy pathbasic-tutorial-5.py
executable file
·317 lines (258 loc) · 11.8 KB
/
basic-tutorial-5.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
#!/usr/bin/env python3
import sys
import gi
gi.require_version('Gst', '1.0')
gi.require_version('Gtk', '3.0')
gi.require_version('GdkX11', '3.0')
gi.require_version('GstVideo', '1.0')
from gi.repository import Gst, Gtk, GLib, GdkX11, GstVideo
# http://docs.gstreamer.com/display/GstSDK/Basic+tutorial+5%3A+GUI+toolkit+integration
class Player(object):
def __init__(self):
# initialize GTK
Gtk.init(sys.argv)
# initialize GStreamer
Gst.init(sys.argv)
self.state = Gst.State.NULL
self.duration = Gst.CLOCK_TIME_NONE
self.playbin = Gst.ElementFactory.make("playbin", "playbin")
if not self.playbin:
print("ERROR: Could not create playbin.")
sys.exit(1)
# set up URI
self.playbin.set_property(
"uri", "http://docs.gstreamer.com/media/sintel_trailer-480p.webm")
# connect to interesting signals in playbin
self.playbin.connect("video-tags-changed", self.on_tags_changed)
self.playbin.connect("audio-tags-changed", self.on_tags_changed)
self.playbin.connect("text-tags-changed", self.on_tags_changed)
# create the GUI
self.build_ui()
# instruct the bus to emit signals for each received message
# and connect to the interesting signals
bus = self.playbin.get_bus()
bus.add_signal_watch()
bus.connect("message::error", self.on_error)
bus.connect("message::eos", self.on_eos)
bus.connect("message::state-changed", self.on_state_changed)
bus.connect("message::application", self.on_application_message)
# set the playbin to PLAYING (start playback), register refresh callback
# and start the GTK main loop
def start(self):
# start playing
ret = self.playbin.set_state(Gst.State.PLAYING)
if ret == Gst.StateChangeReturn.FAILURE:
print("ERROR: Unable to set the pipeline to the playing state")
sys.exit(1)
# register a function that GLib will call every second
GLib.timeout_add_seconds(1, self.refresh_ui)
# start the GTK main loop. we will not regain control until
# Gtk.main_quit() is called
Gtk.main()
# free resources
self.cleanup()
# set the playbin state to NULL and remove the reference to it
def cleanup(self):
if self.playbin:
self.playbin.set_state(Gst.State.NULL)
self.playbin = None
def build_ui(self):
main_window = Gtk.Window.new(Gtk.WindowType.TOPLEVEL)
main_window.connect("delete-event", self.on_delete_event)
video_window = Gtk.DrawingArea.new()
video_window.set_double_buffered(False)
video_window.connect("realize", self.on_realize)
video_window.connect("draw", self.on_draw)
play_button = Gtk.Button.new_from_stock(Gtk.STOCK_MEDIA_PLAY)
play_button.connect("clicked", self.on_play)
pause_button = Gtk.Button.new_from_stock(Gtk.STOCK_MEDIA_PAUSE)
pause_button.connect("clicked", self.on_pause)
stop_button = Gtk.Button.new_from_stock(Gtk.STOCK_MEDIA_STOP)
stop_button.connect("clicked", self.on_stop)
self.slider = Gtk.HScale.new_with_range(0, 100, 1)
self.slider.set_draw_value(False)
self.slider_update_signal_id = self.slider.connect(
"value-changed", self.on_slider_changed)
self.streams_list = Gtk.TextView.new()
self.streams_list.set_editable(False)
controls = Gtk.HBox.new(False, 0)
controls.pack_start(play_button, False, False, 2)
controls.pack_start(pause_button, False, False, 2)
controls.pack_start(stop_button, False, False, 2)
controls.pack_start(self.slider, True, True, 0)
main_hbox = Gtk.HBox.new(False, 0)
main_hbox.pack_start(video_window, True, True, 0)
main_hbox.pack_start(self.streams_list, False, False, 2)
main_box = Gtk.VBox.new(False, 0)
main_box.pack_start(main_hbox, True, True, 0)
main_box.pack_start(controls, False, False, 0)
main_window.add(main_box)
main_window.set_default_size(640, 480)
main_window.show_all()
# this function is called when the GUI toolkit creates the physical window
# that will hold the video
# at this point we can retrieve its handler and pass it to GStreamer
# through the XOverlay interface
def on_realize(self, widget):
window = widget.get_window()
window_handle = window.get_xid()
# pass it to playbin, which implements XOverlay and will forward
# it to the video sink
self.playbin.set_window_handle(window_handle)
# self.playbin.set_xwindow_id(window_handle)
# this function is called when the PLAY button is clicked
def on_play(self, button):
self.playbin.set_state(Gst.State.PLAYING)
pass
# this function is called when the PAUSE button is clicked
def on_pause(self, button):
self.playbin.set_state(Gst.State.PAUSED)
pass
# this function is called when the STOP button is clicked
def on_stop(self, button):
self.playbin.set_state(Gst.State.READY)
pass
# this function is called when the main window is closed
def on_delete_event(self, widget, event):
self.on_stop(None)
Gtk.main_quit()
# this function is called every time the video window needs to be
# redrawn. GStreamer takes care of this in the PAUSED and PLAYING states.
# in the other states we simply draw a black rectangle to avoid
# any garbage showing up
def on_draw(self, widget, cr):
if self.state < Gst.State.PAUSED:
allocation = widget.get_allocation()
cr.set_source_rgb(0, 0, 0)
cr.rectangle(0, 0, allocation.width, allocation.height)
cr.fill()
return False
# this function is called when the slider changes its position.
# we perform a seek to the new position here
def on_slider_changed(self, range):
value = self.slider.get_value()
self.playbin.seek_simple(Gst.Format.TIME,
Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT,
value * Gst.SECOND)
# this function is called periodically to refresh the GUI
def refresh_ui(self):
current = -1
# we do not want to update anything unless we are in the PAUSED
# or PLAYING states
if self.state < Gst.State.PAUSED:
return True
# if we don't know it yet, query the stream duration
if self.duration == Gst.CLOCK_TIME_NONE:
ret, self.duration = self.playbin.query_duration(Gst.Format.TIME)
if not ret:
print("ERROR: Could not query current duration")
else:
# set the range of the slider to the clip duration (in seconds)
self.slider.set_range(0, self.duration / Gst.SECOND)
ret, current = self.playbin.query_position(Gst.Format.TIME)
if ret:
# block the "value-changed" signal, so the on_slider_changed
# callback is not called (which would trigger a seek the user
# has not requested)
self.slider.handler_block(self.slider_update_signal_id)
# set the position of the slider to the current pipeline position
# (in seconds)
self.slider.set_value(current / Gst.SECOND)
# enable the signal again
self.slider.handler_unblock(self.slider_update_signal_id)
return True
# this function is called when new metadata is discovered in the stream
def on_tags_changed(self, playbin, stream):
# we are possibly in a GStreamer working thread, so we notify
# the main thread of this event through a message in the bus
self.playbin.post_message(
Gst.Message.new_application(
self.playbin,
Gst.Structure.new_empty("tags-changed")))
# this function is called when an error message is posted on the bus
def on_error(self, bus, msg):
err, dbg = msg.parse_error()
print("ERROR:", msg.src.get_name(), ":", err.message)
if dbg:
print("Debug info:", dbg)
# this function is called when an End-Of-Stream message is posted on the bus
# we just set the pipeline to READY (which stops playback)
def on_eos(self, bus, msg):
print("End-Of-Stream reached")
self.playbin.set_state(Gst.State.READY)
# this function is called when the pipeline changes states.
# we use it to keep track of the current state
def on_state_changed(self, bus, msg):
old, new, pending = msg.parse_state_changed()
if not msg.src == self.playbin:
# not from the playbin, ignore
return
self.state = new
print("State changed from {0} to {1}".format(
Gst.Element.state_get_name(old), Gst.Element.state_get_name(new)))
if old == Gst.State.READY and new == Gst.State.PAUSED:
# for extra responsiveness we refresh the GUI as soons as
# we reach the PAUSED state
self.refresh_ui()
# extract metadata from all the streams and write it to the text widget
# in the GUI
def analyze_streams(self):
# clear current contents of the widget
buffer = self.streams_list.get_buffer()
buffer.set_text("")
# read some properties
nr_video = self.playbin.get_property("n-video")
nr_audio = self.playbin.get_property("n-audio")
nr_text = self.playbin.get_property("n-text")
for i in range(nr_video):
tags = None
# retrieve the stream's video tags
tags = self.playbin.emit("get-video-tags", i)
if tags:
buffer.insert_at_cursor("video stream {0}\n".format(i))
_, str = tags.get_string(Gst.TAG_VIDEO_CODEC)
buffer.insert_at_cursor(
" codec: {0}\n".format(
str or "unknown"))
for i in range(nr_audio):
tags = None
# retrieve the stream's audio tags
tags = self.playbin.emit("get-audio-tags", i)
if tags:
buffer.insert_at_cursor("\naudio stream {0}\n".format(i))
ret, str = tags.get_string(Gst.TAG_AUDIO_CODEC)
if ret:
buffer.insert_at_cursor(
" codec: {0}\n".format(
str or "unknown"))
ret, str = tags.get_string(Gst.TAG_LANGUAGE_CODE)
if ret:
buffer.insert_at_cursor(
" language: {0}\n".format(
str or "unknown"))
ret, str = tags.get_uint(Gst.TAG_BITRATE)
if ret:
buffer.insert_at_cursor(
" bitrate: {0}\n".format(
str or "unknown"))
for i in range(nr_text):
tags = None
# retrieve the stream's subtitle tags
tags = self.playbin.emit("get-text-tags", i)
if tags:
buffer.insert_at_cursor("\nsubtitle stream {0}\n".format(i))
ret, str = tags.get_string(Gst.TAG_LANGUAGE_CODE)
if ret:
buffer.insert_at_cursor(
" language: {0}\n".format(
str or "unknown"))
# this function is called when an "application" message is posted on the bus
# here we retrieve the message posted by the on_tags_changed callback
def on_application_message(self, bus, msg):
if msg.get_structure().get_name() == "tags-changed":
# if the message is the "tags-changed", update the stream info in
# the GUI
self.analyze_streams()
if __name__ == '__main__':
p = Player()
p.start()