aboutsummaryrefslogtreecommitdiffstats
path: root/mediagoblin
diff options
context:
space:
mode:
Diffstat (limited to 'mediagoblin')
-rw-r--r--mediagoblin/config_spec.ini19
-rw-r--r--mediagoblin/media_types/__init__.py60
-rw-r--r--mediagoblin/media_types/ascii/__init__.py4
-rw-r--r--mediagoblin/media_types/ascii/asciitoimage.py1
-rw-r--r--mediagoblin/media_types/ascii/processing.py22
-rw-r--r--mediagoblin/media_types/audio/__init__.py25
l---------mediagoblin/media_types/audio/audioprocessing.py1
-rw-r--r--mediagoblin/media_types/audio/processing.py131
-rw-r--r--mediagoblin/media_types/audio/transcoders.py237
-rw-r--r--mediagoblin/media_types/image/__init__.py4
-rw-r--r--mediagoblin/media_types/image/processing.py55
-rw-r--r--mediagoblin/media_types/video/__init__.py12
-rw-r--r--mediagoblin/media_types/video/processing.py33
-rw-r--r--mediagoblin/media_types/video/transcoders.py82
-rw-r--r--mediagoblin/processing/__init__.py3
-rw-r--r--mediagoblin/static/css/audio.css53
-rw-r--r--mediagoblin/static/js/audio.js146
-rw-r--r--mediagoblin/static/js/geolocation-map.js5
-rw-r--r--mediagoblin/submit/views.py19
-rw-r--r--mediagoblin/templates/mediagoblin/media_displays/audio.html61
-rw-r--r--mediagoblin/tests/test_submission.py37
21 files changed, 920 insertions, 90 deletions
diff --git a/mediagoblin/config_spec.ini b/mediagoblin/config_spec.ini
index 866d799b..e30825de 100644
--- a/mediagoblin/config_spec.ini
+++ b/mediagoblin/config_spec.ini
@@ -69,11 +69,28 @@ base_url = string(default="/mgoblin_media/")
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)
+[media_type:mediagoblin.media_types.audio]
+# vorbisenc qualiy
+quality = float(default=0.3)
+create_spectrogram = boolean(default=True)
+
[beaker.cache]
type = string(default="file")
diff --git a/mediagoblin/media_types/__init__.py b/mediagoblin/media_types/__init__.py
index 5128826b..93d2319f 100644
--- a/mediagoblin/media_types/__init__.py
+++ b/mediagoblin/media_types/__init__.py
@@ -16,10 +16,13 @@
import os
import sys
+import logging
+import tempfile
from mediagoblin import mg_globals
from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
+_log = logging.getLogger(__name__)
class FileTypeNotSupported(Exception):
pass
@@ -28,6 +31,35 @@ class InvalidFileType(Exception):
pass
+def sniff_media(media):
+ '''
+ Iterate through the enabled media types and find those suited
+ for a certain file.
+ '''
+
+ 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
+ _(u'Sorry, I don\'t support that file type :('))
+
+
def get_media_types():
"""
Generator, yields the available media types
@@ -42,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
@@ -67,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 :('))
diff --git a/mediagoblin/media_types/ascii/__init__.py b/mediagoblin/media_types/ascii/__init__.py
index 1c8ca562..856d1d7b 100644
--- a/mediagoblin/media_types/ascii/__init__.py
+++ b/mediagoblin/media_types/ascii/__init__.py
@@ -14,13 +14,15 @@
# 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/>.
-from mediagoblin.media_types.ascii.processing import process_ascii
+from mediagoblin.media_types.ascii.processing import process_ascii, \
+ sniff_handler
MEDIA_MANAGER = {
"human_readable": "ASCII",
"processor": process_ascii, # alternately a string,
# 'mediagoblin.media_types.image.processing'?
+ "sniff_handler": sniff_handler,
"display_template": "mediagoblin/media_displays/ascii.html",
"default_thumb": "images/media_thumbs/ascii.jpg",
"accepted_extensions": [
diff --git a/mediagoblin/media_types/ascii/asciitoimage.py b/mediagoblin/media_types/ascii/asciitoimage.py
index e1c4fb44..3017d2ad 100644
--- a/mediagoblin/media_types/ascii/asciitoimage.py
+++ b/mediagoblin/media_types/ascii/asciitoimage.py
@@ -23,6 +23,7 @@ import os
_log = logging.getLogger(__name__)
+
class AsciiToImage(object):
'''
Converter of ASCII art into image files, preserving whitespace
diff --git a/mediagoblin/media_types/ascii/processing.py b/mediagoblin/media_types/ascii/processing.py
index 83b5ea33..a2a52e9d 100644
--- a/mediagoblin/media_types/ascii/processing.py
+++ b/mediagoblin/media_types/ascii/processing.py
@@ -19,11 +19,25 @@ import Image
import logging
from mediagoblin import mg_globals as mgg
-from mediagoblin.processing import create_pub_filepath, THUMB_SIZE
+from mediagoblin.processing import create_pub_filepath
from mediagoblin.media_types.ascii import asciitoimage
_log = logging.getLogger(__name__)
+SUPPORTED_EXTENSIONS = ['txt', 'asc', 'nfo']
+
+
+def sniff_handler(media_file, **kw):
+ if kw.get('media') is not None:
+ name, ext = os.path.splitext(kw['media'].filename)
+ clean_ext = ext[1:].lower()
+
+ if clean_ext in SUPPORTED_EXTENSIONS:
+ return True
+
+ return False
+
+
def process_ascii(entry):
'''
Code to process a txt file
@@ -69,7 +83,10 @@ def process_ascii(entry):
queued_file.read())
with file(tmp_thumb_filename, 'w') as thumb_file:
- thumb.thumbnail(THUMB_SIZE, Image.ANTIALIAS)
+ thumb.thumbnail(
+ (mgg.global_config['media:thumb']['max_width'],
+ mgg.global_config['media:thumb']['max_height']),
+ Image.ANTIALIAS)
thumb.save(thumb_file)
_log.debug('Copying local file to public storage')
@@ -84,7 +101,6 @@ def process_ascii(entry):
as original_file:
original_file.write(queued_file.read())
-
queued_file.seek(0) # Rewind *again*
unicode_filepath = create_pub_filepath(entry, 'ascii-portable.txt')
diff --git a/mediagoblin/media_types/audio/__init__.py b/mediagoblin/media_types/audio/__init__.py
new file mode 100644
index 00000000..9b33f9e3
--- /dev/null
+++ b/mediagoblin/media_types/audio/__init__.py
@@ -0,0 +1,25 @@
+# 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/>.
+
+from mediagoblin.media_types.audio.processing import process_audio, \
+ sniff_handler
+
+MEDIA_MANAGER = {
+ 'human_readable': 'Audio',
+ 'processor': process_audio,
+ 'sniff_handler': sniff_handler,
+ 'display_template': 'mediagoblin/media_displays/audio.html',
+ 'accepted_extensions': ['mp3', 'flac', 'ogg', 'wav', 'm4a']}
diff --git a/mediagoblin/media_types/audio/audioprocessing.py b/mediagoblin/media_types/audio/audioprocessing.py
new file mode 120000
index 00000000..c5e3c52c
--- /dev/null
+++ b/mediagoblin/media_types/audio/audioprocessing.py
@@ -0,0 +1 @@
+../../../extlib/freesound/audioprocessing.py \ No newline at end of file
diff --git a/mediagoblin/media_types/audio/processing.py b/mediagoblin/media_types/audio/processing.py
new file mode 100644
index 00000000..62daf412
--- /dev/null
+++ b/mediagoblin/media_types/audio/processing.py
@@ -0,0 +1,131 @@
+# 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/>.
+
+import logging
+import tempfile
+import os
+
+from mediagoblin import mg_globals as mgg
+from mediagoblin.processing import create_pub_filepath, BadMediaFail
+
+from mediagoblin.media_types.audio.transcoders import AudioTranscoder, \
+ AudioThumbnailer
+
+_log = logging.getLogger(__name__)
+
+def sniff_handler(media_file, **kw):
+ try:
+ transcoder = AudioTranscoder()
+ data = transcoder.discover(media_file.name)
+ except BadMediaFail:
+ _log.debug('Audio discovery raised BadMediaFail')
+ return False
+
+ if data.is_audio == True and data.is_video == False:
+ return True
+
+ return False
+
+def process_audio(entry):
+ audio_config = mgg.global_config['media_type:mediagoblin.media_types.audio']
+
+ workbench = mgg.workbench_manager.create_workbench()
+
+ queued_filepath = entry.queued_media_file
+ queued_filename = workbench.localized_file(
+ mgg.queue_store, queued_filepath,
+ 'source')
+
+ ogg_filepath = create_pub_filepath(
+ entry,
+ '{original}.webm'.format(
+ original=os.path.splitext(
+ queued_filepath[-1])[0]))
+
+ transcoder = AudioTranscoder()
+
+ with tempfile.NamedTemporaryFile() as ogg_tmp:
+
+ transcoder.transcode(
+ queued_filename,
+ ogg_tmp.name,
+ quality=audio_config['quality'])
+
+ data = transcoder.discover(ogg_tmp.name)
+
+ _log.debug('Saving medium...')
+ mgg.public_store.get_file(ogg_filepath, 'wb').write(
+ ogg_tmp.read())
+
+ entry.media_files['ogg'] = ogg_filepath
+
+ entry.media_data['audio'] = {
+ u'length': int(data.audiolength)}
+
+ if audio_config['create_spectrogram']:
+ spectrogram_filepath = create_pub_filepath(
+ entry,
+ '{original}-spectrogram.jpg'.format(
+ original=os.path.splitext(
+ queued_filepath[-1])[0]))
+
+ with tempfile.NamedTemporaryFile(suffix='.wav') as wav_tmp:
+ _log.info('Creating WAV source for spectrogram')
+ transcoder.transcode(
+ queued_filename,
+ wav_tmp.name,
+ mux_string='wavenc')
+
+ thumbnailer = AudioThumbnailer()
+
+ with tempfile.NamedTemporaryFile(suffix='.jpg') as spectrogram_tmp:
+ thumbnailer.spectrogram(
+ wav_tmp.name,
+ spectrogram_tmp.name,
+ width=mgg.global_config['media:medium']['max_width'])
+
+ _log.debug('Saving spectrogram...')
+ mgg.public_store.get_file(spectrogram_filepath, 'wb').write(
+ spectrogram_tmp.read())
+
+ entry.media_files['spectrogram'] = spectrogram_filepath
+
+ with tempfile.NamedTemporaryFile(suffix='.jpg') as thumb_tmp:
+ thumbnailer.thumbnail_spectrogram(
+ spectrogram_tmp.name,
+ thumb_tmp.name,
+ (mgg.global_config['media:thumb']['max_width'],
+ mgg.global_config['media:thumb']['max_height']))
+
+ thumb_filepath = create_pub_filepath(
+ entry,
+ '{original}-thumbnail.jpg'.format(
+ original=os.path.splitext(
+ queued_filepath[-1])[0]))
+
+ mgg.public_store.get_file(thumb_filepath, 'wb').write(
+ thumb_tmp.read())
+
+ entry.media_files['thumb'] = thumb_filepath
+ else:
+ entry.media_files['thumb'] = ['fake', 'thumb', 'path.jpg']
+
+ mgg.queue_store.delete_file(queued_filepath)
+
+ entry.save()
+
+ # clean up workbench
+ workbench.destroy_self()
diff --git a/mediagoblin/media_types/audio/transcoders.py b/mediagoblin/media_types/audio/transcoders.py
new file mode 100644
index 00000000..f84ab7f2
--- /dev/null
+++ b/mediagoblin/media_types/audio/transcoders.py
@@ -0,0 +1,237 @@
+# 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/>.
+
+import pdb
+import logging
+from PIL import Image
+
+from mediagoblin.processing import BadMediaFail
+from mediagoblin.media_types.audio import audioprocessing
+
+
+_log = logging.getLogger(__name__)
+
+CPU_COUNT = 2 # Just assuming for now
+
+# IMPORT MULTIPROCESSING
+try:
+ import multiprocessing
+ try:
+ CPU_COUNT = multiprocessing.cpu_count()
+ except NotImplementedError:
+ _log.warning('multiprocessing.cpu_count not implemented!\n'
+ 'Assuming 2 CPU cores')
+except ImportError:
+ _log.warning('Could not import multiprocessing, assuming 2 CPU cores')
+
+# IMPORT GOBJECT
+try:
+ import gobject
+ gobject.threads_init()
+except ImportError:
+ raise Exception('gobject could not be found')
+
+# IMPORT PYGST
+try:
+ import pygst
+
+ # We won't settle for less. For now, this is an arbitrary limit
+ # as we have not tested with > 0.10
+ pygst.require('0.10')
+
+ import gst
+
+ import gst.extend.discoverer
+except ImportError:
+ raise Exception('gst/pygst >= 0.10 could not be imported')
+
+import numpy
+
+
+class AudioThumbnailer(object):
+ def __init__(self):
+ _log.info('Initializing {0}'.format(self.__class__.__name__))
+
+ def spectrogram(self, src, dst, **kw):
+ width = kw['width']
+ height = int(kw.get('height', float(width) * 0.3))
+ fft_size = kw.get('fft_size', 2048)
+ callback = kw.get('progress_callback')
+
+ processor = audioprocessing.AudioProcessor(
+ src,
+ fft_size,
+ numpy.hanning)
+
+ samples_per_pixel = processor.audio_file.nframes / float(width)
+
+ spectrogram = audioprocessing.SpectrogramImage(width, height, fft_size)
+
+ for x in range(width):
+ if callback and x % (width / 10) == 0:
+ callback((x * 100) / width)
+
+ seek_point = int(x * samples_per_pixel)
+
+ (spectral_centroid, db_spectrum) = processor.spectral_centroid(
+ seek_point)
+
+ spectrogram.draw_spectrum(x, db_spectrum)
+
+ if callback:
+ callback(100)
+
+ spectrogram.save(dst)
+
+ def thumbnail_spectrogram(self, src, dst, thumb_size):
+ '''
+ Takes a spectrogram and creates a thumbnail from it
+ '''
+ if not (type(thumb_size) == tuple and len(thumb_size) == 2):
+ raise Exception('thumb_size argument should be a tuple(width, height)')
+
+ im = Image.open(src)
+
+ im_w, im_h = [float(i) for i in im.size]
+ th_w, th_h = [float(i) for i in thumb_size]
+
+ wadsworth_position = im_w * 0.3
+
+ start_x = max((
+ wadsworth_position - ((im_h * (th_w / th_h)) / 2.0),
+ 0.0))
+
+ stop_x = start_x + (im_h * (th_w / th_h))
+
+ th = im.crop((
+ int(start_x), 0,
+ int(stop_x), int(im_h)))
+
+ if th.size[0] > th_w or th.size[1] > th_h:
+ th.thumbnail(thumb_size, Image.ANTIALIAS)
+
+ th.save(dst)
+
+
+class AudioTranscoder(object):
+ def __init__(self):
+ _log.info('Initializing {0}'.format(self.__class__.__name__))
+
+ # Instantiate MainLoop
+ self._loop = gobject.MainLoop()
+ self._failed = None
+
+ def discover(self, src):
+ self._src_path = src
+ _log.info('Discovering {0}'.format(src))
+ self._discovery_path = src
+
+ self._discoverer = gst.extend.discoverer.Discoverer(
+ self._discovery_path)
+ self._discoverer.connect('discovered', self.__on_discovered)
+ self._discoverer.discover()
+
+ self._loop.run() # Run MainLoop
+
+ if self._failed:
+ raise self._failed
+
+ # Once MainLoop has returned, return discovery data
+ return getattr(self, '_discovery_data', False)
+
+ def __on_discovered(self, data, is_media):
+ if not is_media:
+ self._failed = BadMediaFail()
+ _log.error('Could not discover {0}'.format(self._src_path))
+ self.halt()
+
+ _log.debug('Discovered: {0}'.format(data.__dict__))
+
+ self._discovery_data = data
+
+ # Gracefully shut down MainLoop
+ self.halt()
+
+ def transcode(self, src, dst, **kw):
+ _log.info('Transcoding {0} into {1}'.format(src, dst))
+ self._discovery_data = kw.get('data', self.discover(src))
+
+ self.__on_progress = kw.get('progress_callback')
+
+ quality = kw.get('quality', 0.3)
+
+ mux_string = kw.get(
+ 'mux_string',
+ 'vorbisenc quality={0} ! webmmux'.format(quality))
+
+ # Set up pipeline
+ self.pipeline = gst.parse_launch(
+ 'filesrc location="{src}" ! '
+ 'decodebin2 ! queue ! audiorate tolerance={tolerance} ! '
+ 'audioconvert ! audio/x-raw-float,channels=2 ! '
+ '{mux_string} ! '
+ 'progressreport silent=true ! '
+ 'filesink location="{dst}"'.format(
+ src=src,
+ tolerance=80000000,
+ mux_string=mux_string,
+ dst=dst))
+
+ self.bus = self.pipeline.get_bus()
+ self.bus.add_signal_watch()
+ self.bus.connect('message', self.__on_bus_message)
+
+ self.pipeline.set_state(gst.STATE_PLAYING)
+
+ self._loop.run()
+
+ def __on_bus_message(self, bus, message):
+ _log.debug(message)
+
+ if (message.type == gst.MESSAGE_ELEMENT
+ and message.structure.get_name() == 'progress'):
+ data = dict(message.structure)
+
+ if self.__on_progress:
+ self.__on_progress(data)
+
+ _log.info('{0}% done...'.format(
+ data.get('percent')))
+ elif message.type == gst.MESSAGE_EOS:
+ _log.info('Done')
+ self.halt()
+
+ def halt(self):
+ if getattr(self, 'pipeline', False):
+ self.pipeline.set_state(gst.STATE_NULL)
+ del self.pipeline
+ _log.info('Quitting MainLoop gracefully...')
+ gobject.idle_add(self._loop.quit)
+
+if __name__ == '__main__':
+ import sys
+ logging.basicConfig()
+ _log.setLevel(logging.INFO)
+
+ #transcoder = AudioTranscoder()
+ #data = transcoder.discover(sys.argv[1])
+ #res = transcoder.transcode(*sys.argv[1:3])
+
+ thumbnailer = AudioThumbnailer()
+
+ thumbnailer.spectrogram(*sys.argv[1:], width=640)
+
+ pdb.set_trace()
diff --git a/mediagoblin/media_types/image/__init__.py b/mediagoblin/media_types/image/__init__.py
index 98e0c32a..d4720fab 100644
--- a/mediagoblin/media_types/image/__init__.py
+++ b/mediagoblin/media_types/image/__init__.py
@@ -14,13 +14,15 @@
# 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/>.
-from mediagoblin.media_types.image.processing import process_image
+from mediagoblin.media_types.image.processing import process_image, \
+ sniff_handler
MEDIA_MANAGER = {
"human_readable": "Image",
"processor": process_image, # alternately a string,
# 'mediagoblin.media_types.image.processing'?
+ "sniff_handler": sniff_handler,
"display_template": "mediagoblin/media_displays/image.html",
"default_thumb": "images/media_thumbs/image.jpg",
"accepted_extensions": ["jpg", "jpeg", "png", "gif", "tiff"]}
diff --git a/mediagoblin/media_types/image/processing.py b/mediagoblin/media_types/image/processing.py
index 43a4a484..bbfcd32d 100644
--- a/mediagoblin/media_types/image/processing.py
+++ b/mediagoblin/media_types/image/processing.py
@@ -16,14 +16,18 @@
import Image
import os
+import logging
from mediagoblin import mg_globals as mgg
from mediagoblin.processing import BadMediaFail, \
- create_pub_filepath, THUMB_SIZE, MEDIUM_SIZE, FilenameBuilder
+ create_pub_filepath, FilenameBuilder
from mediagoblin.tools.exif import exif_fix_image_orientation, \
extract_exif, clean_exif, get_gps_data, get_useful, \
exif_image_needs_rotation
+_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.
@@ -35,18 +39,13 @@ def resize_image(entry, filename, new_path, exif_tags, workdir, new_size,
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
- size_limits (optional) -- image is only resized if it exceeds this size
-
"""
try:
resized = Image.open(filename)
except IOError:
raise BadMediaFail()
resized = exif_fix_image_orientation(resized, exif_tags) # Fix orientation
-
- if ((resized.size[0] > size_limits[0]) or
- (resized.size[1] > size_limits[1])):
- resized.thumbnail(new_size, Image.ANTIALIAS)
+ 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])
@@ -54,6 +53,33 @@ def resize_image(entry, filename, new_path, exif_tags, workdir, new_size,
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
@@ -77,19 +103,24 @@ def process_image(entry):
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
# 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):
+ if medium.size[0] > mgg.global_config['media:medium']['max_width'] \
+ 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']))
else:
medium_filepath = None
@@ -99,7 +130,7 @@ def process_image(entry):
with queued_file:
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:
diff --git a/mediagoblin/media_types/video/__init__.py b/mediagoblin/media_types/video/__init__.py
index 579fdc6a..3faa5b9f 100644
--- a/mediagoblin/media_types/video/__init__.py
+++ b/mediagoblin/media_types/video/__init__.py
@@ -14,16 +14,16 @@
# 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/>.
-from mediagoblin.media_types.video.processing import process_video
+from mediagoblin.media_types.video.processing import process_video, \
+ sniff_handler
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", "m4v"]}
diff --git a/mediagoblin/media_types/video/processing.py b/mediagoblin/media_types/video/processing.py
index 24c03648..6a5ce364 100644
--- a/mediagoblin/media_types/video/processing.py
+++ b/mediagoblin/media_types/video/processing.py
@@ -20,7 +20,7 @@ import os
from mediagoblin import mg_globals as mgg
from mediagoblin.processing import mark_entry_failed, \
- THUMB_SIZE, MEDIUM_SIZE, create_pub_filepath, FilenameBuilder
+ create_pub_filepath, FilenameBuilder
from . import transcoders
logging.basicConfig()
@@ -29,17 +29,27 @@ _log = logging.getLogger(__name__)
_log.setLevel(logging.DEBUG)
-def process_video(entry):
- """
- Code to process a video
+def sniff_handler(media_file, **kw):
+ transcoder = transcoders.VideoTranscoder()
+ data = transcoder.discover(media_file.name)
+
+ _log.debug('Discovered: {0}'.format(data))
+
+ if not data:
+ _log.error('Could not discover {0}'.format(
+ kw.get('media')))
+ return False
- Much of this code is derived from the arista-transcoder script in
- the arista PyPI package and changed to match the needs of
- MediaGoblin
+ if data['is_video'] == True:
+ return True
- This function sets up the arista video encoder in some kind of new thread
- and attaches callbacks to that child process, hopefully, the
- entry-complete callback will be called when the video is done.
+ return False
+
+
+def process_video(entry):
+ """
+ Process a video entry, transcode the queued media files (originals) and
+ create a thumbnail for the entry.
"""
video_config = mgg.global_config['media_type:mediagoblin.media_types.video']
@@ -62,7 +72,8 @@ def process_video(entry):
with tmp_dst:
# Transcode queued file to a VP8/vorbis file that fits in a 640x640 square
- transcoder = transcoders.VideoTranscoder(queued_filename, tmp_dst.name)
+ transcoder = transcoders.VideoTranscoder()
+ transcoder.transcode(queued_filename, tmp_dst.name)
# Push transcoded video to public storage
_log.debug('Saving medium...')
diff --git a/mediagoblin/media_types/video/transcoders.py b/mediagoblin/media_types/video/transcoders.py
index 6137c3bf..e0bd0d3d 100644
--- a/mediagoblin/media_types/video/transcoders.py
+++ b/mediagoblin/media_types/video/transcoders.py
@@ -21,12 +21,9 @@ os.putenv('GST_DEBUG_DUMP_DOT_DIR', '/tmp')
import sys
import logging
-import pdb
import urllib
_log = logging.getLogger(__name__)
-logging.basicConfig()
-_log.setLevel(logging.DEBUG)
CPU_COUNT = 2
try:
@@ -38,17 +35,16 @@ try:
pass
except ImportError:
_log.warning('Could not import multiprocessing, defaulting to 2 CPU cores')
- pass
try:
import gtk
-except:
+except ImportError:
raise Exception('Could not find pygtk')
try:
import gobject
gobject.threads_init()
-except:
+except ImportError:
raise Exception('gobject could not be found')
try:
@@ -56,7 +52,7 @@ try:
pygst.require('0.10')
import gst
from gst.extend import discoverer
-except:
+except ImportError:
raise Exception('gst/pygst 0.10 could not be found')
@@ -270,7 +266,7 @@ class VideoThumbnailer:
return 0
try:
- return pipeline.query_duration(gst.FORMAT_TIME)[0]
+ return pipeline.query_duration(gst.FORMAT_TIME)[0]
except gst.QueryError:
return self._get_duration(pipeline, retries + 1)
@@ -320,12 +316,11 @@ class VideoThumbnailer:
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()
@@ -341,10 +336,15 @@ class VideoTranscoder:
that it was refined afterwards and therefore is done more
correctly.
'''
- def __init__(self, src, dst, **kwargs):
+ def __init__(self):
_log.info('Initializing VideoTranscoder...')
self.loop = gobject.MainLoop()
+
+ def transcode(self, src, dst, **kwargs):
+ '''
+ Transcode a video file into a 'medium'-sized version.
+ '''
self.source_path = src
self.destination_path = dst
@@ -358,6 +358,34 @@ class VideoTranscoder:
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()
+
+ if hasattr(self, '_discovered_data'):
+ return self._discovered_data.__dict__
+ else:
+ return None
+
+ def __on_discovered(self, data, is_media):
+ _log.debug('Discovered: {0}'.format(data))
+ 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()
@@ -370,12 +398,14 @@ class VideoTranscoder:
_log.debug('Initializing MainLoop()')
self.loop.run()
- def _setup_discover(self):
+ 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', self.__discovered)
+ self.discoverer.connect(
+ 'discovered',
+ kw.get('discovered_callback', self.__discovered))
def __discovered(self, data, is_media):
'''
@@ -422,7 +452,7 @@ class VideoTranscoder:
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)
@@ -516,7 +546,6 @@ class VideoTranscoder:
# Setup the message bus and connect _on_message to the pipeline
self._setup_bus()
-
def _on_dynamic_pad(self, dbin, pad, islast):
'''
Callback called when ``decodebin2`` has a pad that we can connect to
@@ -561,11 +590,11 @@ class VideoTranscoder:
t = message.type
- if t == gst.MESSAGE_EOS:
+ if message.type == gst.MESSAGE_EOS:
self._discover_dst_and_stop()
_log.info('Done')
- elif t == gst.MESSAGE_ELEMENT:
+ elif message.type == gst.MESSAGE_ELEMENT:
if message.structure.get_name() == 'progress':
data = dict(message.structure)
@@ -587,7 +616,6 @@ class VideoTranscoder:
self.dst_discoverer.discover()
-
def __dst_discovered(self, data, is_media):
self.dst_data = data
@@ -596,8 +624,9 @@ class VideoTranscoder:
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)
@@ -615,14 +644,15 @@ class VideoTranscoder:
if __name__ == '__main__':
os.nice(19)
+ logging.basicConfig()
from optparse import OptionParser
parser = OptionParser(
- usage='%prog [-v] -a [ video | thumbnail ] SRC DEST')
+ usage='%prog [-v] -a [ video | thumbnail | discover ] SRC [ DEST ]')
parser.add_option('-a', '--action',
dest='action',
- help='One of "video" or "thumbnail"')
+ help='One of "video", "discover" or "thumbnail"')
parser.add_option('-v',
dest='verbose',
@@ -646,13 +676,17 @@ if __name__ == '__main__':
_log.debug(args)
- if not len(args) == 2:
+ if not len(args) == 2 and not options.action == 'discover':
parser.print_help()
sys.exit()
+ transcoder = VideoTranscoder()
+
if options.action == 'thumbnail':
VideoThumbnailer(*args)
elif options.action == 'video':
def cb(data):
print('I\'m a callback!')
- transcoder = VideoTranscoder(*args, progress_callback=cb)
+ transcoder.transcode(*args, progress_callback=cb)
+ elif options.action == 'discover':
+ print transcoder.discover(*args).__dict__
diff --git a/mediagoblin/processing/__init__.py b/mediagoblin/processing/__init__.py
index 9dee3baa..4a827af4 100644
--- a/mediagoblin/processing/__init__.py
+++ b/mediagoblin/processing/__init__.py
@@ -24,9 +24,6 @@ from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
_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(
diff --git a/mediagoblin/static/css/audio.css b/mediagoblin/static/css/audio.css
new file mode 100644
index 00000000..5f7a888a
--- /dev/null
+++ b/mediagoblin/static/css/audio.css
@@ -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
index 00000000..91d52f96
--- /dev/null
+++ b/mediagoblin/static/js/audio.js
@@ -0,0 +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]);
+});
+
diff --git a/mediagoblin/static/js/geolocation-map.js b/mediagoblin/static/js/geolocation-map.js
index a2c62045..de49a37d 100644
--- a/mediagoblin/static/js/geolocation-map.js
+++ b/mediagoblin/static/js/geolocation-map.js
@@ -17,6 +17,11 @@
*/
$(document).ready(function () {
+ if (!$('#tile-map').length) {
+ return;
+ }
+ console.log('Initializing map');
+
var longitude = Number(
$('#tile-map #gps-longitude').val());
var latitude = Number(
diff --git a/mediagoblin/submit/views.py b/mediagoblin/submit/views.py
index 1df676ab..517fb646 100644
--- a/mediagoblin/submit/views.py
+++ b/mediagoblin/submit/views.py
@@ -20,7 +20,8 @@ from os.path import splitext
from cgi import FieldStorage
from celery import registry
-import urllib,urllib2
+import urllib
+import urllib2
import logging
_log = logging.getLogger(__name__)
@@ -36,7 +37,7 @@ 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
@@ -56,7 +57,11 @@ def submit_start(request):
else:
try:
filename = request.POST['file'].filename
- media_type, media_manager = get_media_type_and_manager(filename)
+
+ # Sniff the submitted media to determine which
+ # media plugin should handle processing
+ media_type, media_manager = sniff_media(
+ request.POST['file'])
# create entry and save in database
entry = request.db.MediaEntry()
@@ -131,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}
@@ -160,10 +166,9 @@ def submit_start(request):
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(
diff --git a/mediagoblin/templates/mediagoblin/media_displays/audio.html b/mediagoblin/templates/mediagoblin/media_displays/audio.html
new file mode 100644
index 00000000..36bd9d1d
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/media_displays/audio.html
@@ -0,0 +1,61 @@
+{#
+# 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/>.
+#}
+
+{% 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 %}
+ <div class="audio-spectrogram">
+ <img src="{{ request.app.public_store.file_url(
+ media.media_files.spectrogram) }}"
+ alt="Spectrogram" />
+ </div>
+ {% endif %}
+ <audio class="audio-player" controls="controls"
+ preload="metadata">
+ <source src="{{ request.app.public_store.file_url(
+ media.media_files.ogg) }}" type="video/webm; encoding=&quot;vorbis&quot;" />
+ <div class="no_html5">
+ {%- trans -%}Sorry, this audio will not work because
+ your web browser does not support HTML5
+ audio.{%- endtrans -%}<br/>
+ {%- trans -%}You can get a modern web browser that
+ can play the audio at <a href="http://getfirefox.com">
+ http://getfirefox.com</a>!{%- endtrans -%}
+ </div>
+ </audio>
+ </div>
+ {% if 'original' in media.media_files %}
+ <p>
+ <a href="{{ request.app.public_store.file_url(
+ media.media_files['original']) }}">
+ {%- trans -%}
+ Original
+ {%- endtrans -%}
+ </a>
+ </p>
+ {% endif %}
+{% endblock %}
diff --git a/mediagoblin/tests/test_submission.py b/mediagoblin/tests/test_submission.py
index ba80ba20..8bf7d13c 100644
--- a/mediagoblin/tests/test_submission.py
+++ b/mediagoblin/tests/test_submission.py
@@ -17,20 +17,20 @@
import urlparse
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
+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')
@@ -44,6 +44,7 @@ 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()
@@ -76,7 +77,7 @@ class TestSubmission:
for key in context_keys:
context_data = context_data[key]
return response, context_data
-
+
def upload_data(self, filename):
return {'upload_files': [('file', filename)]}
@@ -102,7 +103,7 @@ class TestSubmission:
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(context.has_key('mediagoblin/user_pages/user.html'))
+ assert_true('mediagoblin/user_pages/user.html' in context)
# Make sure the media view is at least reachable, logged in...
url = '/u/{0}/m/{1}/'.format(self.test_user.username,
title.lower().replace(' ', '-'))
@@ -190,8 +191,30 @@ class TestSubmission:
r'^Could not extract any file extension from ".*?"$',
str(form.file.errors[0])))
+ 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 check_false_image(self, title, filename):
- # NOTE: These images should ultimately fail, but they
+ # 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,