Faster sniffing
authorJoar Wandborg <git@wandborg.com>
Thu, 22 Mar 2012 00:27:19 +0000 (01:27 +0100)
committerJoar Wandborg <git@wandborg.com>
Thu, 22 Mar 2012 00:27:19 +0000 (01:27 +0100)
- Sniffing now goes through the old extension-based filter before
  doing it the bitsniffing way.
- Refractored get_media_type_and_manager(filename).
- Removed ogg extension from video accepted extensions, audio
  will take care of that.
- Added custom audio player, still WIP,but working.
- Added test for sniffing. This only tests for the
  mediagoblin.media_types.image type, as that is the only
  one enabled from start.

mediagoblin/media_types/__init__.py
mediagoblin/media_types/video/__init__.py
mediagoblin/static/css/audio.css [new file with mode: 0644]
mediagoblin/static/js/audio.js [new file with mode: 0644]
mediagoblin/templates/mediagoblin/media_displays/audio.html
mediagoblin/tests/test_submission.py

index db8c6f73de06f4386d495a5af5f27150df16d614..93d2319fbb73b2bddb3c8d7e25cf0a1d4d0463a4 100644 (file)
@@ -36,16 +36,24 @@ def sniff_media(media):
     Iterate through the enabled media types and find those suited
     for a certain file.
     '''
-    media_file = tempfile.NamedTemporaryFile()
-    media_file.write(media.file.read())
-    media.file.seek(0)
-    for media_type, manager in get_media_managers():
-        _log.info('Sniffing {0}'.format(media_type))
-        if manager['sniff_handler'](media_file, media=media):
-            _log.info('{0} accepts the file'.format(media_type))
-            return media_type, manager
-        else:
-            _log.debug('{0} did not accept the file'.format(media_type))
+
+    try:
+        return get_media_type_and_manager(media.filename)
+    except FileTypeNotSupported:
+        _log.info('No media handler found by file extension. Doing it the expensive way...')
+        # Create a temporary file for sniffers suchs as GStreamer-based
+        # Audio video
+        media_file = tempfile.NamedTemporaryFile()
+        media_file.write(media.file.read())
+        media.file.seek(0)
+
+        for media_type, manager in get_media_managers():
+            _log.info('Sniffing {0}'.format(media_type))
+            if manager['sniff_handler'](media_file, media=media):
+                _log.info('{0} accepts the file'.format(media_type))
+                return media_type, manager
+            else:
+                _log.debug('{0} did not accept the file'.format(media_type))
 
     raise FileTypeNotSupported(
         # TODO: Provide information on which file types are supported
@@ -66,7 +74,7 @@ def get_media_managers():
     '''
     for media_type in get_media_types():
         __import__(media_type)
-            
+
         yield media_type, sys.modules[media_type].MEDIA_MANAGER
 
 
@@ -91,22 +99,22 @@ def get_media_manager(_media_type):
 
 def get_media_type_and_manager(filename):
     '''
-    Get the media type and manager based on a filename
+    Try to find the media type based on the file name, extension
+    specifically. This is used as a speedup, the sniffing functionality
+    then falls back on more in-depth bitsniffing of the source file.
     '''
     if filename.find('.') > 0:
         # Get the file extension
         ext = os.path.splitext(filename)[1].lower()
-    else:
-        raise InvalidFileType(
-            _(u'Could not extract any file extension from "{filename}"').format(
-                filename=filename))
 
-    for media_type, manager in get_media_managers():
-        # Omit the dot from the extension and match it against
-        # the media manager
-        if ext[1:] in manager['accepted_extensions']:
-            return media_type, manager
+        for media_type, manager in get_media_managers():
+            # Omit the dot from the extension and match it against
+            # the media manager
+            if ext[1:] in manager['accepted_extensions']:
+                return media_type, manager
     else:
