1
# Miro - an RSS based video player application
2
# Copyright (C) 2005-2010 Participatory Culture Foundation
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU General Public License for more details.
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18
# In addition, as a special exception, the copyright holders give
19
# permission to link the code of portions of this program with the OpenSSL
22
# You must obey the GNU General Public License in all respects for all of
23
# the code used other than OpenSSL. If you modify file(s) with this
24
# exception, you may extend this exception to your version of the file(s),
25
# but you are not obligated to do so. If you do not wish to do so, delete
26
# this exception statement from your version. If you delete this exception
27
# statement from all source files in the program, then also delete it here.
42
def __init__(self, filename, thumbnail_filename, callback):
43
self.thumbnail_filename = thumbnail_filename
44
self.filename = filename
45
self.callback = callback
48
self.first_pause = True
51
self.buffer_probes = {}
52
self.audio_only = False
53
self.saw_video_tag = self.saw_audio_tag = False
55
self.pipeline = gst.element_factory_make('playbin')
56
self.videosink = gst.element_factory_make("fakesink", "videosink")
57
self.pipeline.set_property("video-sink", self.videosink)
58
self.audiosink = gst.element_factory_make("fakesink", "audiosink")
59
self.pipeline.set_property("audio-sink", self.audiosink)
61
self.thumbnail_pipeline = None
63
self.bus = self.pipeline.get_bus()
64
self.bus.add_signal_watch()
65
self.watch_id = self.bus.connect("message", self.on_bus_message)
67
self.pipeline.set_property("uri", "file://%s" % filename)
68
self.pipeline.set_state(gst.STATE_PAUSED)
70
def on_bus_message(self, bus, message):
71
if message.src == self.pipeline:
72
if message.type == gst.MESSAGE_STATE_CHANGED:
73
prev, new_, pending = message.parse_state_changed()
74
if new_ == gst.STATE_PAUSED:
75
gobject.idle_add(self.paused_reached)
77
elif message.type == gst.MESSAGE_ERROR:
78
gobject.idle_add(self.error_occurred)
80
elif message.src == self.thumbnail_pipeline:
81
if message.type == gst.MESSAGE_STATE_CHANGED:
82
prev, new_, pending = message.parse_state_changed()
83
if new_ == gst.STATE_PAUSED:
84
for sink in self.thumbnail_pipeline.sinks():
85
name = sink.get_name()
86
factoryname = sink.get_factory().get_name()
87
if factoryname == "fakesink":
88
pad = sink.get_pad("sink")
89
self.buffer_probes[name] = pad.add_buffer_probe(
90
self.buffer_probe_handler, name)
92
seek_result = self.thumbnail_pipeline.seek(
94
gst.SEEK_FLAG_FLUSH | gst.SEEK_FLAG_ACCURATE,
95
gst.SEEK_TYPE_SET, min(self.duration / 2, 20 * gst.SECOND),
96
gst.SEEK_TYPE_NONE, 0)
102
elif message.type == gst.MESSAGE_ERROR:
103
gobject.idle_add(self.error_occurred)
106
if self.saw_video_tag:
108
elif self.saw_audio_tag:
112
self.callback(self.duration, self.success, media_type)
114
def get_duration(self, pipeline, attempts=0):
118
return pipeline.query_duration(gst.FORMAT_TIME)[0]
119
except gst.QueryError:
120
return self.get_duration(pipeline, attempts + 1)
122
def paused_reached(self):
123
self.saw_video_tag = False
124
self.saw_audio_tag = False
126
if not self.first_pause:
129
self.first_pause = True
130
current_video = self.pipeline.get_property("current-video")
131
current_audio = self.pipeline.get_property("current-audio")
133
if current_video == 0:
134
self.saw_video_tag = True
135
if current_audio == 0:
136
self.saw_audio_tag = True
138
if not self.saw_video_tag and self.saw_audio_tag:
140
self.audio_only = True
141
self.duration = self.get_duration(self.pipeline)
147
if not self.saw_video_tag and not self.saw_audio_tag:
148
# no audio and no video
149
self.audio_only = False
154
self.duration = self.get_duration(self.pipeline)
156
self.buffer_probes = {}
158
self.thumbnail_pipeline = gst.parse_launch(
159
'filesrc location="%s" ! decodebin ! '
160
'ffmpegcolorspace ! video/x-raw-rgb,depth=24,bpp=24 ! '
161
'fakesink signal-handoffs=True' % self.filename)
163
self.thumbnail_bus = self.thumbnail_pipeline.get_bus()
164
self.thumbnail_bus.add_signal_watch()
165
self.thumbnail_watch_id = self.thumbnail_bus.connect(
166
"message", self.on_bus_message)
168
self.thumbnail_pipeline.set_state(gst.STATE_PAUSED)
171
def error_occurred(self):
176
def buffer_probe_handler_real(self, pad, buff, name):
177
"""Capture buffers as gdk_pixbufs when told to.
188
width = filters["width"]
189
height = filters["height"]
190
timecode = self.thumbnail_pipeline.query_position(gst.FORMAT_TIME)[0]
191
pixbuf = gtk.gdk.pixbuf_new_from_data(
192
buff.data, gtk.gdk.COLORSPACE_RGB, False, 8,
193
width, height, width * 3)
194
pixbuf.save(self.thumbnail_filename, "png")
199
except gst.QueryError:
203
def buffer_probe_handler(self, pad, buff, name):
205
lambda: self.buffer_probe_handler_real(pad, buff, name))
208
def disconnect(self):
209
if self.pipeline is not None:
210
self.pipeline.set_state(gst.STATE_NULL)
211
if not self.audio_only:
212
for sink in self.pipeline.sinks():
213
name = sink.get_name()
214
factoryname = sink.get_factory().get_name()
215
if factoryname == "fakesink" :
216
pad = sink.get_pad("sink")
217
pad.remove_buffer_probe(self.buffer_probes[name])
218
del self.buffer_probes[name]
221
if self.bus is not None:
222
self.bus.disconnect(self.watch_id)
227
logging.basicConfig(level=logging.INFO)
229
def _wrap_func(*args, **kwargs):
230
logging.info("calling %s (%s) (%s)",
231
func.__name__, repr(args), repr(kwargs))
232
return func(*args, **kwargs)
235
for mem in dir(Extractor):
236
fun = Extractor.__dict__[mem]
238
Extractor.__dict__[mem] = wrap_func(fun)
240
def handle_result(duration, success, media_type):
242
print "Miro-Movie-Data-Length: %s" % (duration / 1000000)
244
print "Miro-Movie-Data-Length: -1"
246
print "Miro-Movie-Data-Thumbnail: Success"
248
print "Miro-Movie-Data-Thumbnail: Failure"
249
print "Miro-Movie-Data-Type: %s" % media_type
253
if "--verbose" in argv:
255
argv.remove("--verbose")
258
print "Syntax: gst_extractor.py <media-file> <path-to-thumbnail>"
262
argv.append(os.path.join(os.path.dirname(__file__), "thumbnail.png"))
264
extractor = Extractor(argv[1], argv[2], handle_result)
265
gtk.gdk.threads_init()
268
if __name__ == "__main__":