Updated VideoThumbnailerMarkII, removed old
authorJoar Wandborg <joar@wandborg.se>
Sun, 24 Mar 2013 17:49:05 +0000 (18:49 +0100)
committerJoar Wandborg <joar@wandborg.se>
Sun, 24 Mar 2013 17:49:05 +0000 (18:49 +0100)
Removed the old VideoThumbnailer since it's not used anymore.

VideoThumbnailerMarkII:
Changed the state switching in on_thumbnail_message to only set the
state to "processing thumbnail" if the seek was succesful.

I'm not sure what I'm doing here, but I know at least some of it is
good, and as a whole, it seems to work, so far :)

mediagoblin/media_types/video/transcoders.py

index d8290d41d83f7770c1ec9283fe00e9c2752a238d..58b2c0d42b41f0f8a2b00001867d08ecf888faa8 100644 (file)
@@ -53,283 +53,6 @@ def pixbuf_to_pilbuf(buf):
     return data
 
 
-class VideoThumbnailer:
-    # Declaration of thumbnailer states
-    STATE_NULL = 0
-    STATE_HALTING = 1
-    STATE_PROCESSING = 2
-
-    # The current thumbnailer state
-
-    def __init__(self, source_path, dest_path):
-        '''
-        Set up playbin pipeline in order to get video properties.
-
-        Initializes and runs the gobject.MainLoop()
-
-        Abstract
-        - Set up a playbin with a fake audio sink and video sink. Load the video
-          into the playbin
-        - Initialize
-        '''
-        # This will contain the thumbnailing pipeline
-        self.state = self.STATE_NULL
-        self.thumbnail_pipeline = None
-        self.buffer_probes = {}
-        self.errors = []
-
-        self.source_path = source_path
-        self.dest_path = dest_path
-
-        self.loop = gobject.MainLoop()
-
-        # Set up the playbin. It will be used to discover certain
-        # properties of the input file
-        self.playbin = gst.element_factory_make('playbin')
-
-        self.videosink = gst.element_factory_make('fakesink', 'videosink')
-        self.playbin.set_property('video-sink', self.videosink)
-
-        self.audiosink = gst.element_factory_make('fakesink', 'audiosink')
-        self.playbin.set_property('audio-sink', self.audiosink)
-
-        self.bus = self.playbin.get_bus()
-        self.bus.add_signal_watch()
-        self.watch_id = self.bus.connect('message', self._on_bus_message)
-
-        self.playbin.set_property('uri', 'file:{0}'.format(
-                urllib.pathname2url(self.source_path)))
-
-        self.playbin.set_state(gst.STATE_PAUSED)
-
-        self.run()
-
-    def run(self):
-        self.loop.run()
-
-    def _on_bus_message(self, bus, message):
-        _log.debug(' thumbnail playbin: {0}'.format(message))
-
-        if message.type == gst.MESSAGE_ERROR:
-            _log.error('thumbnail playbin: {0}'.format(message))
-            gobject.idle_add(self._on_bus_error)
-
-        elif message.type == gst.MESSAGE_STATE_CHANGED:
-            # The pipeline state has changed
-            # Parse state changing data
-            _prev, state, _pending = message.parse_state_changed()
-
-            _log.debug('State changed: {0}'.format(state))
-
-            if state == gst.STATE_PAUSED:
-                if message.src == self.playbin:
-                    gobject.idle_add(self._on_bus_paused)
-
-    def _on_bus_paused(self):
-        '''
-        Set up thumbnailing pipeline
-        '''
-        current_video = self.playbin.get_property('current-video')
-
-        if current_video == 0:
-            _log.debug('Found current video from playbin')
-        else:
-            _log.error('Could not get any current video from playbin!')
-
-        self.duration = self._get_duration(self.playbin)
-        _log.info('Video length: {0}'.format(self.duration / gst.SECOND))
-
-        _log.info('Setting up thumbnailing pipeline')
-        self.thumbnail_pipeline = gst.parse_launch(
-            'filesrc location="{0}" ! decodebin ! '
-            'ffmpegcolorspace ! videoscale ! '
-            'video/x-raw-rgb,depth=24,bpp=24,pixel-aspect-ratio=1/1,width=180 ! '
-            'fakesink signal-handoffs=True'.format(self.source_path))
-
-        self.thumbnail_bus = self.thumbnail_pipeline.get_bus()
-        self.thumbnail_bus.add_signal_watch()
-        self.thumbnail_watch_id = self.thumbnail_bus.connect(
-            'message', self._on_thumbnail_bus_message)
-
-        self.thumbnail_pipeline.set_state(gst.STATE_PAUSED)
-
-        #gobject.timeout_add(3000, self._on_timeout)
-
-        return False
-
-    def _on_thumbnail_bus_message(self, bus, message):
-        _log.debug('thumbnail: {0}'.format(message))
-
-        if message.type == gst.MESSAGE_ERROR:
-            _log.error(message)
-            gobject.idle_add(self._on_bus_error)
-
-        if message.type == gst.MESSAGE_STATE_CHANGED:
-            _log.debug('State changed')
-            _prev, state, _pending = message.parse_state_changed()
-
-            if (state == gst.STATE_PAUSED and
-                not self.state == self.STATE_PROCESSING and
-                message.src == self.thumbnail_pipeline):
-                _log.info('Pipeline paused, processing')
-                self.state = self.STATE_PROCESSING
-
-                for sink in self.thumbnail_pipeline.sinks():
-                    name = sink.get_name()
-                    factoryname = sink.get_factory().get_name()
-
-                    if factoryname == 'fakesink':
-                        sinkpad = sink.get_pad('sink')
-
-                        self.buffer_probes[name] = sinkpad.add_buffer_probe(
-                            self.buffer_probe_handler, name)
-
-                        _log.info('Added buffer probe')
-
-                        break
-
-                # Apply the wadsworth constant, fallback to 1 second
-                # TODO: Will break if video is shorter than 1 sec
-                seek_amount = max(self.duration / 100 * 30, 1 * gst.SECOND)
-
-                _log.debug('seek amount: {0}'.format(seek_amount))
-
-                seek_result = self.thumbnail_pipeline.seek(
-                    1.0,
-                    gst.FORMAT_TIME,
-                    gst.SEEK_FLAG_FLUSH | gst.SEEK_FLAG_ACCURATE,
-                    gst.SEEK_TYPE_SET,
-                    seek_amount,
-                    gst.SEEK_TYPE_NONE,
-                    0)
-
-                if not seek_result:
-                    self.errors.append('COULD_NOT_SEEK')
-                    _log.error('Couldn\'t seek! result: {0}'.format(
-                            seek_result))
-                    _log.info(message)
-                    self.shutdown()
-                else:
-                    _log.debug('Seek successful')
-                    self.thumbnail_pipeline.set_state(gst.STATE_PAUSED)
-            else:
-                _log.debug('Won\'t seek: \t{0}\n\t{1}'.format(
-                    self.state,
-                    message.src))
-
-    def buffer_probe_handler_real(self, pad, buff, name):
-        '''
-        Capture buffers as gdk_pixbufs when told to.
-        '''
-        _log.info('Capturing frame')
-        try:
-            caps = buff.caps
-            if caps is None:
-                _log.error('No caps passed to buffer probe handler!')
-                self.shutdown()
-                return False
-
-            _log.debug('caps: {0}'.format(caps))
-
-            filters = caps[0]
-            width = filters["width"]
-            height = filters["height"]
-
-            im = Image.new('RGB', (width, height))
-
-            data = pixbuf_to_pilbuf(buff.data)
-
-            im.putdata(data)
-
-            im.save(self.dest_path)
-
-            _log.info('Saved thumbnail')
-
-            self.shutdown()
-
-        except gst.QueryError as e:
-            _log.error('QueryError: {0}'.format(e))
-
-        return False
-
-    def buffer_probe_handler(self, pad, buff, name):
-        '''
-        Proxy function for buffer_probe_handler_real
-        '''
-        _log.debug('Attaching real buffer handler to gobject idle event')
-        gobject.idle_add(
-            lambda: self.buffer_probe_handler_real(pad, buff, name))
-
-        return True
-
-    def _get_duration(self, pipeline, retries=0):
-        '''
-        Get the duration of a pipeline.
-
-        Retries 5 times.
-        '''
-        if retries == 5:
-            return 0
-
-        try:
-            return pipeline.query_duration(gst.FORMAT_TIME)[0]
-        except gst.QueryError:
-            return self._get_duration(pipeline, retries + 1)
-
-    def _on_timeout(self):
-        _log.error('Timeout in thumbnailer!')
-        self.shutdown()
-
-    def _on_bus_error(self, *args):
-        _log.error('AHAHAHA! Error! args: {0}'.format(args))
-
-    def shutdown(self):
-        '''
-        Tell gobject to call __halt when the mainloop is idle.
-        '''
-        _log.info('Shutting down')
-        self.__halt()
-
-    def __halt(self):
-        '''
-        Halt all pipelines and shut down the main loop
-        '''
-        _log.info('Halting...')
-        self.state = self.STATE_HALTING
-
-        self.__disconnect()
-
-        gobject.idle_add(self.__halt_final)
-
-    def __disconnect(self):
-        _log.debug('Disconnecting...')
-        if not self.playbin is None:
-            self.playbin.set_state(gst.STATE_NULL)
-            for sink in self.playbin.sinks():
-                name = sink.get_name()
-                factoryname = sink.get_factory().get_name()
-
-                _log.debug('Disconnecting {0}'.format(name))
-
-                if factoryname == "fakesink":
-                    pad = sink.get_pad("sink")
-                    pad.remove_buffer_probe(self.buffer_probes[name])
-                    del self.buffer_probes[name]
-
-        self.playbin = None
-
-        if self.bus is not None:
-            self.bus.disconnect(self.watch_id)
-            self.bus = None
-
-    def __halt_final(self):
-        _log.info('Done')
-        if self.errors:
-            _log.error(','.join(self.errors))
-
-        self.loop.quit()
-
-
 class VideoThumbnailerMarkII(object):
     '''
     Creates a thumbnail from a video file. Rewrite of VideoThumbnailer.
@@ -398,8 +121,8 @@ class VideoThumbnailerMarkII(object):
             self.run()
         except Exception as exc:
             _log.critical(
-                    'Exception "{0}" caught, disconnecting and re-raising'\
-                            .format(exc))
+                'Exception "{0}" caught, shutting down mainloop and re-raising'\
+                    .format(exc))
             self.disconnect()
             raise
 
@@ -410,7 +133,8 @@ class VideoThumbnailerMarkII(object):
         self.mainloop.run()
 
     def on_playbin_message(self, message_bus, message):
-        _log.debug('playbin message: {0}'.format(message))
+        # Silenced to prevent clobbering of output
+        #_log.debug('playbin message: {0}'.format(message))
 
         if message.type == gst.MESSAGE_ERROR:
             _log.error('playbin error: {0}'.format(message))
@@ -433,17 +157,20 @@ pending: {2}'.format(
 
     def on_playbin_paused(self):
         if self.has_reached_playbin_pause:
-            _log.warn('Has already reached logic for playbin pause. Aborting \
+            _log.warn('Has already reached on_playbin_paused. Aborting \
 without doing anything this time.')
             return False
 
         self.has_reached_playbin_pause = True
 
+        # XXX: Why is this even needed at this point?
         current_video = self.playbin.get_property('current-video')
 
         if not current_video:
-            _log.critical('thumbnail could not get any video data \
+            _log.critical('Could not get any video data \
 from playbin')
+        else:
+            _log.info('Got video data from playbin')
 
         self.duration = self.get_duration(self.playbin)
         self.permission_to_take_picture = True
@@ -474,7 +201,8 @@ from playbin')
         return False
 
     def on_thumbnail_message(self, message_bus, message):
-        _log.debug('thumbnail message: {0}'.format(message))
+        # This is silenced to prevent clobbering of the terminal window
+        #_log.debug('thumbnail message: {0}'.format(message))
 
         if message.type == gst.MESSAGE_ERROR:
             _log.error('thumbnail error: {0}'.format(message.parse_error()))
@@ -490,29 +218,10 @@ pending: {2}'.format(
     cur_state,
     pending_state))
 
-            if cur_state == gst.STATE_PAUSED and\
-                    not self.state == self.STATE_PROCESSING_THUMBNAIL:
-                self.state = self.STATE_PROCESSING_THUMBNAIL
-
+            if cur_state == gst.STATE_PAUSED and \
+               not self.state == self.STATE_PROCESSING_THUMBNAIL:
                 # Find the fakesink sink pad and attach the on_buffer_probe
                 # handler to it.
-                for sink in self.thumbnail_pipeline.sinks():
-                    sink_name = sink.get_name()
-                    sink_factory_name = sink.get_factory().get_name()
-
-                    if sink_factory_name == 'fakesink':
-                        sink_pad = sink.get_pad('sink')
-
-                        self.buffer_probes[sink_name] = sink_pad\
-                                .add_buffer_probe(
-                                        self.on_pad_buffer_probe,
-                                        sink_name)
-
-                        _log.info('Attached buffer probes: {0}'.format(
-                            self.buffer_probes))
-
-                        break
-
                 seek_amount = self.position_callback(self.duration, gst)
 
                 seek_result = self.thumbnail_pipeline.seek(
@@ -525,10 +234,30 @@ pending: {2}'.format(
                         0)
 
                 if not seek_result:
-                    _log.critical('Could not seek.')
+                    _log.info('Could not seek.')
+                else:
+                    _log.info('Seek successful, attaching buffer probe')
+                    self.state = self.STATE_PROCESSING_THUMBNAIL
+                    for sink in self.thumbnail_pipeline.sinks():
+                        sink_name = sink.get_name()
+                        sink_factory_name = sink.get_factory().get_name()
+
+                        if sink_factory_name == 'fakesink':
+                            sink_pad = sink.get_pad('sink')
+
+                            self.buffer_probes[sink_name] = sink_pad\
+                                    .add_buffer_probe(
+                                            self.on_pad_buffer_probe,
+                                            sink_name)
+
+                            _log.info('Attached buffer probes: {0}'.format(
+                                self.buffer_probes))
+
+                            break
+
 
             elif self.state == self.STATE_PROCESSING_THUMBNAIL:
-                _log.debug('Already processing thumbnail')
+                _log.info('Already processing thumbnail')
 
     def on_pad_buffer_probe(self, *args):
         _log.debug('buffer probe handler: {0}'.format(args))
@@ -649,7 +378,7 @@ pending: {2}'.format(
             return self.get_duration(pipeline, attempt + 1)
 
 
-class VideoTranscoder:
+class VideoTranscoder(object):
     '''
     Video transcoder
 
@@ -1011,6 +740,10 @@ if __name__ == '__main__':
                       action='store_true',
                       help='Dear program, please be quiet unless *error*')
 
+    parser.add_option('-w', '--width',
+                      type=int,
+                      default=180)
+
     (options, args) = parser.parse_args()
 
     if options.verbose:
@@ -1030,6 +763,7 @@ if __name__ == '__main__':
     transcoder = VideoTranscoder()
 
     if options.action == 'thumbnail':
+        args.append(options.width)
         VideoThumbnailerMarkII(*args)
     elif options.action == 'video':
         def cb(data):