From b06ea4ab469fd822074d80938e6163b36ccbf2a6 Mon Sep 17 00:00:00 2001 From: Joar Wandborg Date: Sun, 24 Mar 2013 18:49:05 +0100 Subject: [PATCH] Updated VideoThumbnailerMarkII, removed old 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 | 348 +++---------------- 1 file changed, 41 insertions(+), 307 deletions(-) diff --git a/mediagoblin/media_types/video/transcoders.py b/mediagoblin/media_types/video/transcoders.py index d8290d41..58b2c0d4 100644 --- a/mediagoblin/media_types/video/transcoders.py +++ b/mediagoblin/media_types/video/transcoders.py @@ -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): -- 2.25.1