-        raise FileTypeNotSupported(
-            # TODO: Provide information on which file types are supported
-            _(u'Sorry, I don\'t support that file type :('))
+        _log.info('File {0} has no file extension, let\'s hope the sniffers get it.'.format(
+            filename))
+
+    raise FileTypeNotSupported(
+        _(u'Sorry, I don\'t support that file type :('))
index 5351abe68473801e3ea5a3f1b7018e6a090c191d..a53927d52f74c550f93b6f13fdf2fb6a5a7670c2 100644 (file)
@@ -26,4 +26,4 @@ MEDIA_MANAGER = {
     "display_template": "mediagoblin/media_displays/video.html",
     "default_thumb": "images/media_thumbs/video.jpg",
     "accepted_extensions": [
-        "mp4", "mov", "webm", "avi", "3gp", "3gpp", "mkv", "ogv", "ogg"]}
+        "mp4", "mov", "webm", "avi", "3gp", "3gpp", "mkv", "ogv"]}
diff --git a/mediagoblin/static/css/audio.css b/mediagoblin/static/css/audio.css
new file mode 100644 (file)
index 0000000..5f7a888
--- /dev/null
@@ -0,0 +1,53 @@
+.audio-spectrogram {
+    position: relative;
+}
+.playhead {
+    position: absolute;
+    top: 0;
+    left: 0;
+    background: rgba(134, 212, 177, 0.3);
+    border-right: thin solid #ffaa00;
+    height: 100%;
+    -webkit-transition: width .1s ease-out;
+    -moz-transition: width .1s ease-out;
+}
+.audio-control-play-pause {
+    position: absolute;
+    bottom: 0;
+    left: 5px;
+    cursor: pointer;
+    /* background: rgba(0, 0, 0, 0.7); */
+    font-size: 40px;
+    width: 50px;
+    text-shadow: 0 0 10px black;
+}
+    .audio-control-play-pause.playing {
+        color: #b71500;
+    }
+    .audio-control-play-pause.paused {
+        color: rgb(134, 212, 177);
+    }
+.buffered {
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    height: 2px;
+    width: 0;
+    -webkit-transition: width 1s ease-out;
+    -moz-transition: width 1s ease-out;
+    background: rgba(134, 177, 212, 1);
+    cursor: pointer;
+}
+.seekbar {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+}
+.audio-currentTime {
+    position: absolute;
+    bottom: 0;
+    right: 0;
+    background: rgba(0, 0, 0, 0.7);
+}
diff --git a/mediagoblin/static/js/audio.js b/mediagoblin/static/js/audio.js
new file mode 100644 (file)
index 0000000..8c91bd0
--- /dev/null
@@ -0,0 +1,140 @@
+/**
+ * GNU MediaGoblin -- federated, autonomous media hosting
+ * Copyright (C) 2011, 2012 MediaGoblin contributors.  See AUTHORS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+var audioPlayer = new Object();
+
+(function (audioPlayer) {
+    audioPlayer.init = function (audioElement) {
+        audioPlayer.audioElement = audioElement;
+        console.log(audioElement);
+        attachEvents();
+        $(audioElement).hide();
+    };
+
+    function attachEvents () {
+        audioPlayer.audioElement.addEventListener('durationchange', audioPlayer.durationChange, true);
+        audioPlayer.audioElement.addEventListener('timeupdate', audioPlayer.timeUpdate, true);
+        audioPlayer.audioElement.addEventListener('progress', audioPlayer.onProgress, true);
+        $(document).ready( function () {
+            $('.audio-spectrogram').delegate('.seekbar', 'click', audioPlayer.onSeek);
+            $('.audio-spectrogram').delegate('.audio-control-play-pause', 'click', audioPlayer.playPause);
+        });
+    }
+
+    audioPlayer.onProgress = function(a, b, c) {
+        console.log(a, b, c);
+        buffered = audioPlayer.audioElement.buffered;
+
+        ranges = new Array(); 
+
+        for (i = 0; i < buffered.length; i++) {
+            ranges[i] = new Array();
+            ranges[i][0] = buffered.start(i);
+            ranges[i][1] = buffered.end(i);
+        }
+        console.log('ranges', ranges);
+        $('.audio-spectrogram .buffered').width(
+            (ranges[0][1] / audioPlayer.audioElement.duration) * audioPlayer.imageElement.width());
+    };
+
+    audioPlayer.onSeek = function (e) {
+        console.log('onSeek', e);
+        im = audioPlayer.imageElement;
+        pos = e.offsetX / im.width();
+        audioPlayer.audioElement.currentTime = pos * audioPlayer.audioElement.duration;
+        audioPlayer.audioElement.play();
+        audioPlayer.setState(audioPlayer.PLAYING);
+    };
+
+    audioPlayer.playPause = function (e) {
+        console.log('playPause', e);
+        if (audioPlayer.audioElement.paused) {
+            audioPlayer.audioElement.play();
+            audioPlayer.setState(audioPlayer.PLAYING);
+        } else {
+            audioPlayer.audioElement.pause();
+            audioPlayer.setState(audioPlayer.PAUSED);
+        }
+    };
+
+    audioPlayer.NULL = null;
+    audioPlayer.PLAYING = 2;
+    audioPlayer.PAUSED = 4;
+
+    audioPlayer.state = audioPlayer.NULL;
+
+    audioPlayer.setState = function (state) {
+        if (state == audioPlayer.state) {
+            return;
+        }
+
+        switch (state) {
+            case audioPlayer.PLAYING:
+                $('.audio-spectrogram .audio-control-play-pause')
+                    .removeClass('paused').addClass('playing')
+                    .text('■');
+                break;
+            case audioPlayer.PAUSED:
+                $('.audio-spectrogram .audio-control-play-pause')
+                    .removeClass('playing').addClass('paused')
+                    .text('▶');
+                break;
+        }
+    };
+
+    audioPlayer.durationChange = function () {
+        duration = audioPlayer.audioElement.duration;
+    };
+
+    audioPlayer.timeUpdate = function () {
+        currentTime = audioPlayer.audioElement.currentTime;
+        playhead = audioPlayer.imageElement.parent().find('.playhead');
+        playhead.css('width', (currentTime / audioPlayer.audioElement.duration) * audioPlayer.imageElement.width());
+        time = formatTime(currentTime);
+        duration = formatTime(audioPlayer.audioElement.duration);
+        audioPlayer.imageElement.parent().find('.audio-currentTime').text(time + '/' + duration);
+    };
+
+    function formatTime(seconds) {
+        var h = Math.floor(seconds / (60 * 60));
+        var m = Math.floor((seconds - h * 60 * 60) / 60);
+        var s = Math.round(seconds - h * 60 * 60 - m * 60);
+        return '' + (h ? (h < 10 ? '0' + h : h) + ':' : '') + (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s);
+    }
+
+    audioPlayer.attachToImage = function (imageElement) {
+        /**
+         * Attach the player to an image element
+         */
+        console.log(imageElement);
+        im = $(imageElement);
+        audioPlayer.imageElement = im;
+        $('<div class="playhead"></div>').appendTo(im.parent());
+        $('<div class="buffered"></div>').appendTo(im.parent());
+        $('<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());
+    };
+})(audioPlayer);
+
+$(document).ready(function () {
+    audioElements = $('.audio-media .audio-player');
+    audioPlayer.init(audioElements[0]);
+    audioPlayer.attachToImage($('.audio-spectrogram img')[0]);
+});
+
index f130e323528330a802fe5a224239b872afdf9c6c..36bd9d1d8e30c0aa028b80d3049f27cd734f53d1 100644 (file)
 
 {% extends 'mediagoblin/user_pages/media.html' %}
 
