Media processing, transcoding, display fixes
authorJoar Wandborg <git@wandborg.com>
Mon, 9 Apr 2012 14:28:46 +0000 (16:28 +0200)
committerJoar Wandborg <git@wandborg.com>
Mon, 9 Apr 2012 14:28:46 +0000 (16:28 +0200)
- Added configurable options
  - Video
    - vp8_quality
    - vp8_threads
    - vorbis_quality
  - Audio
    - spectrogram_fft_size
  - ASCII
    - thumbnail_font
- Cleaned up ascii.asciitoimage
- Cleaned up video.transcoders
- Changed default video quality settings to better quality
- Changed default audio spectrogram solution to the double.
- Added a hacky notice for Firefox users instead of the broken range
  input.

mediagoblin/config_spec.ini
mediagoblin/media_types/ascii/asciitoimage.py
mediagoblin/media_types/ascii/processing.py
mediagoblin/media_types/audio/processing.py
mediagoblin/media_types/video/processing.py
mediagoblin/media_types/video/transcoders.py
mediagoblin/static/css/audio.css
mediagoblin/static/js/audio.js

index e30825dedadf142a7e89b19d335eb1f9f443318e..01853e48528b3522d0bd55361a9e5ce10d21156d 100644 (file)
@@ -86,11 +86,23 @@ max_height = integer(default=180)
 # Should we keep the original file?
 keep_original = boolean(default=False)
 
+# 0 means autodetect, autodetect means number_of_CPUs - 1
+vp8_threads = integer(default=0)
+# Range: 0..10
+vp8_quality = integer(default=8)
+# Range: -0.1..1
+vorbis_quality = float(default=0.3)
+
+
 [media_type:mediagoblin.media_types.audio]
 # vorbisenc qualiy
 quality = float(default=0.3)
 create_spectrogram = boolean(default=True)
+spectrogram_fft_size = integer(default=4096)
+
 
+[media_type:mediagoblin.media_types.ascii]
+thumbnail_font = string(default=None)
 
 [beaker.cache]
 type = string(default="file")
index 3017d2ad25f10539fd0c57d4f050a853f086e3d5..108de023c59ae6d525495f9be4cc6363c11b6e6c 100644 (file)
@@ -34,31 +34,12 @@ class AsciiToImage(object):
     - font_size: Font size, ``int``
       default: 11
     '''
-
-    # Font file path
-    _font = None
-
-    _font_size = 11
-
-    # ImageFont instance
-    _if = None
-
-    # ImageFont
-    _if_dims = None
-
-    # Image instance
-    _im = None
-
     def __init__(self, **kw):
-        if kw.get('font'):
-            self._font = kw.get('font')
-        else:
-            self._font = pkg_resources.resource_filename(
+        self._font = kw.get('font', pkg_resources.resource_filename(
                 'mediagoblin.media_types.ascii',
-                os.path.join('fonts', 'Inconsolata.otf'))
+                os.path.join('fonts', 'Inconsolata.otf')))
 
-        if kw.get('font_size'):
-            self._font_size = kw.get('font_size')
+        self._font_size = kw.get('font_size', 11)
 
         self._if = ImageFont.truetype(
             self._font,
index a2a52e9d6aee76780dd4b68cc72bd89bb135cdcb..04d1166c46b33af73531ee92e9c5a106ee800fa2 100644 (file)
@@ -42,6 +42,7 @@ def process_ascii(entry):
     '''
     Code to process a txt file
     '''
+    ascii_config = mgg.global_config['media_type:mediagoblin.media_types.ascii']
     workbench = mgg.workbench_manager.create_workbench()
     # Conversions subdirectory to avoid collisions
     conversions_subdir = os.path.join(
@@ -77,7 +78,14 @@ def process_ascii(entry):
         tmp_thumb_filename = os.path.join(
             conversions_subdir, thumb_filepath[-1])
 
-        converter = asciitoimage.AsciiToImage()
+        ascii_converter_args = {}
+
+        if ascii_config['thumbnail_font']:
+            ascii_converter_args.update(
+                    {'font': ascii_config['thumbnail_font']})
+
+        converter = asciitoimage.AsciiToImage(
+               **ascii_converter_args)
 
         thumb = converter._create_image(
             queued_file.read())
index c0ff7bffb62e3f34aa709ac774430d86e38d4266..f0b8d0f9c2b957bbd88d3ff44d1587fd355f8a2a 100644 (file)
@@ -27,7 +27,7 @@ from mediagoblin.media_types.audio.transcoders import AudioTranscoder, \
 _log = logging.getLogger(__name__)
 
 def sniff_handler(media_file, **kw):
-    try: 
+    try:
         transcoder = AudioTranscoder()
         data = transcoder.discover(media_file.name)
     except BadMediaFail:
@@ -94,7 +94,8 @@ def process_audio(entry):
                 thumbnailer.spectrogram(
                     wav_tmp.name,
                     spectrogram_tmp.name,
-                    width=mgg.global_config['media:medium']['max_width'])
+                    width=mgg.global_config['media:medium']['max_width'],
+                    fft_size=audio_config['spectrogram_fft_size'])
 
                 _log.debug('Saving spectrogram...')
                 mgg.public_store.get_file(spectrogram_filepath, 'wb').write(
@@ -121,7 +122,7 @@ def process_audio(entry):
                     entry.media_files['thumb'] = thumb_filepath
     else:
         entry.media_files['thumb'] = ['fake', 'thumb', 'path.jpg']
-            
+
     mgg.queue_store.delete_file(queued_filepath)
 
     entry.save()
index d4b4e983775787b10dac2d687b4c3ae3b9f59134..5bbcc92f76956340410c5a88a6ac661f3b80f642 100644 (file)
 
 import tempfile
 import logging
-import os
 
 from mediagoblin import mg_globals as mgg
-from mediagoblin.processing import mark_entry_failed, \
+from mediagoblin.processing import \
     create_pub_filepath, FilenameBuilder
 from . import transcoders
 
-logging.basicConfig()
-
 _log = logging.getLogger(__name__)
 _log.setLevel(logging.DEBUG)
 
@@ -73,7 +70,10 @@ def process_video(entry):
     with tmp_dst:
         # Transcode queued file to a VP8/vorbis file that fits in a 640x640 square
         transcoder = transcoders.VideoTranscoder()
-        transcoder.transcode(queued_filename, tmp_dst.name)
+        transcoder.transcode(queued_filename, tmp_dst.name,
+                vp8_quality=video_config['vp8_quality'],
+                vp8_threads=video_config['vp8_threads'],
+                vorbis_quality=video_config['vorbis_quality'])
 
         # Push transcoded video to public storage
         _log.debug('Saving medium...')
index 74821877c3559b5899a2d385e295ef6caa731e27..d5c162ba9baebe36448dc672b36e2c20b43fd820 100644 (file)
@@ -72,6 +72,11 @@ class VideoThumbnailer:
         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
         '''
         self.errors = []
 
