Merge remote-tracking branch 'joar/audio+sniffing'
authorJoar Wandborg <git@wandborg.com>
Tue, 27 Mar 2012 10:05:09 +0000 (12:05 +0200)
committerJoar Wandborg <git@wandborg.com>
Tue, 27 Mar 2012 10:05:09 +0000 (12:05 +0200)
Conflicts:
mediagoblin/media_types/image/processing.py
mediagoblin/media_types/video/__init__.py
mediagoblin/media_types/video/processing.py
mediagoblin/tests/test_submission.py

1  2 
mediagoblin/config_spec.ini
mediagoblin/media_types/ascii/processing.py
mediagoblin/media_types/image/processing.py
mediagoblin/media_types/video/__init__.py
mediagoblin/media_types/video/processing.py
mediagoblin/processing/__init__.py
mediagoblin/static/js/audio.js
mediagoblin/static/js/geolocation-map.js
mediagoblin/submit/views.py
mediagoblin/tests/test_submission.py

index 866d799befaf9ee8145f0e3a9f4538ec37571f36,5c71a9b85c028eb6210717a89b18a6225c5f5147..e30825dedadf142a7e89b19d335eb1f9f443318e
@@@ -69,11 -65,28 +69,28 @@@ base_url = string(default="/mgoblin_med
  storage_class = string(default="mediagoblin.storage.filestorage:BasicFileStorage")
  base_dir = string(default="%(here)s/user_dev/media/queue")
  
+ [media:medium]
+ # Dimensions used when creating media display images.
+ max_width = integer(default=640)
+ max_height = integer(default=640)
+ [media:thumb]
+ # Dimensions used when creating media thumbnails
+ # This is unfortunately not implemented in the media
+ # types yet. You can help!
+ # TODO: Make plugins follow the media size settings
+ max_width = integer(default=180)
+ max_height = integer(default=180)
  
- # Should we keep the original file?
  [media_type:mediagoblin.media_types.video]
+ # Should we keep the original file?
  keep_original = boolean(default=False)
  
 -create_spectrogram = boolean(default=False)
+ [media_type:mediagoblin.media_types.audio]
+ # vorbisenc qualiy
+ quality = float(default=0.3)
++create_spectrogram = boolean(default=True)
  
  [beaker.cache]
  type = string(default="file")
index 43a4a48401b457f9971afbf11993cb2b4362fbbb,bacfecb85e820c25fd57f16eccd2167315f64f7b..bbfcd32d91ecce8f4400548449096bd7985b7411
@@@ -19,41 -20,38 +20,66 @@@ import loggin
  
  from mediagoblin import mg_globals as mgg
  from mediagoblin.processing import BadMediaFail, \
-     create_pub_filepath, THUMB_SIZE, MEDIUM_SIZE, FilenameBuilder
 -    create_pub_filepath
++    create_pub_filepath, FilenameBuilder
  from mediagoblin.tools.exif import exif_fix_image_orientation, \
 -    extract_exif, clean_exif, get_gps_data, get_useful
 +    extract_exif, clean_exif, get_gps_data, get_useful, \
 +    exif_image_needs_rotation
  
-     size_limits (optional) -- image is only resized if it exceeds this size
+ _log = logging.getLogger(__name__)
++
 +def resize_image(entry, filename, new_path, exif_tags, workdir, new_size,
 +                 size_limits=(0, 0)):
 +    """Store a resized version of an image and return its pathname.
 +
 +    Arguments:
 +    entry -- the entry for the image to resize
 +    filename -- the filename of the original image being resized
 +    new_path -- public file path for the new resized image
 +    exif_tags -- EXIF data for the original image
 +    workdir -- directory path for storing converted image files
 +    new_size -- 2-tuple size for the resized image
-     if ((resized.size[0] > size_limits[0]) or
-         (resized.size[1] > size_limits[1])):
-         resized.thumbnail(new_size, Image.ANTIALIAS)
 +    """
 +    try:
 +        resized = Image.open(filename)
 +    except IOError:
 +        raise BadMediaFail()
 +    resized = exif_fix_image_orientation(resized, exif_tags)  # Fix orientation
++    resized.thumbnail(new_size, Image.ANTIALIAS)
 +
 +    # Copy the new file to the conversion subdir, then remotely.
 +    tmp_resized_filename = os.path.join(workdir, new_path[-1])
 +    with file(tmp_resized_filename, 'w') as resized_file:
 +        resized.save(resized_file)
 +    mgg.public_store.copy_local_to_storage(tmp_resized_filename, new_path)
 +
++
+ SUPPORTED_FILETYPES = ['png', 'gif', 'jpg', 'jpeg']
+ def sniff_handler(media_file, **kw):
+     if kw.get('media') is not None:  # That's a double negative!
+         name, ext = os.path.splitext(kw['media'].filename)
+         clean_ext = ext[1:].lower()  # Strip the . from ext and make lowercase
+         _log.debug('name: {0}\next: {1}\nlower_ext: {2}'.format(
+                 name,
+                 ext,
+                 clean_ext))
+         if clean_ext in SUPPORTED_FILETYPES:
+             _log.info('Found file extension in supported filetypes')
+             return True
+         else:
+             _log.debug('Media present, extension not found in {0}'.format(
+                     SUPPORTED_FILETYPES))
+     else:
+         _log.warning('Need additional information (keyword argument \'media\')'
+                      ' to be able to handle sniffing')
+     return False
  def process_image(entry):
      """
      Code to process an image
      exif_tags = extract_exif(queued_filename)
      gps_data = get_gps_data(exif_tags)
  
 -    try:
 -        thumb = Image.open(queued_filename)
 -    except IOError:
 -        raise BadMediaFail()
 -
 -    thumb = exif_fix_image_orientation(thumb, exif_tags)
 -
 -    thumb.thumbnail(
 -        (mgg.global_config['media:thumb']['max_width'],
 -         mgg.global_config['media:thumb']['max_height']),
 -        Image.ANTIALIAS)
 -
 -    # Copy the thumb to the conversion subdir, then remotely.
 -    thumb_filename = 'thumbnail' + extension
 -    thumb_filepath = create_pub_filepath(entry, thumb_filename)
 -
 -    tmp_thumb_filename = os.path.join(
 -        conversions_subdir, thumb_filename)
 -
 -    with file(tmp_thumb_filename, 'w') as thumb_file:
 -        thumb.save(thumb_file)
 -
 -    mgg.public_store.copy_local_to_storage(
 -        tmp_thumb_filename, thumb_filepath)
 +    # Always create a small thumbnail
 +    thumb_filepath = create_pub_filepath(
 +        entry, name_builder.fill('{basename}.thumbnail{ext}'))
 +    resize_image(entry, queued_filename, thumb_filepath,
-                  exif_tags, conversions_subdir, THUMB_SIZE)
++                exif_tags, conversions_subdir,
++                (mgg.global_config['media:thumb']['max_width'],
++                 mgg.global_config['media:thumb']['max_height']))
  
      # If the size of the original file exceeds the specified size of a `medium`
 -    # file, a `medium.jpg` files is created and later associated with the media
 +    # file, a `.medium.jpg` files is created and later associated with the media
      # entry.
      medium = Image.open(queued_filename)
-     if medium.size[0] > MEDIUM_SIZE[0] or medium.size[1] > MEDIUM_SIZE[1] \
-             or exif_image_needs_rotation(exif_tags):
 -
 -    # Fix orientation
 -    medium = exif_fix_image_orientation(medium, exif_tags)
 -
+     if medium.size[0] > mgg.global_config['media:medium']['max_width'] \
 -        or medium.size[1] > mgg.global_config['media:medium']['max_height']:
 -        medium.thumbnail(
++        or medium.size[1] > mgg.global_config['media:medium']['max_height'] \
++        or exif_image_needs_rotation(exif_tags):
 +        medium_filepath = create_pub_filepath(
 +            entry, name_builder.fill('{basename}.medium{ext}'))
 +        resize_image(
 +            entry, queued_filename, medium_filepath,
-             exif_tags, conversions_subdir, MEDIUM_SIZE, MEDIUM_SIZE)
++            exif_tags, conversions_subdir,
+             (mgg.global_config['media:medium']['max_width'],
 -             mgg.global_config['media:medium']['max_height']),
 -            Image.ANTIALIAS)
 -
 -    medium_filename = 'medium' + extension
 -    medium_filepath = create_pub_filepath(entry, medium_filename)
 -
 -    tmp_medium_filename = os.path.join(
 -        conversions_subdir, medium_filename)
 -
 -    with file(tmp_medium_filename, 'w') as medium_file:
 -        medium.save(medium_file)
 -
 -    mgg.public_store.copy_local_to_storage(
 -        tmp_medium_filename, medium_filepath)
++             mgg.global_config['media:medium']['max_height']))
 +    else:
 +        medium_filepath = None
  
      # we have to re-read because unlike PIL, not everything reads
      # things in string representation :)
      queued_file = file(queued_filename, 'rb')
  
      with queued_file:
 -        #create_pub_filepath(entry, queued_filepath[-1])
 -        original_filepath = create_pub_filepath(entry, basename + extension)
 +        original_filepath = create_pub_filepath(
-             entry, name_builder.fill('{basename}{ext}') )
++            entry, name_builder.fill('{basename}{ext}'))
  
          with mgg.public_store.get_file(original_filepath, 'wb') \
              as original_file:
index 579fdc6a64c372641940918466293ee86b4e7854,a53927d52f74c550f93b6f13fdf2fb6a5a7670c2..3faa5b9fad70e7a78b81cfe243836699ddd2fa77
@@@ -19,11 -20,10 +20,10 @@@ from mediagoblin.media_types.video.proc
  
  MEDIA_MANAGER = {
      "human_readable": "Video",
--    "processor": process_video, # alternately a string,
--                                # 'mediagoblin.media_types.image.processing'?
++    "processor": process_video,  # alternately a string,
++                                 # 'mediagoblin.media_types.image.processing'?
+     "sniff_handler": sniff_handler,
      "display_template": "mediagoblin/media_displays/video.html",
      "default_thumb": "images/media_thumbs/video.jpg",
-     # TODO: This list should be autogenerated based on gst plugins
      "accepted_extensions": [
-         "mp4", "mov", "webm", "avi", "3gp", "3gpp", "mkv", "ogv", "ogg",
-         "m4v"]}
 -        "mp4", "mov", "webm", "avi", "3gp", "3gpp", "mkv", "ogv"]}
++        "mp4", "mov", "webm", "avi", "3gp", "3gpp", "mkv", "ogv", "m4v"]}
index 24c036488199707015567089484dfae0e95e5d33,4c44e65f593143a2f3c3367001f44606211dd9ca..6a5ce3646668573097faf0dd8ebfcabbfa3e31e9
@@@ -19,8 -19,7 +19,8 @@@ import loggin
  import os
  
  from mediagoblin import mg_globals as mgg
 -from mediagoblin.processing import create_pub_filepath
 +from mediagoblin.processing import mark_entry_failed, \
-     THUMB_SIZE, MEDIUM_SIZE, create_pub_filepath, FilenameBuilder
++    create_pub_filepath, FilenameBuilder
  from . import transcoders
  
  logging.basicConfig()
index 9dee3baa465b23d141c676564539e7c319bc0ad2,989591de33634b9e6f3b2e69613f6706de48cf32..4a827af490c97374268f6361abaeb7de995690e0
@@@ -22,11 -23,10 +22,8 @@@ from mediagoblin import mg_globals as m
  
  from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
  
 -from mediagoblin.media_types import get_media_manager
 -
  _log = logging.getLogger(__name__)
  
- THUMB_SIZE = 180, 180
- MEDIUM_SIZE = 640, 640
  
  def create_pub_filepath(entry, filename):
      return mgg.public_store.get_unique_filepath(
index 0000000000000000000000000000000000000000,8c91bd0b9fb35b054b7d9e794a6d3bf16dcd503b..91d52f960df9e95f306a5fb9f19ba285a17a6959
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,140 +1,146 @@@
+ /**
+  * 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 () {
++    if (!$('.audio-media').length) {
++        return;
++    }
++
++    console.log('Initializing audio player');
++
+     audioElements = $('.audio-media .audio-player');
+     audioPlayer.init(audioElements[0]);
+     audioPlayer.attachToImage($('.audio-spectrogram img')[0]);
+ });
index a2c620458baf93bd94484176fd2682c8c08bb445,a2c620458baf93bd94484176fd2682c8c08bb445..de49a37ddcd0ebfe9918ec4240c1f23b22ba9f22
   */
  
  $(document).ready(function () {
++    if (!$('#tile-map').length) {
++        return;
++    }
++    console.log('Initializing map');
++
      var longitude = Number(
        $('#tile-map #gps-longitude').val());
      var latitude = Number(
index 1df676ab7fb7eba197db3bb98aeaab1776f2ccda,4fafe1e32b3e6d6d2ac476cc0e3cf17d634d6775..517fb646cb07289cbe25676bedffb6f4032867b0
@@@ -20,7 -20,7 +20,8 @@@ from os.path import splitex
  from cgi import FieldStorage
  
  from celery import registry
--import urllib,urllib2
++import urllib
++import urllib2
  import logging
  
  _log = logging.getLogger(__name__)
@@@ -32,11 -32,10 +33,11 @@@ from mediagoblin.tools.text import conv
  from mediagoblin.tools.translate import pass_to_ugettext as _
  from mediagoblin.tools.response import render_to_response, redirect
  from mediagoblin.decorators import require_active_login
 -from mediagoblin.submit import forms as submit_forms, security
 -from mediagoblin.processing import mark_entry_failed, ProcessMedia
 +from mediagoblin.submit import forms as submit_forms
 +from mediagoblin.processing import mark_entry_failed
 +from mediagoblin.processing.task import ProcessMedia
  from mediagoblin.messages import add_message, SUCCESS
- from mediagoblin.media_types import get_media_type_and_manager, \
+ from mediagoblin.media_types import sniff_media, \
      InvalidFileType, FileTypeNotSupported
  
  
@@@ -131,9 -136,9 +136,10 @@@ def submit_start(request)
                      raise
  
                  if mg_globals.app_config["push_urls"]:
--                    feed_url=request.urlgen(
++                    feed_url = request.urlgen(
                                         'mediagoblin.user_pages.atom_feed',
--                                       qualified=True,user=request.user.username)
++                                       qualified=True,
++                                       user=request.user.username)
                      hubparameters = {
                          'hub.mode': 'publish',
                          'hub.url': feed_url}
                                  user=request.user.username)
              except Exception as e:
                  '''
--                This section is intended to catch exceptions raised in 
++                This section is intended to catch exceptions raised in
                  mediagobling.media_types
                  '''
                  if isinstance(e, InvalidFileType) or \
                          isinstance(e, FileTypeNotSupported):
                      submit_form.file.errors.append(
index ba80ba2086d65afb06eeefc278da61528676b6fb,702741b4adf6ab02b717595b4f6b0cf1d959d900..8bf7d13c36fba0c0fca44ce0941d617b52fe2838
  # along with this program.  If not, see <http://www.gnu.org/licenses/>.
  
  import urlparse
 -import pkg_resources
 +import os
  import re
- import time
  
  from nose.tools import assert_equal, assert_true, assert_false
 +from pkg_resources import resource_filename
  
--from mediagoblin.tests.tools import setup_fresh_app, get_test_app, \
++from mediagoblin.tests.tools import get_test_app, \
      fixture_add_user
  from mediagoblin import mg_globals
- from mediagoblin.processing import create_pub_filepath
--from mediagoblin.tools import template, common
 -
 -GOOD_JPG = pkg_resources.resource_filename(
 -  'mediagoblin.tests', 'test_submission/good.jpg')
 -GOOD_PNG = pkg_resources.resource_filename(
 -  'mediagoblin.tests', 'test_submission/good.png')
 -EVIL_FILE = pkg_resources.resource_filename(
 -  'mediagoblin.tests', 'test_submission/evil')
 -EVIL_JPG = pkg_resources.resource_filename(
 -  'mediagoblin.tests', 'test_submission/evil.jpg')
 -EVIL_PNG = pkg_resources.resource_filename(
 -  'mediagoblin.tests', 'test_submission/evil.png')
++from mediagoblin.tools import template
++
 +
 +def resource(filename):
 +    return resource_filename('mediagoblin.tests', 'test_submission/' + filename)
 +
++
 +GOOD_JPG = resource('good.jpg')
 +GOOD_PNG = resource('good.png')
 +EVIL_FILE = resource('evil')
 +EVIL_JPG = resource('evil.jpg')
 +EVIL_PNG = resource('evil.png')
 +BIG_BLUE = resource('bigblue.png')
  
  GOOD_TAG_STRING = 'yin,yang'
  BAD_TAG_STRING = 'rage,' + 'f' * 26 + 'u' * 26
  
 +FORM_CONTEXT = ['mediagoblin/submit/start.html', 'submit_form']
 +REQUEST_CONTEXT = ['mediagoblin/user_pages/user.html', 'request']
 +
  class TestSubmission:
      def setUp(self):
          self.test_app = get_test_app()
      def logout(self):
          self.test_app.get('/auth/logout/')
  
-         
 +    def do_post(self, data, *context_keys, **kwargs):
 +        url = kwargs.pop('url', '/submit/')
 +        do_follow = kwargs.pop('do_follow', False)
 +        template.clear_test_template_context()
 +        response = self.test_app.post(url, data, **kwargs)
 +        if do_follow:
 +            response.follow()
 +        context_data = template.TEMPLATE_TEST_CONTEXT
 +        for key in context_keys:
 +            context_data = context_data[key]
 +        return response, context_data
++
 +    def upload_data(self, filename):
 +        return {'upload_files': [('file', filename)]}
 +
 +    def check_comments(self, request, media, count):
 +        comments = request.db.MediaComment.find({'media_entry': media._id})
 +        assert_equal(count, len(list(comments)))
 +
      def test_missing_fields(self):
          # Test blank form
          # ---------------
  
          # Test blank file
          # ---------------
 -        template.clear_test_template_context()
 -        response = self.test_app.post(
 -            '/submit/', {
 -                'title': 'test title'})
 -        context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/submit/start.html']
 -        form = context['submit_form']
 -        assert form.file.errors == [u'You must provide a file.']
 -
 -
 -    def test_normal_uploads(self):
 -        # Test JPG
 -        # --------
 -        template.clear_test_template_context()
 -        response = self.test_app.post(
 -            '/submit/', {
 -                'title': 'Normal upload 1'
 -                }, upload_files=[(
 -                    'file', GOOD_JPG)])
 +        response, form = self.do_post({'title': 'test title'}, *FORM_CONTEXT)
 +        assert_equal(form.file.errors, [u'You must provide a file.'])
  
 -        # User should be redirected
 -        response.follow()
 -        assert_equal(
 -            urlparse.urlsplit(response.location)[2],
 -            '/u/chris/')
 -        assert template.TEMPLATE_TEST_CONTEXT.has_key(
 -            'mediagoblin/user_pages/user.html')
 +    def check_url(self, response, path):
 +        assert_equal(urlparse.urlsplit(response.location)[2], path)
  
-         assert_true(context.has_key('mediagoblin/user_pages/user.html'))
 +    def check_normal_upload(self, title, filename):
 +        response, context = self.do_post({'title': title}, do_follow=True,
 +                                         **self.upload_data(filename))
 +        self.check_url(response, '/u/{0}/'.format(self.test_user.username))
++        assert_true('mediagoblin/user_pages/user.html' in context)
          # Make sure the media view is at least reachable, logged in...
 -        self.test_app.get('/u/chris/m/normal-upload-1/')
 +        url = '/u/{0}/m/{1}/'.format(self.test_user.username,
 +                                     title.lower().replace(' ', '-'))
 +        self.test_app.get(url)
          # ... and logged out too.
          self.logout()
 -        self.test_app.get('/u/chris/m/normal-upload-1/')
 -        # Log back in for the remaining tests.
 -        self.login()
 +        self.test_app.get(url)
  
 -        # Test PNG
 -        # --------
 -        template.clear_test_template_context()
 -        response = self.test_app.post(
 -            '/submit/', {
 -                'title': 'Normal upload 2'
 -                }, upload_files=[(
 -                    'file', GOOD_PNG)])
 +    def test_normal_jpg(self):
 +        self.check_normal_upload('Normal upload 1', GOOD_JPG)
  
 -        response.follow()
 -        assert_equal(
 -            urlparse.urlsplit(response.location)[2],
 -            '/u/chris/')
 -        assert template.TEMPLATE_TEST_CONTEXT.has_key(
 -            'mediagoblin/user_pages/user.html')
 +    def test_normal_png(self):
 +        self.check_normal_upload('Normal upload 2', GOOD_PNG)
 +
 +    def check_media(self, request, find_data, count=None):
 +        media = request.db.MediaEntry.find(find_data)
 +        if count is not None:
 +            assert_equal(media.count(), count)
 +            if count == 0:
 +                return
 +        return media[0]
  
      def test_tags(self):
          # Good tag string
  
          # Confirm deletion
          # ---------------------------------------------------
 -        response = self.test_app.post(
 -            request.urlgen('mediagoblin.user_pages.media_confirm_delete',
 -                           # No work: user=media.uploader().username,
 -                           user=self.test_user.username,
 -                           media=media._id),
 -            {'confirm': 'y'})
 -
 -        response.follow()
 -
 -        request = template.TEMPLATE_TEST_CONTEXT[
 -            'mediagoblin/user_pages/user.html']['request']
 +        response, request = self.do_post({'confirm': 'y'}, *REQUEST_CONTEXT,
 +                                         do_follow=True, url=delete_url)
 +        self.check_media(request, {'_id': media._id}, 0)
 +        self.check_comments(request, media, 0)
  
 -        # Does media entry still exist?
 -        assert_false(
 -            request.db.MediaEntry.find(
 -                {'_id': media._id}).count())
 +    def test_evil_file(self):
 +        # Test non-suppoerted file with non-supported extension
 +        # -----------------------------------------------------
 +        response, form = self.do_post({'title': 'Malicious Upload 1'},
 +                                      *FORM_CONTEXT,
 +                                      **self.upload_data(EVIL_FILE))
 +        assert_equal(len(form.file.errors), 1)
 +        assert_true(re.match(
 +                r'^Could not extract any file extension from ".*?"$',
 +                str(form.file.errors[0])))
  
 -    def test_malicious_uploads(self):
 -        # Test non-suppoerted file with non-supported extension
 -        # -----------------------------------------------------
 -        template.clear_test_template_context()
 -        response = self.test_app.post(
 -            '/submit/', {
 -                'title': 'Malicious Upload 1'
 -                }, upload_files=[(
 -                    'file', EVIL_FILE)])
 -
 -        context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/submit/start.html']
 -        form = context['submit_form']
 -        assert 'Sorry, I don\'t support that file type :(' == \
 -            str(form.file.errors[0])
 -        assert len(form.file.errors) == 1
 -
+     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'
-         # NOTE: These images should ultimately fail, but they
 +    def check_false_image(self, title, filename):
+         # NOTE: The following 2 tests will ultimately fail, but they
          #   *will* pass the initial form submission step.  Instead,
          #   they'll be caught as failures during the processing step.
 +        response, context = self.do_post({'title': title}, do_follow=True,
 +                                         **self.upload_data(filename))
 +        self.check_url(response, '/u/{0}/'.format(self.test_user.username))
 +        entry = mg_globals.database.MediaEntry.find_one({'title': title})
 +        assert_equal(entry.state, 'failed')
 +        assert_equal(entry.fail_error, u'mediagoblin.processing:BadMediaFail')
  
 +    def test_evil_jpg(self):
          # Test non-supported file with .jpg extension
          # -------------------------------------------
 -        template.clear_test_template_context()
 -        response = self.test_app.post(
 -           '/submit/', {
 -               'title': 'Malicious Upload 2'
 -               }, upload_files=[(
 -                   'file', EVIL_JPG)])
 -        response.follow()
 -        assert_equal(
 -            urlparse.urlsplit(response.location)[2],
 -            '/u/chris/')
 -
 -        entry = mg_globals.database.MediaEntry.find_one(
 -            {'title': 'Malicious Upload 2'})
 -        assert_equal(entry.state, 'failed')
 -        assert_equal(
 -            entry['fail_error'],
 -            u'mediagoblin.processing:BadMediaFail')
 +        self.check_false_image('Malicious Upload 2', EVIL_JPG)
  
 +    def test_evil_png(self):
          # Test non-supported file with .png extension
          # -------------------------------------------
 -        template.clear_test_template_context()
 -        response = self.test_app.post(
 -           '/submit/', {
 -               'title': 'Malicious Upload 3'
 -               }, upload_files=[(
 -                   'file', EVIL_PNG)])
 -        response.follow()
 -        assert_equal(
 -            urlparse.urlsplit(response.location)[2],
 -            '/u/chris/')
 -
 -        entry = mg_globals.database.MediaEntry.find_one(
 -            {'title': 'Malicious Upload 3'})
 -        assert_equal(entry.state, 'failed')
 -        assert_equal(
 -            entry['fail_error'],
 -            u'mediagoblin.processing:BadMediaFail')
 +        self.check_false_image('Malicious Upload 3', EVIL_PNG)
 +
 +    def test_processing(self):
 +        data = {'title': 'Big Blue'}
 +        response, request = self.do_post(data, *REQUEST_CONTEXT, do_follow=True,
 +                                         **self.upload_data(BIG_BLUE))
 +        media = self.check_media(request, data, 1)
 +        last_size = 1024 ** 3  # Needs to be larger than bigblue.png
 +        for key, basename in (('original', 'bigblue.png'),
 +                              ('medium', 'bigblue.medium.png'),
 +                              ('thumb', 'bigblue.thumbnail.png')):
 +            # Does the processed image have a good filename?
 +            filename = resource_filename(
 +                'mediagoblin.tests',
 +                os.path.join('test_user_dev/media/public',
 +                             *media['media_files'].get(key, [])))
 +            assert_true(filename.endswith('_' + basename))
 +            # Is it smaller than the last processed image we looked at?
 +            size = os.stat(filename).st_size
 +            assert_true(last_size > size)
 +            last_size = size