From 5a34a80d0a9d8a930d2a8c49f48d3fe08b60a237 Mon Sep 17 00:00:00 2001 From: Joar Wandborg Date: Tue, 14 Feb 2012 23:26:07 +0100 Subject: Audio media handler, media sniffing, video fixes * Added audio processing code * Added audio display template * Added audio configuration setting * Changed video docstring --- mediagoblin/media_types/__init__.py | 8 ++ mediagoblin/media_types/audio/__init__.py | 25 +++++ mediagoblin/media_types/audio/processing.py | 75 ++++++++++++++ mediagoblin/media_types/audio/transcoder.py | 18 ++++ mediagoblin/media_types/audio/transcoders.py | 150 +++++++++++++++++++++++++++ mediagoblin/media_types/video/processing.py | 13 +-- mediagoblin/media_types/video/transcoders.py | 7 +- 7 files changed, 282 insertions(+), 14 deletions(-) create mode 100644 mediagoblin/media_types/audio/__init__.py create mode 100644 mediagoblin/media_types/audio/processing.py create mode 100644 mediagoblin/media_types/audio/transcoder.py create mode 100644 mediagoblin/media_types/audio/transcoders.py (limited to 'mediagoblin/media_types') diff --git a/mediagoblin/media_types/__init__.py b/mediagoblin/media_types/__init__.py index 5128826b..c53ef1ab 100644 --- a/mediagoblin/media_types/__init__.py +++ b/mediagoblin/media_types/__init__.py @@ -28,6 +28,14 @@ class InvalidFileType(Exception): pass +def sniff_media(media): + ''' + Iterate through the enabled media types and find those suited + for a certain file. + ''' + pass + + def get_media_types(): """ Generator, yields the available media types 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 . + +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/processing.py b/mediagoblin/media_types/audio/processing.py new file mode 100644 index 00000000..beb12391 --- /dev/null +++ b/mediagoblin/media_types/audio/processing.py @@ -0,0 +1,75 @@ +# 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 . + +import logging +import tempfile +import os + +from mediagoblin import mg_globals as mgg +from mediagoblin.processing import create_pub_filepath + +from mediagoblin.media_types.audio.transcoders import AudioTranscoder + +_log = logging.getLogger() + +def sniff_handler(media): + return True + +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])) + + ogg_tmp = tempfile.NamedTemporaryFile() + + with ogg_tmp: + transcoder = AudioTranscoder() + + 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)} + + thumbnail_tmp = tempfile.NamedTemporaryFile() + + with thumbnail_tmp: + entry.media_files['thumb'] = ['fake', 'thumb', 'path.jpg'] + + mgg.queue_store.delete_file(queued_filepath) + + entry.save() diff --git a/mediagoblin/media_types/audio/transcoder.py b/mediagoblin/media_types/audio/transcoder.py new file mode 100644 index 00000000..e1000faa --- /dev/null +++ b/mediagoblin/media_types/audio/transcoder.py @@ -0,0 +1,18 @@ +# 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 . + +class AudioTranscoder(object): + diff --git a/mediagoblin/media_types/audio/transcoders.py b/mediagoblin/media_types/audio/transcoders.py new file mode 100644 index 00000000..e59214b0 --- /dev/null +++ b/mediagoblin/media_types/audio/transcoders.py @@ -0,0 +1,150 @@ +# 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 . + +import pdb +import logging + +from mediagoblin.processing import BadMediaFail + + +_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') + +class AudioTranscoder(object): + def __init__(self): + _log.info('Initializing {0}'.format(self.__class__.__name__)) + + # Instantiate MainLoop + self._loop = gobject.MainLoop() + + def discover(self, 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 + + # Once MainLoop has returned, return discovery data + return self._discovery_data + + def __on_discovered(self, data, is_media): + if not is_media: + self.halt() + _log.error('Could not discover {0}'.format(self._src_path)) + raise BadMediaFail() + + _log.debug('Discovered: {0}'.format(data.__dict__)) + + self._discovery_data = data + + # Gracefully shut down MainLoop + self.halt() + + def transcode(self, src, dst, **kw): + self._discovery_data = kw.get('data', self.discover(src)) + + self.__on_progress = kw.get('progress_callback') + + quality = kw.get('quality', 0.3) + + # Set up pipeline + self.pipeline = gst.parse_launch( + 'filesrc location="{src}" ! ' + 'decodebin2 ! queue ! audiorate tolerance={tolerance} ! ' + 'audioconvert ! audio/x-raw-float,channels=2 ! ' + 'vorbisenc quality={quality} ! webmmux ! ' + 'progressreport silent=true ! ' + 'filesink location="{dst}"'.format( + src=src, + tolerance=80000000, + quality=quality, + 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): + _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]) + + pdb.set_trace() diff --git a/mediagoblin/media_types/video/processing.py b/mediagoblin/media_types/video/processing.py index 9dc23c55..089d96fb 100644 --- a/mediagoblin/media_types/video/processing.py +++ b/mediagoblin/media_types/video/processing.py @@ -31,15 +31,8 @@ _log.setLevel(logging.DEBUG) def process_video(entry): """ - Code to process a video - - Much of this code is derived from the arista-transcoder script in - the arista PyPI package and changed to match the needs of - MediaGoblin - - 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. + 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'] @@ -54,7 +47,7 @@ def process_video(entry): entry, '{original}-640p.webm'.format( original=os.path.splitext( - queued_filepath[-1])[0] # Select the + queued_filepath[-1])[0] # Select the file name without .ext )) thumbnail_filepath = create_pub_filepath( diff --git a/mediagoblin/media_types/video/transcoders.py b/mediagoblin/media_types/video/transcoders.py index 6137c3bf..903bd810 100644 --- a/mediagoblin/media_types/video/transcoders.py +++ b/mediagoblin/media_types/video/transcoders.py @@ -38,17 +38,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 +55,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') -- cgit v1.2.3 From ec4261a449c11b015190bc90dd9ae828261065cd Mon Sep 17 00:00:00 2001 From: Joar Wandborg Date: Wed, 15 Feb 2012 01:15:29 +0100 Subject: Changed media processing delegation to a 'sniffing' method - Added sniff handlers to all media plugins All of them except audio returning False for ANYTHING at the moment. --- mediagoblin/media_types/__init__.py | 15 ++++++++++++++- mediagoblin/media_types/ascii/__init__.py | 4 +++- mediagoblin/media_types/ascii/processing.py | 3 +++ mediagoblin/media_types/audio/processing.py | 11 +++++++++-- mediagoblin/media_types/audio/transcoders.py | 12 +++++++++--- mediagoblin/media_types/image/__init__.py | 4 +++- mediagoblin/media_types/image/processing.py | 3 +++ mediagoblin/media_types/video/__init__.py | 4 +++- mediagoblin/media_types/video/processing.py | 2 ++ 9 files changed, 49 insertions(+), 9 deletions(-) (limited to 'mediagoblin/media_types') diff --git a/mediagoblin/media_types/__init__.py b/mediagoblin/media_types/__init__.py index c53ef1ab..19d822a3 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 @@ -33,7 +36,17 @@ def sniff_media(media): Iterate through the enabled media types and find those suited for a certain file. ''' - pass + 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): + return media_type, manager + + raise FileTypeNotSupported( + # TODO: Provide information on which file types are supported + _(u'Sorry, I don\'t support that file type :(')) def get_media_types(): 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 . -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/processing.py b/mediagoblin/media_types/ascii/processing.py index 7ece866e..f698b97a 100644 --- a/mediagoblin/media_types/ascii/processing.py +++ b/mediagoblin/media_types/ascii/processing.py @@ -24,6 +24,9 @@ from mediagoblin.media_types.ascii import asciitoimage _log = logging.getLogger(__name__) +def sniff_handler(media_file, **kw): + return False + def process_ascii(entry): ''' Code to process a txt file diff --git a/mediagoblin/media_types/audio/processing.py b/mediagoblin/media_types/audio/processing.py index beb12391..7aa7ace8 100644 --- a/mediagoblin/media_types/audio/processing.py +++ b/mediagoblin/media_types/audio/processing.py @@ -25,8 +25,15 @@ from mediagoblin.media_types.audio.transcoders import AudioTranscoder _log = logging.getLogger() -def sniff_handler(media): - return True +def sniff_handler(media_file, **kw): + transcoder = AudioTranscoder() + try: + data = transcoder.discover(media_file.name) + + if data.is_audio == True and data.is_video == False: + return True + except: + return False def process_audio(entry): audio_config = mgg.global_config['media_type:mediagoblin.media_types.audio'] diff --git a/mediagoblin/media_types/audio/transcoders.py b/mediagoblin/media_types/audio/transcoders.py index e59214b0..c5634964 100644 --- a/mediagoblin/media_types/audio/transcoders.py +++ b/mediagoblin/media_types/audio/transcoders.py @@ -62,8 +62,10 @@ class AudioTranscoder(object): # 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 @@ -74,14 +76,17 @@ class AudioTranscoder(object): self._loop.run() # Run MainLoop + if self._failed: + raise self._failed + # Once MainLoop has returned, return discovery data - return self._discovery_data + return getattr(self, '_discovery_data', False) def __on_discovered(self, data, is_media): if not is_media: - self.halt() + self._failed = BadMediaFail() _log.error('Could not discover {0}'.format(self._src_path)) - raise BadMediaFail() + self.halt() _log.debug('Discovered: {0}'.format(data.__dict__)) @@ -91,6 +96,7 @@ class AudioTranscoder(object): 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') 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 . -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 769de89b..d301a69f 100644 --- a/mediagoblin/media_types/image/processing.py +++ b/mediagoblin/media_types/image/processing.py @@ -23,6 +23,9 @@ from mediagoblin.processing import BadMediaFail, \ from mediagoblin.tools.exif import exif_fix_image_orientation, \ extract_exif, clean_exif, get_gps_data, get_useful +def sniff_handler(media_file, **kw): + return False + def process_image(entry): """ Code to process an image diff --git a/mediagoblin/media_types/video/__init__.py b/mediagoblin/media_types/video/__init__.py index ed542f16..5351abe6 100644 --- a/mediagoblin/media_types/video/__init__.py +++ b/mediagoblin/media_types/video/__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 . -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'? + "sniff_handler": sniff_handler, "display_template": "mediagoblin/media_displays/video.html", "default_thumb": "images/media_thumbs/video.jpg", "accepted_extensions": [ diff --git a/mediagoblin/media_types/video/processing.py b/mediagoblin/media_types/video/processing.py index 089d96fb..1890ef0c 100644 --- a/mediagoblin/media_types/video/processing.py +++ b/mediagoblin/media_types/video/processing.py @@ -28,6 +28,8 @@ logging.basicConfig() _log = logging.getLogger(__name__) _log.setLevel(logging.DEBUG) +def sniff_handler(media_file, **kw): + return False def process_video(entry): """ -- cgit v1.2.3 From 92f129b5c707bdeaceb0cdf9807342b52db33b45 Mon Sep 17 00:00:00 2001 From: Joar Wandborg Date: Thu, 16 Feb 2012 18:43:15 +0100 Subject: Added sniffing logic for image media type For now, it's a re-implementation of the old file-extension checking logic, as I have not found a GStreamer-like "discoverer" in PIL. --- mediagoblin/media_types/image/processing.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) (limited to 'mediagoblin/media_types') diff --git a/mediagoblin/media_types/image/processing.py b/mediagoblin/media_types/image/processing.py index d301a69f..364a5afa 100644 --- a/mediagoblin/media_types/image/processing.py +++ b/mediagoblin/media_types/image/processing.py @@ -16,6 +16,7 @@ import Image import os +import logging from mediagoblin import mg_globals as mgg from mediagoblin.processing import BadMediaFail, \ @@ -23,7 +24,30 @@ from mediagoblin.processing import BadMediaFail, \ from mediagoblin.tools.exif import exif_fix_image_orientation, \ extract_exif, clean_exif, get_gps_data, get_useful +_log = logging.getLogger(__name__) + +SUPPORTED_FILETYPES = ['png', 'gif', 'jpg', 'jpeg'] + def sniff_handler(media_file, **kw): + if not kw.get('media') == 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 {1}'.format( + SUPPORTED_FILETYPES)) + else: + _log.warning('Need additional information (keyword argument \'media\')' + ' to be able to handle sniffing') + return False def process_image(entry): -- cgit v1.2.3 From 9f46a79ddebcc3b2a8bc1dd1ef4f67575bfb53c6 Mon Sep 17 00:00:00 2001 From: Joar Wandborg Date: Thu, 16 Feb 2012 18:59:55 +0100 Subject: Removed old audio.transcoder file (the real one is audio.transcoders) --- mediagoblin/media_types/audio/transcoder.py | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 mediagoblin/media_types/audio/transcoder.py (limited to 'mediagoblin/media_types') diff --git a/mediagoblin/media_types/audio/transcoder.py b/mediagoblin/media_types/audio/transcoder.py deleted file mode 100644 index e1000faa..00000000 --- a/mediagoblin/media_types/audio/transcoder.py +++ /dev/null @@ -1,18 +0,0 @@ -# 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 . - -class AudioTranscoder(object): - -- cgit v1.2.3 From 10085b77391c0ca0b33a6ba82ca46da1babbea8f Mon Sep 17 00:00:00 2001 From: Joar Wandborg Date: Tue, 28 Feb 2012 21:59:38 +0100 Subject: Audio thumbnailing & spectrograms, media plugins use sniffing * Added extlib/freesound/audioprocessing.py * config_spec * Added create_spectrogram setting * Added media:medium and media:thumb max_{width,height} settings * Added sniffing logic to - audio.processing:sniff_handler - video.processing:sniff_handler * Changed audio.processing:sniff_handler logic * Added audio thumbnailing functionality to audio.processing (works only with create_spectrogram enabled) * Refractored contexts in audio.processing * Added audio.transcoders:AudioThumbnailer Used for creating spectrograms and spectrogram thumbnails - Wadsworth's Constant, we meet again :) * audio.transcoders:AudioTranscoder - Added mux_string kwarg - Delete self.pipeline on self.halt() * Changed str.format formatting in image.processing:sniff_handler Had {1} without an {0}, changed to {0} * Refractored VideoTranscoder to use transcode() for transcoding instead of __init__() * Added discover() method to video.transcoders:VideoTranscoder * Added spectrogram display to media_displays/audio.html * Updated test_submission to reflect changes in media plugin delegation --- mediagoblin/media_types/ascii/processing.py | 9 +++ mediagoblin/media_types/audio/audioprocessing.py | 1 + mediagoblin/media_types/audio/processing.py | 65 ++++++++++++++--- mediagoblin/media_types/audio/transcoders.py | 90 ++++++++++++++++++++++-- mediagoblin/media_types/image/processing.py | 2 +- mediagoblin/media_types/video/processing.py | 15 +++- mediagoblin/media_types/video/transcoders.py | 53 +++++++++++--- 7 files changed, 209 insertions(+), 26 deletions(-) create mode 120000 mediagoblin/media_types/audio/audioprocessing.py (limited to 'mediagoblin/media_types') diff --git a/mediagoblin/media_types/ascii/processing.py b/mediagoblin/media_types/ascii/processing.py index f698b97a..75184c1f 100644 --- a/mediagoblin/media_types/ascii/processing.py +++ b/mediagoblin/media_types/ascii/processing.py @@ -24,7 +24,16 @@ from mediagoblin.media_types.ascii import asciitoimage _log = logging.getLogger(__name__) +SUPPORTED_EXTENSIONS = ['txt', 'asc', 'nfo'] + def sniff_handler(media_file, **kw): + if not kw.get('media') == 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): 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 index 7aa7ace8..6769f605 100644 --- a/mediagoblin/media_types/audio/processing.py +++ b/mediagoblin/media_types/audio/processing.py @@ -21,9 +21,10 @@ import os from mediagoblin import mg_globals as mgg from mediagoblin.processing import create_pub_filepath -from mediagoblin.media_types.audio.transcoders import AudioTranscoder +from mediagoblin.media_types.audio.transcoders import AudioTranscoder, \ + AudioThumbnailer -_log = logging.getLogger() +_log = logging.getLogger(__name__) def sniff_handler(media_file, **kw): transcoder = AudioTranscoder() @@ -33,7 +34,9 @@ def sniff_handler(media_file, **kw): if data.is_audio == True and data.is_video == False: return True except: - return False + pass + + return False def process_audio(entry): audio_config = mgg.global_config['media_type:mediagoblin.media_types.audio'] @@ -51,10 +54,9 @@ def process_audio(entry): original=os.path.splitext( queued_filepath[-1])[0])) - ogg_tmp = tempfile.NamedTemporaryFile() + transcoder = AudioTranscoder() - with ogg_tmp: - transcoder = AudioTranscoder() + with tempfile.NamedTemporaryFile() as ogg_tmp: transcoder.transcode( queued_filename, @@ -72,11 +74,54 @@ def process_audio(entry): entry.media_data['audio'] = { u'length': int(data.audiolength)} - thumbnail_tmp = tempfile.NamedTemporaryFile() - - with thumbnail_tmp: + 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() diff --git a/mediagoblin/media_types/audio/transcoders.py b/mediagoblin/media_types/audio/transcoders.py index c5634964..7649309c 100644 --- a/mediagoblin/media_types/audio/transcoders.py +++ b/mediagoblin/media_types/audio/transcoders.py @@ -16,8 +16,10 @@ import pdb import logging +from PIL import Image from mediagoblin.processing import BadMediaFail +from mediagoblin.media_types.audio import audioprocessing _log = logging.getLogger(__name__) @@ -56,6 +58,73 @@ try: 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('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 - (th_w / 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__)) @@ -103,17 +172,21 @@ class AudioTranscoder(object): 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 ! ' - 'vorbisenc quality={quality} ! webmmux ! ' + '{mux_string} ! ' 'progressreport silent=true ! ' 'filesink location="{dst}"'.format( src=src, tolerance=80000000, - quality=quality, + mux_string=mux_string, dst=dst)) self.bus = self.pipeline.get_bus() @@ -141,6 +214,9 @@ class AudioTranscoder(object): 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) @@ -149,8 +225,12 @@ if __name__ == '__main__': logging.basicConfig() _log.setLevel(logging.INFO) - transcoder = AudioTranscoder() - data = transcoder.discover(sys.argv[1]) - res = transcoder.transcode(*sys.argv[1:3]) + #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/processing.py b/mediagoblin/media_types/image/processing.py index 364a5afa..28cde2aa 100644 --- a/mediagoblin/media_types/image/processing.py +++ b/mediagoblin/media_types/image/processing.py @@ -42,7 +42,7 @@ def sniff_handler(media_file, **kw): _log.info('Found file extension in supported filetypes') return True else: - _log.debug('Media present, extension not found in {1}'.format( + _log.debug('Media present, extension not found in {0}'.format( SUPPORTED_FILETYPES)) else: _log.warning('Need additional information (keyword argument \'media\')' diff --git a/mediagoblin/media_types/video/processing.py b/mediagoblin/media_types/video/processing.py index 1890ef0c..d2562e3b 100644 --- a/mediagoblin/media_types/video/processing.py +++ b/mediagoblin/media_types/video/processing.py @@ -29,6 +29,18 @@ _log = logging.getLogger(__name__) _log.setLevel(logging.DEBUG) def sniff_handler(media_file, **kw): + transcoder = transcoders.VideoTranscoder() + try: + data = transcoder.discover(media_file.name) + + _log.debug('Discovered: {0}'.format(data.__dict__)) + + if data.is_video == True: + return True + except: + _log.error('Exception caught when trying to discover {0}'.format( + kw.get('media'))) + return False def process_video(entry): @@ -61,7 +73,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 903bd810..6c2e885e 100644 --- a/mediagoblin/media_types/video/transcoders.py +++ b/mediagoblin/media_types/video/transcoders.py @@ -25,8 +25,6 @@ import pdb import urllib _log = logging.getLogger(__name__) -logging.basicConfig() -_log.setLevel(logging.DEBUG) CPU_COUNT = 2 try: @@ -340,10 +338,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 @@ -357,6 +360,30 @@ 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() + + return self._discovered_data + + def __on_discovered(self, data, is_media): + if not is_media: + self.__stop() + raise Exception('Could not discover {0}'.format(self.source_path)) + + self._discovered_data = data + + self.__stop_mainloop() + def _setup(self): self._setup_discover() self._setup_pipeline() @@ -369,12 +396,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): ''' @@ -614,14 +643,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', @@ -645,13 +675,18 @@ 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__ + -- cgit v1.2.3 From 4f4f2531ad5207d074027519b881bf485a249b44 Mon Sep 17 00:00:00 2001 From: Joar Wandborg Date: Wed, 7 Mar 2012 22:48:23 +0100 Subject: Removed diaper patterns from audio/video sniffers, video preload set to 'metadata' * mediagoblin.processing THUMB_/MEDIUM_ constants are now based on the ini settings * Removed diaper patterns from audio and video sniffing * Changed HTML5 video preload to 'metadata' --- mediagoblin/media_types/__init__.py | 3 +++ mediagoblin/media_types/audio/processing.py | 15 ++++++++------- mediagoblin/media_types/audio/transcoders.py | 4 ++-- mediagoblin/media_types/video/processing.py | 15 ++++++++------- mediagoblin/media_types/video/transcoders.py | 11 ++++++++--- 5 files changed, 29 insertions(+), 19 deletions(-) (limited to 'mediagoblin/media_types') diff --git a/mediagoblin/media_types/__init__.py b/mediagoblin/media_types/__init__.py index 19d822a3..db8c6f73 100644 --- a/mediagoblin/media_types/__init__.py +++ b/mediagoblin/media_types/__init__.py @@ -42,7 +42,10 @@ def sniff_media(media): 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 diff --git a/mediagoblin/media_types/audio/processing.py b/mediagoblin/media_types/audio/processing.py index 6769f605..8348f843 100644 --- a/mediagoblin/media_types/audio/processing.py +++ b/mediagoblin/media_types/audio/processing.py @@ -19,7 +19,7 @@ import tempfile import os from mediagoblin import mg_globals as mgg -from mediagoblin.processing import create_pub_filepath +from mediagoblin.processing import create_pub_filepath, BadMediaFail from mediagoblin.media_types.audio.transcoders import AudioTranscoder, \ AudioThumbnailer @@ -27,14 +27,15 @@ from mediagoblin.media_types.audio.transcoders import AudioTranscoder, \ _log = logging.getLogger(__name__) def sniff_handler(media_file, **kw): - transcoder = AudioTranscoder() - try: + 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 - except: - pass + if data.is_audio == True and data.is_video == False: + return True return False diff --git a/mediagoblin/media_types/audio/transcoders.py b/mediagoblin/media_types/audio/transcoders.py index 7649309c..ed40e03c 100644 --- a/mediagoblin/media_types/audio/transcoders.py +++ b/mediagoblin/media_types/audio/transcoders.py @@ -100,7 +100,7 @@ class AudioThumbnailer(object): Takes a spectrogram and creates a thumbnail from it ''' if not (type(thumb_size) == tuple and len(thumb_size) == 2): - raise Exception('size argument should be a tuple(width, height)') + raise Exception('thumb_size argument should be a tuple(width, height)') im = Image.open(src) @@ -110,7 +110,7 @@ class AudioThumbnailer(object): wadsworth_position = im_w * 0.3 start_x = max(( - wadsworth_position - (th_w / 2.0), + wadsworth_position - ((im_h * (th_w / th_h)) / 2.0), 0.0)) stop_x = start_x + (im_h * (th_w / th_h)) diff --git a/mediagoblin/media_types/video/processing.py b/mediagoblin/media_types/video/processing.py index d2562e3b..a8fcf86d 100644 --- a/mediagoblin/media_types/video/processing.py +++ b/mediagoblin/media_types/video/processing.py @@ -30,16 +30,17 @@ _log.setLevel(logging.DEBUG) def sniff_handler(media_file, **kw): transcoder = transcoders.VideoTranscoder() - try: - data = transcoder.discover(media_file.name) + data = transcoder.discover(media_file.name) - _log.debug('Discovered: {0}'.format(data.__dict__)) + _log.debug('Discovered: {0}'.format(data)) - if data.is_video == True: - return True - except: - _log.error('Exception caught when trying to discover {0}'.format( + if not data: + _log.error('Could not discover {0}'.format( kw.get('media'))) + return False + + if data['is_video'] == True: + return True return False diff --git a/mediagoblin/media_types/video/transcoders.py b/mediagoblin/media_types/video/transcoders.py index 6c2e885e..d865bc8d 100644 --- a/mediagoblin/media_types/video/transcoders.py +++ b/mediagoblin/media_types/video/transcoders.py @@ -373,9 +373,13 @@ class VideoTranscoder: self.loop.run() - return self._discovered_data + 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)) @@ -624,8 +628,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) -- cgit v1.2.3 From e2caf5743c29655ebc14e9601042c378c2c00325 Mon Sep 17 00:00:00 2001 From: Joar Wandborg Date: Thu, 8 Mar 2012 22:58:16 +0100 Subject: Changed `not foo == None` to `foo is not None` in image sniff handler >>> class foo(object): def __eq__(self, other): return True >>> f = foo() >>> f == None True >>> f is None False -- --- mediagoblin/media_types/image/processing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'mediagoblin/media_types') diff --git a/mediagoblin/media_types/image/processing.py b/mediagoblin/media_types/image/processing.py index 28cde2aa..5275981e 100644 --- a/mediagoblin/media_types/image/processing.py +++ b/mediagoblin/media_types/image/processing.py @@ -29,7 +29,7 @@ _log = logging.getLogger(__name__) SUPPORTED_FILETYPES = ['png', 'gif', 'jpg', 'jpeg'] def sniff_handler(media_file, **kw): - if not kw.get('media') == None: # That's a double negative! + 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 -- cgit v1.2.3 From e2007352f580f2f959a659a75ca79cb7f8233a8b Mon Sep 17 00:00:00 2001 From: Joar Wandborg Date: Thu, 8 Mar 2012 23:03:52 +0100 Subject: Clean up the workbench after audio processing --- mediagoblin/media_types/audio/processing.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'mediagoblin/media_types') diff --git a/mediagoblin/media_types/audio/processing.py b/mediagoblin/media_types/audio/processing.py index 8348f843..62daf412 100644 --- a/mediagoblin/media_types/audio/processing.py +++ b/mediagoblin/media_types/audio/processing.py @@ -126,3 +126,6 @@ def process_audio(entry): mgg.queue_store.delete_file(queued_filepath) entry.save() + + # clean up workbench + workbench.destroy_self() -- cgit v1.2.3 From 9743ce886ab3655662afaa0ef7b08791a65e37da Mon Sep 17 00:00:00 2001 From: Joar Wandborg Date: Thu, 8 Mar 2012 23:49:41 +0100 Subject: Replaced 'not foo == None' with 'foo is not None' in ascii sniffing too --- mediagoblin/media_types/ascii/processing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'mediagoblin/media_types') diff --git a/mediagoblin/media_types/ascii/processing.py b/mediagoblin/media_types/ascii/processing.py index 75184c1f..846c39fb 100644 --- a/mediagoblin/media_types/ascii/processing.py +++ b/mediagoblin/media_types/ascii/processing.py @@ -27,7 +27,7 @@ _log = logging.getLogger(__name__) SUPPORTED_EXTENSIONS = ['txt', 'asc', 'nfo'] def sniff_handler(media_file, **kw): - if not kw.get('media') == None: + if kw.get('media') is not None: name, ext = os.path.splitext(kw['media'].filename) clean_ext = ext[1:].lower() -- cgit v1.2.3 From a9d84d4cb79252549d472f9f2059b45bbce4f4f1 Mon Sep 17 00:00:00 2001 From: Joar Wandborg Date: Thu, 22 Mar 2012 01:27:19 +0100 Subject: Faster sniffing - 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 | 56 ++++++++++++++++++------------- mediagoblin/media_types/video/__init__.py | 2 +- 2 files changed, 33 insertions(+), 25 deletions(-) (limited to 'mediagoblin/media_types') diff --git a/mediagoblin/media_types/__init__.py b/mediagoblin/media_types/__init__.py index db8c6f73..93d2319f 100644 --- a/mediagoblin/media_types/__init__.py +++ b/mediagoblin/media_types/__init__.py @@ -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 :(')) diff --git a/mediagoblin/media_types/video/__init__.py b/mediagoblin/media_types/video/__init__.py index 5351abe6..a53927d5 100644 --- a/mediagoblin/media_types/video/__init__.py +++ b/mediagoblin/media_types/video/__init__.py @@ -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"]} -- cgit v1.2.3 From c56d4b55a169d67a3e5e5aba4271a67f0cb79c6f Mon Sep 17 00:00:00 2001 From: Joar Wandborg Date: Mon, 26 Mar 2012 17:44:08 +0200 Subject: Media type refractors, pep8, lint - Removed THUMB_SIZE, MEDIUM_SIZE constants, depend on configuration values instead. - pep8 refractoring --- mediagoblin/media_types/ascii/asciitoimage.py | 1 + mediagoblin/media_types/ascii/processing.py | 10 +++++++--- mediagoblin/media_types/audio/transcoders.py | 5 +++-- mediagoblin/media_types/image/processing.py | 19 ++++++++++++++----- mediagoblin/media_types/video/processing.py | 8 ++++---- mediagoblin/media_types/video/transcoders.py | 15 +++++---------- 6 files changed, 34 insertions(+), 24 deletions(-) (limited to 'mediagoblin/media_types') 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 846c39fb..cd00b346 100644 --- a/mediagoblin/media_types/ascii/processing.py +++ b/mediagoblin/media_types/ascii/processing.py @@ -19,13 +19,14 @@ 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) @@ -36,6 +37,7 @@ def sniff_handler(media_file, **kw): return False + def process_ascii(entry): ''' Code to process a txt file @@ -81,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') @@ -96,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/transcoders.py b/mediagoblin/media_types/audio/transcoders.py index ed40e03c..f84ab7f2 100644 --- a/mediagoblin/media_types/audio/transcoders.py +++ b/mediagoblin/media_types/audio/transcoders.py @@ -24,7 +24,7 @@ from mediagoblin.media_types.audio import audioprocessing _log = logging.getLogger(__name__) -CPU_COUNT = 2 # Just assuming for now +CPU_COUNT = 2 # Just assuming for now # IMPORT MULTIPROCESSING try: @@ -60,6 +60,7 @@ except ImportError: import numpy + class AudioThumbnailer(object): def __init__(self): _log.info('Initializing {0}'.format(self.__class__.__name__)) @@ -178,7 +179,7 @@ class AudioTranscoder(object): # Set up pipeline self.pipeline = gst.parse_launch( - 'filesrc location="{src}" ! ' + 'filesrc location="{src}" ! ' 'decodebin2 ! queue ! audiorate tolerance={tolerance} ! ' 'audioconvert ! audio/x-raw-float,channels=2 ! ' '{mux_string} ! ' diff --git a/mediagoblin/media_types/image/processing.py b/mediagoblin/media_types/image/processing.py index 5275981e..bacfecb8 100644 --- a/mediagoblin/media_types/image/processing.py +++ b/mediagoblin/media_types/image/processing.py @@ -20,7 +20,7 @@ import logging from mediagoblin import mg_globals as mgg from mediagoblin.processing import BadMediaFail, \ - create_pub_filepath, THUMB_SIZE, MEDIUM_SIZE + create_pub_filepath from mediagoblin.tools.exif import exif_fix_image_orientation, \ extract_exif, clean_exif, get_gps_data, get_useful @@ -28,6 +28,7 @@ _log = logging.getLogger(__name__) 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) @@ -50,6 +51,7 @@ def sniff_handler(media_file, **kw): return False + def process_image(entry): """ Code to process an image @@ -80,7 +82,10 @@ def process_image(entry): thumb = exif_fix_image_orientation(thumb, exif_tags) - thumb.thumbnail(THUMB_SIZE, Image.ANTIALIAS) + 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 @@ -103,8 +108,12 @@ def process_image(entry): # Fix orientation medium = exif_fix_image_orientation(medium, exif_tags) - if medium.size[0] > MEDIUM_SIZE[0] or medium.size[1] > MEDIUM_SIZE[1]: - medium.thumbnail(MEDIUM_SIZE, Image.ANTIALIAS) + if medium.size[0] > mgg.global_config['media:medium']['max_width'] \ + or medium.size[1] > mgg.global_config['media:medium']['max_height']: + medium.thumbnail( + (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) @@ -124,7 +133,7 @@ def process_image(entry): with queued_file: #create_pub_filepath(entry, queued_filepath[-1]) - original_filepath = create_pub_filepath(entry, basename + extension) + original_filepath = create_pub_filepath(entry, basename + extension) with mgg.public_store.get_file(original_filepath, 'wb') \ as original_file: diff --git a/mediagoblin/media_types/video/processing.py b/mediagoblin/media_types/video/processing.py index a8fcf86d..4c44e65f 100644 --- a/mediagoblin/media_types/video/processing.py +++ b/mediagoblin/media_types/video/processing.py @@ -19,8 +19,7 @@ import logging import os from mediagoblin import mg_globals as mgg -from mediagoblin.processing import mark_entry_failed, \ - THUMB_SIZE, MEDIUM_SIZE, create_pub_filepath +from mediagoblin.processing import create_pub_filepath from . import transcoders logging.basicConfig() @@ -28,6 +27,7 @@ logging.basicConfig() _log = logging.getLogger(__name__) _log.setLevel(logging.DEBUG) + def sniff_handler(media_file, **kw): transcoder = transcoders.VideoTranscoder() data = transcoder.discover(media_file.name) @@ -44,6 +44,7 @@ def sniff_handler(media_file, **kw): return False + def process_video(entry): """ Process a video entry, transcode the queued media files (originals) and @@ -62,13 +63,12 @@ def process_video(entry): entry, '{original}-640p.webm'.format( original=os.path.splitext( - queued_filepath[-1])[0] # Select the file name without .ext + queued_filepath[-1])[0] # Select the file name without .ext )) thumbnail_filepath = create_pub_filepath( entry, 'thumbnail.jpg') - # Create a temporary file for the video destination tmp_dst = tempfile.NamedTemporaryFile() diff --git a/mediagoblin/media_types/video/transcoders.py b/mediagoblin/media_types/video/transcoders.py index d865bc8d..e0bd0d3d 100644 --- a/mediagoblin/media_types/video/transcoders.py +++ b/mediagoblin/media_types/video/transcoders.py @@ -21,7 +21,6 @@ os.putenv('GST_DEBUG_DUMP_DOT_DIR', '/tmp') import sys import logging -import pdb import urllib _log = logging.getLogger(__name__) @@ -267,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) @@ -317,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() @@ -454,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) @@ -548,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 @@ -593,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) @@ -619,7 +616,6 @@ class VideoTranscoder: self.dst_discoverer.discover() - def __dst_discovered(self, data, is_media): self.dst_data = data @@ -694,4 +690,3 @@ if __name__ == '__main__': transcoder.transcode(*args, progress_callback=cb) elif options.action == 'discover': print transcoder.discover(*args).__dict__ - -- cgit v1.2.3