~ubuntu-branches/ubuntu/natty/miro/natty

« back to all changes in this revision

Viewing changes to platform/gtk-x11/plat/renderers/gst_extractor.py

  • Committer: Bazaar Package Importer
  • Author(s): Bryce Harrington
  • Date: 2011-01-22 02:46:33 UTC
  • mfrom: (1.4.10 upstream) (1.7.5 experimental)
  • Revision ID: james.westby@ubuntu.com-20110122024633-kjme8u93y2il5nmf
Tags: 3.5.1-1ubuntu1
* Merge from debian.  Remaining ubuntu changes:
  - Use python 2.7 instead of python 2.6
  - Relax dependency on python-dbus to >= 0.83.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Miro - an RSS based video player application
2
 
# Copyright (C) 2005-2010 Participatory Culture Foundation
3
 
#
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.
8
 
#
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.
13
 
#
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
17
 
#
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
20
 
# library.
21
 
#
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.
28
 
 
29
 
import sys
30
 
import os
31
 
 
32
 
import pygtk
33
 
import gtk
34
 
import gobject
35
 
 
36
 
import pygst
37
 
pygst.require('0.10')
38
 
import gst
39
 
import gst.interfaces
40
 
 
41
 
class Extractor:
42
 
    def __init__(self, filename, thumbnail_filename, callback):
43
 
        self.thumbnail_filename = thumbnail_filename
44
 
        self.filename = filename
45
 
        self.callback = callback
46
 
 
47
 
        self.grabit = False
48
 
        self.first_pause = True
49
 
        self.success = False
50
 
        self.duration = -1
51
 
        self.buffer_probes = {}
52
 
        self.audio_only = False
53
 
        self.saw_video_tag = self.saw_audio_tag = False
54
 
 
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)
60
 
 
61
 
        self.thumbnail_pipeline = None
62
 
 
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)
66
 
 
67
 
        self.pipeline.set_property("uri", "file://%s" % filename)
68
 
        self.pipeline.set_state(gst.STATE_PAUSED)
69
 
 
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)
76
 
 
77
 
            elif message.type == gst.MESSAGE_ERROR:
78
 
                gobject.idle_add(self.error_occurred)
79
 
 
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)
91
 
 
92
 
                    seek_result = self.thumbnail_pipeline.seek(
93
 
                        1.0, gst.FORMAT_TIME,
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)
97
 
 
98
 
                    if not seek_result:
99
 
                        self.disconnect()
100
 
                        self.done()
101
 
 
102
 
            elif message.type == gst.MESSAGE_ERROR:
103
 
                gobject.idle_add(self.error_occurred)
104
 
 
105
 
    def done(self):
106
 
        if self.saw_video_tag:
107
 
            media_type = 'video'
108
 
        elif self.saw_audio_tag:
109
 
            media_type = 'audio'
110
 
        else:
111
 
            media_type = 'other'
112
 
        self.callback(self.duration, self.success, media_type)
113
 
 
114
 
    def get_duration(self, pipeline, attempts=0):
115
 
        if attempts == 5:
116
 
            return 0
117
 
        try:
118
 
            return pipeline.query_duration(gst.FORMAT_TIME)[0]
119
 
        except gst.QueryError:
120
 
            return self.get_duration(pipeline, attempts + 1)
121
 
 
122
 
    def paused_reached(self):
123
 
        self.saw_video_tag = False
124
 
        self.saw_audio_tag = False
125
 
 
126
 
        if not self.first_pause:
127
 
            return False
128
 
 
129
 
        self.first_pause = True
130
 
        current_video = self.pipeline.get_property("current-video")
131
 
        current_audio = self.pipeline.get_property("current-audio")
132
 
 
133
 
        if current_video == 0:
134
 
            self.saw_video_tag = True
135
 
        if current_audio == 0:
136
 
            self.saw_audio_tag = True
137
 
 
138
 
        if not self.saw_video_tag and self.saw_audio_tag:
139
 
            # audio only
140
 
            self.audio_only = True
141
 
            self.duration = self.get_duration(self.pipeline)
142
 
            self.success = True
143
 
            self.disconnect()
144
 
            self.done()
145
 
            return False
146
 
 
147
 
        if not self.saw_video_tag and not self.saw_audio_tag:
148
 
            # no audio and no video
149
 
            self.audio_only = False
150
 
            self.disconnect()
151
 
            self.done()
152
 
            return False
153
 
 
154
 
        self.duration = self.get_duration(self.pipeline)
155
 
        self.grabit = True
156
 
        self.buffer_probes = {}
157
 
 
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)
162
 
 
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)
167
 
 
168
 
        self.thumbnail_pipeline.set_state(gst.STATE_PAUSED)
169
 
        return False
170
 
 
171
 
    def error_occurred(self):
172
 
        self.disconnect()
173
 
        self.done()
174
 
        return False
175
 
 
176
 
    def buffer_probe_handler_real(self, pad, buff, name):
177
 
        """Capture buffers as gdk_pixbufs when told to.
178
 
        """
179
 
        try:
180
 
            caps = buff.caps
181
 
            if caps is None:
182
 
                self.success = False
183
 
                self.disconnect()
184
 
                self.done()
185
 
                return False
186
 
 
187
 
            filters = caps[0]
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")
195
 
            del pixbuf
196
 
            self.success = True
197
 
            self.disconnect()
198
 
            self.done()
199
 
        except gst.QueryError:
200
 
            pass
201
 
        return False
202
 
 
203
 
    def buffer_probe_handler(self, pad, buff, name):
204
 
        gobject.idle_add(
205
 
            lambda: self.buffer_probe_handler_real(pad, buff, name))
206
 
        return True
207
 
 
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]
219
 
            self.pipeline = None
220
 
 
221
 
        if self.bus is not None:
222
 
            self.bus.disconnect(self.watch_id)
223
 
            self.bus = None
224
 
 
225
 
def make_verbose():
226
 
    import logging
227
 
    logging.basicConfig(level=logging.INFO)
228
 
    def wrap_func(func):
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)
233
 
        return _wrap_func
234
 
 
235
 
    for mem in dir(Extractor):
236
 
        fun = Extractor.__dict__[mem]
237
 
        if callable(fun):
238
 
            Extractor.__dict__[mem] = wrap_func(fun)
239
 
 
240
 
def handle_result(duration, success, media_type):
241
 
    if duration != -1:
242
 
        print "Miro-Movie-Data-Length: %s" % (duration / 1000000)
243
 
    else:
244
 
        print "Miro-Movie-Data-Length: -1"
245
 
    if success:
246
 
        print "Miro-Movie-Data-Thumbnail: Success"
247
 
    else:
248
 
        print "Miro-Movie-Data-Thumbnail: Failure"
249
 
    print "Miro-Movie-Data-Type: %s" % media_type
250
 
    sys.exit(0)
251
 
 
252
 
def main(argv):
253
 
    if "--verbose" in argv:
254
 
        make_verbose()
255
 
        argv.remove("--verbose")
256
 
 
257
 
    if len(argv) < 2:
258
 
        print "Syntax: gst_extractor.py <media-file> <path-to-thumbnail>"
259
 
        sys.exit(1)
260
 
 
261
 
    if len(argv) < 3:
262
 
        argv.append(os.path.join(os.path.dirname(__file__), "thumbnail.png"))
263
 
 
264
 
    extractor = Extractor(argv[1], argv[2], handle_result)
265
 
    gtk.gdk.threads_init()
266
 
    gtk.main()
267
 
 
268
 
if __name__ == "__main__":
269
 
    main(sys.argv)