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:
parent
6e03e58671
commit
5a34a80d0a
@ -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")
|
||||
|
@ -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
|
||||
|
25
mediagoblin/media_types/audio/__init__.py
Normal file
25
mediagoblin/media_types/audio/__init__.py
Normal 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']}
|
75
mediagoblin/media_types/audio/processing.py
Normal file
75
mediagoblin/media_types/audio/processing.py
Normal 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()
|
18
mediagoblin/media_types/audio/transcoder.py
Normal file
18
mediagoblin/media_types/audio/transcoder.py
Normal 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):
|
||||
|
150
mediagoblin/media_types/audio/transcoders.py
Normal file
150
mediagoblin/media_types/audio/transcoders.py
Normal 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()
|
@ -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(
|
||||
|
@ -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')
|
||||
|
||||
|
||||
|
47
mediagoblin/templates/mediagoblin/media_displays/audio.html
Normal file
47
mediagoblin/templates/mediagoblin/media_displays/audio.html
Normal 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="vorbis"" />
|
||||
<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 %}
|
Loading…
x
Reference in New Issue
Block a user