Audio media handler, media sniffing, video fixes

* Added audio processing code
* Added audio display template
* Added audio configuration setting
* Changed video docstring
This commit is contained in:
Joar Wandborg 2012-02-14 23:26:07 +01:00
parent 6e03e58671
commit 5a34a80d0a
9 changed files with 334 additions and 16 deletions

View File

@ -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")

View File

@ -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

View File

@ -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']}

View File

@ -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 <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
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()

View File

@ -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 <http://www.gnu.org/licenses/>.
class AudioTranscoder(object):

View File

@ -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 <http://www.gnu.org/licenses/>.
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()

View File

@ -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(

View File

@ -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')

View File

@ -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 <http://www.gnu.org/licenses/>.
#}
{% extends 'mediagoblin/user_pages/media.html' %}
{% block mediagoblin_media %}
<div class="audio-media">
<audio 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 %}