+{% block mediagoblin_head %}
+    {{ super() }}
+    <link rel="stylesheet" type="text/css" href="{{ request.staticdirect('/css/audio.css') }}" />
+    <script type="text/javascript" src="{{ request.staticdirect(
+        '/js/audio.js') }}"></script>
+{% endblock %}
+
 {% block mediagoblin_media %}
   <div class="audio-media">
     {% if 'spectrogram' in media.media_files %}
index 53ba25d845d97e07a4f8c1e36decd16f10bab367..702741b4adf6ab02b717595b4f6b0cf1d959d900 100644 (file)
@@ -219,6 +219,28 @@ class TestSubmission:
             request.db.MediaEntry.find(
                 {'_id': media._id}).count())
 
+    def test_sniffing(self):
+        '''
+        Test sniffing mechanism to assert that regular uploads work as intended
+        '''
+        template.clear_test_template_context()
+        response = self.test_app.post(
+            '/submit/', {
+                'title': 'UNIQUE_TITLE_PLS_DONT_CREATE_OTHER_MEDIA_WITH_THIS_TITLE'
+                }, upload_files=[(
+                    'file', GOOD_JPG)])
+
+        response.follow()
+
+        context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/user_pages/user.html']
+
+        request = context['request']
+
+        media = request.db.MediaEntry.find_one({
+            u'title': u'UNIQUE_TITLE_PLS_DONT_CREATE_OTHER_MEDIA_WITH_THIS_TITLE'})
+
+        assert media.media_type == 'mediagoblin.media_types.image'
+
     def test_malicious_uploads(self):
         # Test non-suppoerted file with non-supported extension
         # -----------------------------------------------------