Porting video to GStreamer 1.0
[mediagoblin.git] / mediagoblin / media_types / video / transcoders.py
index 6c2e885e26f0390f3c0f5b8803b5646cc33aa923..d53cabc6c25c520fa88477247eb968724cab75bc 100644 (file)
 from __future__ import division
 
 import os
-os.putenv('GST_DEBUG_DUMP_DOT_DIR', '/tmp')
-
 import sys
 import logging
-import pdb
-import urllib
+import multiprocessing
+from mediagoblin.media_types.tools import discover
 
-_log = logging.getLogger(__name__)
+#os.environ['GST_DEBUG'] = '4,python:4'
 
-CPU_COUNT = 2
-try:
-    import multiprocessing
-    try:
-        CPU_COUNT = multiprocessing.cpu_count()
-    except NotImplementedError:
-        _log.warning('multiprocessing.cpu_count not implemented')
-        pass
-except ImportError:
-    _log.warning('Could not import multiprocessing, defaulting to 2 CPU cores')
-
-try:
-    import gtk
-except ImportError:
-    raise Exception('Could not find pygtk')
+old_argv = sys.argv
+sys.argv = []
 
-try:
-    import gobject
-    gobject.threads_init()
-except ImportError:
-    raise Exception('gobject could not be found')
+import gi
+gi.require_version('Gst', '1.0')
+from gi.repository import GObject, Gst, GstPbutils
+Gst.init(None)
 
+sys.argv = old_argv
+import struct
 try:
-    import pygst
-    pygst.require('0.10')
-    import gst
-    from gst.extend import discoverer
+    from PIL import Image
 except ImportError:
