From 5a34a80d0a9d8a930d2a8c49f48d3fe08b60a237 Mon Sep 17 00:00:00 2001 From: Joar Wandborg Date: Tue, 14 Feb 2012 23:26:07 +0100 Subject: [PATCH] Audio media handler, media sniffing, video fixes * Added audio processing code * Added audio display template * Added audio configuration setting * Changed video docstring --- mediagoblin/config_spec.ini | 7 +- 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 +- .../mediagoblin/media_displays/audio.html | 47 ++++++ 9 files changed, 334 insertions(+), 16 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 create mode 100644 mediagoblin/templates/mediagoblin/media_displays/audio.html diff --git a/mediagoblin/config_spec.ini b/mediagoblin/config_spec.ini index 2d410899..452d9745 100644 --- a/mediagoblin/config_spec.ini +++ b/mediagoblin/config_spec.ini @@ -65,11 +65,14 @@ base_url = string(default="/mgoblin_media/") storage_class = string(default="mediagoblin.storage.filestorage:BasicFileStorage") base_dir = string(default="%(here)s/user_dev/media/queue") - -# 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) + [beaker.cache] type = string(default="file") 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') diff --git a/mediagoblin/templates/mediagoblin/media_displays/audio.html b/mediagoblin/templates/mediagoblin/media_displays/audio.html new file mode 100644 index 00000000..802a85c1 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/media_displays/audio.html @@ -0,0 +1,47 @@ +{# +# 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 . +#} + +{% extends 'mediagoblin/user_pages/media.html' %} + +{% block mediagoblin_media %} +
+ +
+ {% if 'original' in media.media_files %} +

+ + {%- trans -%} + Original + {%- endtrans -%} + +

+ {% endif %} +{% endblock %} -- 2.25.1