@@ -105,9 +110,10 @@ class VideoThumbnailer:
         self.loop.run()
 
     def _on_bus_message(self, bus, message):
-        _log.debug(' BUS MESSAGE: {0}'.format(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:
@@ -154,13 +160,14 @@ class VideoThumbnailer:
         return False
 
     def _on_thumbnail_bus_message(self, bus, message):
-        _log.debug('Thumbnail bus called, message: {0}'.format(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
@@ -184,6 +191,7 @@ class VideoThumbnailer:
                         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))
@@ -204,14 +212,19 @@ class VideoThumbnailer:
                     _log.info(message)
                     self.shutdown()
                 else:
-                    pass
-                    #self.thumbnail_pipeline.set_state(gst.STATE_PAUSED)
+                    _log.debug('Seek successful')
+                    self.thumbnail_pipeline.set_state(gst.STATE_PAUSED)
                     #pdb.set_trace()
+            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:
@@ -237,14 +250,16 @@ class VideoThumbnailer:
 
             self.shutdown()
 
-        except gst.QueryError:
-            pass
+        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))
 
@@ -265,7 +280,7 @@ class VideoThumbnailer:
             return self._get_duration(pipeline, retries + 1)
 
     def _on_timeout(self):
-        _log.error('TIMEOUT! DROP EVERYTHING!')
+        _log.error('Timeout in thumbnailer!')
         self.shutdown()
 
     def _on_bus_error(self, *args):
@@ -342,8 +357,25 @@ 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:
@@ -456,8 +488,9 @@ class VideoTranscoder:
         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.set_property('quality', self.vp8_quality)
+        self.vp8enc.set_property('threads', self.vp8_threads)
+        self.vp8enc.set_property('max-latency', 25)
         self.pipeline.add(self.vp8enc)
 
         # Audio elements
@@ -480,7 +513,7 @@ class VideoTranscoder:
         self.pipeline.add(self.audiocapsfilter)
 
         self.vorbisenc = gst.element_factory_make('vorbisenc', 'vorbisenc')
-        self.vorbisenc.set_property('quality', 1)
+        self.vorbisenc.set_property('quality', self.vorbis_quality)
         self.pipeline.add(self.vorbisenc)
 
         # WebMmux & filesink
index 387278ec8095fe00128ebf18d10528c6e2c1e622..e007a0e11138dbd029f8fef3704254697489b8b9 100644 (file)
@@ -80,5 +80,5 @@
     transition: opacity .1s ease-in-out;
 }
     .audio-spectrogram:hover .audio-volume {
-        opacity: 1;
+        opacity: 0.7;
     }
index f50908a131b733f7e2080e7c137533cc7daeae4b..217a21603c4175f8873fe458a7661b3b2135a336 100644 (file)
@@ -210,8 +210,14 @@ var audioPlayer = new Object();
         $('<div class="seekbar"></div>').appendTo(im.parent());
         $('<div class="audio-control-play-pause paused">▶</div>').appendTo(im.parent());
         $('<div class="audio-currentTime">00:00</div>').appendTo(im.parent());
-        $('<input placeholder="Range input not supported" class="audio-volume"'
-                +'type="range" min="0" max="1" step="0.01" />').appendTo(im.parent());
+        if (navigator && /Firefox/.test(navigator.userAgent)) {
+            $('<p class="message_warning">Sorry, Firefox does not support the '
+                    + 'range input type, you won\'t be able to change the volume</p>')
+                .appendTo(im.parent().parent());
+        } else {
+            $('<input type="range" class="audio-volume"'
+                    +'value="1" min="0" max="1" step="0.001" />').appendTo(im.parent());
+        }
         $('.audio-spectrogram').trigger('attachedControls');
     };
 })(audioPlayer);