-    raise Exception('gst/pygst 0.10 could not be found')
-
-
-class VideoThumbnailer:
-    # Declaration of thumbnailer states
-    STATE_NULL = 0
-    STATE_HALTING = 1
-    STATE_PROCESSING = 2
-
-    # The current thumbnailer state
-    state = STATE_NULL
-
-    # This will contain the thumbnailing pipeline
-    thumbnail_pipeline = None
-
-    buffer_probes = {}
-
-    def __init__(self, source_path, dest_path):
-        '''
-        Set up playbin pipeline in order to get video properties.
-
-        Initializes and runs the gobject.MainLoop()
-        '''
-        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(' BUS MESSAGE: {0}'.format(message))
-
-        if message.type == gst.MESSAGE_ERROR:
-            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 bus called, message: {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:
-            _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
-                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:
-                    pass
-                    #self.thumbnail_pipeline.set_state(gst.STATE_PAUSED)
-                    #pdb.set_trace()
-
-    def buffer_probe_handler_real(self, pad, buff, name):
-        '''
-        Capture buffers as gdk_pixbufs when told to.
-        '''
-        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"]
-
-            pixbuf = gtk.gdk.pixbuf_new_from_data(
-                buff.data, gtk.gdk.COLORSPACE_RGB, False, 8,
-                width, height, width * 3)
-
-            # NOTE: 200x136 is sort of arbitrary.  it's larger than what
-            # the ui uses at the time of this writing.
-            # new_width, new_height = scaled_size((width, height), (200, 136))
-
-            #pixbuf = pixbuf.scale_simple(
-            #new_width, new_height, gtk.gdk.INTERP_BILINEAR)
-
-            pixbuf.save(self.dest_path, 'jpeg')
-            _log.info('Saved thumbnail')
-            del pixbuf
-            self.shutdown()
-        except gst.QueryError:
-            pass
-        return False
-
-    def buffer_probe_handler(self, pad, buff, name):
-        '''
-        Proxy function for buffer_probe_handler_real
-        '''
-        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
+    import Image
 
-        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! DROP EVERYTHING!')
-        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
+_log = logging.getLogger(__name__)
 
-        if self.bus is not None:
-            self.bus.disconnect(self.watch_id)
-            self.bus = None
+CPU_COUNT = 2
 
+try:
+    CPU_COUNT = multiprocessing.cpu_count()
+except NotImplementedError:
+    _log.warning('multiprocessing.cpu_count not implemented')
 
-    def __halt_final(self):
-        _log.info('Done')
-        if self.errors:
-            _log.error(','.join(self.errors))
-            
-        self.loop.quit()
+os.putenv('GST_DEBUG_DUMP_DOT_DIR', '/tmp')
 
 
-class VideoTranscoder:
+def capture_thumb(video_path, dest_path, width=None, height=None, percent=0.5):
+    def pad_added(element, pad, connect_to):
+        '''This is a callback to dynamically add element to pipeline'''
+        caps = pad.query_caps(None)
+        name = caps.to_string()
+        _log.debug('on_pad_added: {0}'.format(name))
+        if name.startswith('video') and not connect_to.is_linked():
+            pad.link(connect_to)
+
+    # construct pipeline: uridecodebin ! videoconvert ! videoscale ! \
+    # ! CAPS ! appsink
+    pipeline = Gst.Pipeline()
+    uridecodebin = Gst.ElementFactory.make('uridecodebin', None)
+    uridecodebin.set_property('uri', 'file://{0}'.format(video_path))
+    videoconvert = Gst.ElementFactory.make('videoconvert', None)
+    uridecodebin.connect('pad-added', pad_added,
+                         videoconvert.get_static_pad('sink'))
+    videoscale = Gst.ElementFactory.make('videoscale', None)
+
+    # create caps for video scaling
+    caps_struct = Gst.Structure.new_empty('video/x-raw')
+    caps_struct.set_value('pixel-aspect-ratio', Gst.Fraction(1, 1))
+    caps_struct.set_value('format', 'RGB')
+    if height:
+        caps_struct.set_value('height', height)
+    if width:
+        caps_struct.set_value('width', width)
+    caps = Gst.Caps.new_empty()
+    caps.append_structure(caps_struct)
+
+    # sink everything to memory
+    appsink = Gst.ElementFactory.make('appsink', None)
+    appsink.set_property('caps', caps)
+
+    # add everything to pipeline
+    elements = [uridecodebin, videoconvert, videoscale, appsink]
+    for e in elements:
+        pipeline.add(e)
+    videoconvert.link(videoscale)
+    videoscale.link(appsink)
+
+    # pipeline constructed, starting playing, but first some preparations
+    # seek to 50% of the file is required
+    pipeline.set_state(Gst.State.PAUSED)
+    # timeout of 3 seconds below was set experimentally
+    state = pipeline.get_state(Gst.SECOND * 3)
+    if state[0] != Gst.StateChangeReturn.SUCCESS:
+        _log.warning('state change failed, {0}'.format(state))
+        return
+
+    # get duration
+    (success, duration) = pipeline.query_duration(Gst.Format.TIME)
+    if not success:
+        _log.warning('query_duration failed')
+        return
+
+    seek_to = int(duration * int(percent * 100) / 100)
+    _log.debug('Seeking to {0} of {1}'.format(
+            float(seek_to) / Gst.SECOND, float(duration) / Gst.SECOND))
+    seek = pipeline.seek_simple(Gst.Format.TIME, Gst.SeekFlags.FLUSH, seek_to)
+    if not seek:
+        _log.warning('seek failed')
+        return
+
+    # get sample, retrieve it's format and save
+    sample = appsink.emit("pull-preroll")
+    if not sample:
+        _log.warning('could not get sample')
+        return
+    caps = sample.get_caps()
+    if not caps:
+        _log.warning('could not get snapshot format')
+        return
+    structure = caps.get_structure(0)
+    (success, width) = structure.get_int('width')
+    (success, height) = structure.get_int('height')
+    buffer = sample.get_buffer()
+
+    # get the image from the buffer and save it to disk
+    im = Image.frombytes('RGB', (width, height),
+                         buffer.extract_dup(0, buffer.get_size()))
+    im.save(dest_path)
+    _log.info('thumbnail saved to {0}'.format(dest_path))
+
+    # cleanup
+    pipeline.set_state(Gst.State.NULL)
+
+
+class VideoTranscoder(object):
     '''
     Video transcoder
 
     Transcodes the SRC video file to a VP8 WebM video file at DST
 
-     - Does the same thing as VideoThumbnailer, but produces a WebM vp8
-       and vorbis video file.
-     - The VideoTranscoder exceeds the VideoThumbnailer in the way
-       that it was refined afterwards and therefore is done more
-       correctly.
+     - Produces a WebM vp8 and vorbis video file.
     '''
     def __init__(self):
         _log.info('Initializing VideoTranscoder...')
-
-        self.loop = gobject.MainLoop()
+        self.progress_percentage = None
+        self.loop = GObject.MainLoop()
 
     def transcode(self, src, dst, **kwargs):
         '''
@@ -350,153 +159,109 @@ class VideoTranscoder:
         self.source_path = src
         self.destination_path = dst
 
-        # Options
-        self.destination_dimensions = kwargs.get('dimensions') or (640, 640)
+        # vp8enc options
+        self.destination_dimensions = kwargs.get('dimensions', (640, 640))
+        self.vp8_quality = kwargs.get('vp8_quality', 8)
+        # Number of threads used by vp8enc:
+        # number of real cores - 1 as per recommendation on
+        # <http://www.webmproject.org/tools/encoder-parameters/#6-multi-threaded-encode-and-decode>
+        self.vp8_threads = kwargs.get('vp8_threads', CPU_COUNT - 1)
+
+        # 0 means auto-detect, but dict.get() only falls back to CPU_COUNT
+        # if value is None, this will correct our incompatibility with
+        # dict.get()
+        # This will also correct cases where there's only 1 CPU core, see
+        # original self.vp8_threads assignment above.
+        if self.vp8_threads == 0:
+            self.vp8_threads = CPU_COUNT
+
+        # vorbisenc options
+        self.vorbis_quality = kwargs.get('vorbis_quality', 0.3)
+
         self._progress_callback = kwargs.get('progress_callback') or None
 
         if not type(self.destination_dimensions) == tuple:
             raise Exception('dimensions must be tuple: (width, height)')
 
-        self._setup()
-        self._run()
-
-    def discover(self, src):
-        '''
-        Discover properties about a media file
-        '''
-        _log.info('Discovering {0}'.format(src))
-
-        self.source_path = src
-        self._setup_discover(discovered_callback=self.__on_discovered)
-
-        self.discoverer.discover()
-
-        self.loop.run()
-
-        return self._discovered_data
-
-    def __on_discovered(self, data, is_media):
-        if not is_media:
-            self.__stop()
-            raise Exception('Could not discover {0}'.format(self.source_path))
-
-        self._discovered_data = data
-
-        self.__stop_mainloop()
-
-    def _setup(self):
-        self._setup_discover()
         self._setup_pipeline()
-
-    def _run(self):
-        _log.info('Discovering...')
-        self.discoverer.discover()
-        _log.info('Done')
-
-        _log.debug('Initializing MainLoop()')
-        self.loop.run()
-
-    def _setup_discover(self, **kw):
-        _log.debug('Setting up discoverer')
-        self.discoverer = discoverer.Discoverer(self.source_path)
-
-        # Connect self.__discovered to the 'discovered' event
-        self.discoverer.connect(
-            'discovered',
-            kw.get('discovered_callback', self.__discovered))
-
-    def __discovered(self, data, is_media):
-        '''
-        Callback for media discoverer.
-        '''
-        if not is_media:
-            self.__stop()
-            raise Exception('Could not discover {0}'.format(self.source_path))
-
-        _log.debug('__discovered, data: {0}'.format(data.__dict__))
-
-        self.data = data
-
-        # Launch things that should be done after discovery
+        self.data = discover(self.source_path)
         self._link_elements()
         self.__setup_videoscale_capsfilter()
-
-        # Tell the transcoding pipeline to start running
-        self.pipeline.set_state(gst.STATE_PLAYING)
+        self.pipeline.set_state(Gst.State.PLAYING)
         _log.info('Transcoding...')
+        _log.debug('Initializing MainLoop()')
+        self.loop.run()
+
 
     def _setup_pipeline(self):
         _log.debug('Setting up transcoding pipeline')
         # Create the pipeline bin.
-        self.pipeline = gst.Pipeline('VideoTranscoderPipeline')
+        self.pipeline = Gst.Pipeline.new('VideoTranscoderPipeline')
 
         # Create all GStreamer elements, starting with
         # filesrc & decoder
-        self.filesrc = gst.element_factory_make('filesrc', 'filesrc')
+        self.filesrc = Gst.ElementFactory.make('filesrc', 'filesrc')
         self.filesrc.set_property('location', self.source_path)
         self.pipeline.add(self.filesrc)
 
-        self.decoder = gst.element_factory_make('decodebin2', 'decoder')
-        self.decoder.connect('new-decoded-pad', self._on_dynamic_pad)
+        self.decoder = Gst.ElementFactory.make('decodebin', 'decoder')
+        self.decoder.connect('pad-added', self._on_dynamic_pad)
         self.pipeline.add(self.decoder)
 
         # Video elements
-        self.videoqueue = gst.element_factory_make('queue', 'videoqueue')
+        self.videoqueue = Gst.ElementFactory.make('queue', 'videoqueue')
         self.pipeline.add(self.videoqueue)
 
-        self.videorate = gst.element_factory_make('videorate', 'videorate')
+        self.videorate = Gst.ElementFactory.make('videorate', 'videorate')
         self.pipeline.add(self.videorate)
 
-        self.ffmpegcolorspace = gst.element_factory_make(
-            'ffmpegcolorspace', 'ffmpegcolorspace')
-        self.pipeline.add(self.ffmpegcolorspace)
-        
-        self.videoscale = gst.element_factory_make('ffvideoscale', 'videoscale')
-        #self.videoscale.set_property('method', 2)  # I'm not sure this works
-        #self.videoscale.set_property('add-borders', 0)
+        self.videoconvert = Gst.ElementFactory.make('videoconvert',
+                                                    'videoconvert')
+        self.pipeline.add(self.videoconvert)
+
+        self.videoscale = Gst.ElementFactory.make('videoscale', 'videoscale')
         self.pipeline.add(self.videoscale)
 
-        self.capsfilter = gst.element_factory_make('capsfilter', 'capsfilter')
+        self.capsfilter = Gst.ElementFactory.make('capsfilter', 'capsfilter')
         self.pipeline.add(self.capsfilter)
 
-        self.vp8enc = gst.element_factory_make('vp8enc', 'vp8enc')
-        self.vp8enc.set_property('quality', 6)
-        self.vp8enc.set_property('threads', 2)
+        self.vp8enc = Gst.ElementFactory.make('vp8enc', 'vp8enc')
+        self.vp8enc.set_property('threads', self.vp8_threads)
         self.pipeline.add(self.vp8enc)
 
         # Audio elements
-        self.audioqueue = gst.element_factory_make('queue', 'audioqueue')
+        self.audioqueue = Gst.ElementFactory.make('queue', 'audioqueue')
         self.pipeline.add(self.audioqueue)
 
-        self.audiorate = gst.element_factory_make('audiorate', 'audiorate')
+        self.audiorate = Gst.ElementFactory.make('audiorate', 'audiorate')
         self.audiorate.set_property('tolerance', 80000000)
         self.pipeline.add(self.audiorate)
 
-        self.audioconvert = gst.element_factory_make('audioconvert', 'audioconvert')
+        self.audioconvert = Gst.ElementFactory.make('audioconvert', 'audioconvert')
         self.pipeline.add(self.audioconvert)
 
-        self.audiocapsfilter = gst.element_factory_make('capsfilter', 'audiocapsfilter')
-        audiocaps = ['audio/x-raw-float']
-        self.audiocapsfilter.set_property(
-            'caps',
-            gst.caps_from_string(
-                ','.join(audiocaps)))
+        self.audiocapsfilter = Gst.ElementFactory.make('capsfilter',
+                                                       'audiocapsfilter')
+        audiocaps = Gst.Caps.new_empty()
+        audiocaps_struct = Gst.Structure.new_empty('audio/x-raw')
+        audiocaps.append_structure(audiocaps_struct)
+        self.audiocapsfilter.set_property('caps', audiocaps)
         self.pipeline.add(self.audiocapsfilter)
 
-        self.vorbisenc = gst.element_factory_make('vorbisenc', 'vorbisenc')
-        self.vorbisenc.set_property('quality', 1)
+        self.vorbisenc = Gst.ElementFactory.make('vorbisenc', 'vorbisenc')
+        self.vorbisenc.set_property('quality', self.vorbis_quality)
         self.pipeline.add(self.vorbisenc)
 
         # WebMmux & filesink
-        self.webmmux = gst.element_factory_make('webmmux', 'webmmux')
+        self.webmmux = Gst.ElementFactory.make('webmmux', 'webmmux')
         self.pipeline.add(self.webmmux)
 
-        self.filesink = gst.element_factory_make('filesink', 'filesink')
+        self.filesink = Gst.ElementFactory.make('filesink', 'filesink')
         self.filesink.set_property('location', self.destination_path)
         self.pipeline.add(self.filesink)
 
         # Progressreport
-        self.progressreport = gst.element_factory_make(
+        self.progressreport = Gst.ElementFactory.make(
             'progressreport', 'progressreport')
         # Update every second
         self.progressreport.set_property('update-freq', 1)
@@ -515,49 +280,41 @@ class VideoTranscoder:
         # 'new-decoded-pad' which links decoded src pads to either a video
         # or audio sink
         self.filesrc.link(self.decoder)
-
-        # Link all the video elements in a row to webmmux
-        gst.element_link_many(
-            self.videoqueue,
-            self.videorate,
-            self.ffmpegcolorspace,
-            self.videoscale,
-            self.capsfilter,
-            self.vp8enc,
-            self.webmmux)
+        # link the rest
+        self.videoqueue.link(self.videorate)
+        self.videorate.link(self.videoconvert)
+        self.videoconvert.link(self.videoscale)
+        self.videoscale.link(self.capsfilter)
+        self.capsfilter.link(self.vp8enc)
+        self.vp8enc.link(self.webmmux)
 
         if self.data.is_audio:
-            # Link all the audio elements in a row to webmux
-            gst.element_link_many(
-                self.audioqueue,
-                self.audiorate,
-                self.audioconvert,
-                self.audiocapsfilter,
-                self.vorbisenc,
-                self.webmmux)
-
-        gst.element_link_many(
-            self.webmmux,
-            self.progressreport,
-            self.filesink)
+            # Link all the audio elements in a row to webmmux
+            self.audioqueue.link(self.audiorate)
+            self.audiorate.link(self.audioconvert)
+            self.audioconvert.link(self.audiocapsfilter)
+            self.audiocapsfilter.link(self.vorbisenc)
+            self.vorbisenc.link(self.webmmux)
+        self.webmmux.link(self.progressreport)
+        self.progressreport.link(self.filesink)
 
         # Setup the message bus and connect _on_message to the pipeline
         self._setup_bus()
 
-
-    def _on_dynamic_pad(self, dbin, pad, islast):
+    def _on_dynamic_pad(self, dbin, pad):
         '''
-        Callback called when ``decodebin2`` has a pad that we can connect to
+        Callback called when ``decodebin`` has a pad that we can connect to
         '''
         # Intersect the capabilities of the video sink and the pad src
         # Then check if they have no common capabilities.
-        if self.ffmpegcolorspace.get_pad_template('sink')\
-                .get_caps().intersect(pad.get_caps()).is_empty():
+        if (self.videorate.get_static_pad('sink').get_pad_template()
+                .get_caps().intersect(pad.query_caps()).is_empty()):
             # It is NOT a video src pad.
-            pad.link(self.audioqueue.get_pad('sink'))
+            pad.link(self.audioqueue.get_static_pad('sink'))
         else:
             # It IS a video src pad.
-            pad.link(self.videoqueue.get_pad('sink'))
+            _log.debug('linking video to the pad dynamically')
+            pad.link(self.videoqueue.get_static_pad('sink'))
 
     def _setup_bus(self):
         self.bus = self.pipeline.get_bus()
@@ -568,71 +325,53 @@ class VideoTranscoder:
         '''
         Sets up the output format (width, height) for the video
         '''
-        caps = ['video/x-raw-yuv', 'pixel-aspect-ratio=1/1', 'framerate=30/1']
-
-        if self.data.videoheight > self.data.videowidth:
-            # Whoa! We have ourselves a portrait video!
-            caps.append('height={0}'.format(
-                    self.destination_dimensions[1]))
+        caps_struct = Gst.Structure.new_empty('video/x-raw')
+        caps_struct.set_value('pixel-aspect-ratio', Gst.Fraction(1, 1))
+        caps_struct.set_value('framerate', Gst.Fraction(30, 1))
+        video_info = self.data.get_video_streams()[0]
+        if video_info.get_height() > video_info.get_width():
+            # portrait
+            caps_struct.set_value('height', self.destination_dimensions[1])
         else:
-            # It's a landscape, phew, how normal.
-            caps.append('width={0}'.format(
-                    self.destination_dimensions[0]))
-
-        self.capsfilter.set_property(
-            'caps',
-            gst.caps_from_string(
-                ','.join(caps)))
+            # landscape
+            caps_struct.set_value('width', self.destination_dimensions[0])
+        caps = Gst.Caps.new_empty()
+        caps.append_structure(caps_struct)
+        self.capsfilter.set_property('caps', caps)
 
     def _on_message(self, bus, message):
         _log.debug((bus, message, message.type))
-
-        t = message.type
-
-        if t == gst.MESSAGE_EOS:
-            self._discover_dst_and_stop()
+        if message.type == Gst.MessageType.EOS:
+            self.dst_data = discover(self.destination_path)
+            self.__stop()
             _log.info('Done')
-
-        elif t == gst.MESSAGE_ELEMENT:
-            if message.structure.get_name() == 'progress':
-                data = dict(message.structure)
-
-                if self._progress_callback:
-                    self._progress_callback(data)
-
-                _log.info('{percent}% done...'.format(
-                        percent=data.get('percent')))
-                _log.debug(data)
-
-        elif t == gst.MESSAGE_ERROR:
-            _log.error((bus, message))
+        elif message.type == Gst.MessageType.ELEMENT:
+            if message.has_name('progress'):
+                structure = message.get_structure()
+                # Update progress state if it has changed
+                (success, percent) = structure.get_int('percent')
+                if self.progress_percentage != percent and success:
+                    self.progress_percentage = percent
+                    if self._progress_callback:
+                        self._progress_callback(percent)
+                    _log.info('{percent}% done...'.format(percent=percent))
+        elif message.type == Gst.MessageType.ERROR:
+            _log.error('Got error: {0}'.format(message.parse_error()))
             self.__stop()
 
-    def _discover_dst_and_stop(self):
-        self.dst_discoverer = discoverer.Discoverer(self.destination_path)
-
-        self.dst_discoverer.connect('discovered', self.__dst_discovered)
-
-        self.dst_discoverer.discover()
-
-
-    def __dst_discovered(self, data, is_media):
-        self.dst_data = data
-
-        self.__stop()
-
     def __stop(self):
         _log.debug(self.loop)
 
-        # Stop executing the pipeline
-        self.pipeline.set_state(gst.STATE_NULL)
+        if hasattr(self, 'pipeline'):
+            # Stop executing the pipeline
+            self.pipeline.set_state(Gst.State.NULL)
 
         # This kills the loop, mercifully
-        gobject.idle_add(self.__stop_mainloop)
+        GObject.idle_add(self.__stop_mainloop)
 
     def __stop_mainloop(self):
         '''
-        Wrapper for gobject.MainLoop.quit()
+        Wrapper for GObject.MainLoop.quit()
 
         This wrapper makes us able to see if self.loop.quit has been called
         '''
@@ -643,7 +382,6 @@ class VideoTranscoder:
 
 if __name__ == '__main__':
     os.nice(19)
-    logging.basicConfig()
     from optparse import OptionParser
 
     parser = OptionParser(
@@ -663,6 +401,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:
@@ -682,11 +424,11 @@ if __name__ == '__main__':
     transcoder = VideoTranscoder()
 
     if options.action == 'thumbnail':
-        VideoThumbnailer(*args)
+        args.append(options.width)
+        VideoThumbnailerMarkII(*args)
     elif options.action == 'video':
         def cb(data):
             print('I\'m a callback!')
         transcoder.transcode(*args, progress_callback=cb)
     elif options.action == 'discover':
-        print transcoder.discover(*args).__dict__
-    
+        print transcoder.discover(*args)