from mediagoblin.media_types.audio.transcoders import (
AudioTranscoder, AudioThumbnailer)
+from mediagoblin.media_types.tools import discover
_log = logging.getLogger(__name__)
def sniff_handler(media_file, filename):
_log.info('Sniffing {0}'.format(MEDIA_TYPE))
- try:
- transcoder = AudioTranscoder()
- data = transcoder.discover(media_file.name)
- except BadMediaFail:
- _log.debug('Audio discovery raised BadMediaFail')
- return None
-
- if data.is_audio is True and data.is_video is False:
+ data = discover(media_file.name)
+ if data and data.get_audio_streams() and not data.get_video_streams():
return MEDIA_TYPE
-
return None
quality=quality,
progress_callback=progress_callback)
- self.transcoder.discover(webm_audio_tmp)
-
self._keep_best()
_log.debug('Saving medium...')
if self._skip_processing('spectrogram', max_width=max_width,
fft_size=fft_size):
return
-
wav_tmp = os.path.join(self.workbench.dir, self.name_builder.fill(
'{basename}.ogg'))
-
_log.info('Creating OGG source for spectrogram')
- self.transcoder.transcode(
- self.process_filename,
- wav_tmp,
- mux_string='vorbisenc quality={0} ! oggmux'.format(
- self.audio_config['quality']))
-
+ self.transcoder.transcode(self.process_filename, wav_tmp,
+ mux_name='oggmux')
spectrogram_tmp = os.path.join(self.workbench.dir,
self.name_builder.fill(
'{basename}-spectrogram.jpg'))
-
self.thumbnailer.spectrogram(
wav_tmp,
spectrogram_tmp,
except ImportError:
import Image
-from mediagoblin.processing import BadMediaFail
from mediagoblin.media_types.audio import audioprocessing
-
_log = logging.getLogger(__name__)
CPU_COUNT = 2 # Just assuming for now
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')
+# uncomment this to get a lot of logs from gst
+# import os;os.environ['GST_DEBUG'] = '5,python:5'
- import gst
-
- import gst.extend.discoverer
-except ImportError:
- raise Exception('gst/pygst >= 0.10 could not be imported')
+import gi
+gi.require_version('Gst', '1.0')
+from gi.repository import GObject, Gst
+Gst.init(None)
import numpy
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,
_log.info('Initializing {0}'.format(self.__class__.__name__))
# Instantiate MainLoop
- self._loop = gobject.MainLoop()
+ self._loop = GObject.MainLoop()
self._failed = None
- def discover(self, src):
- self._src_path = src
- _log.info('Discovering {0}'.format(src))
- self._discovery_path = src
-
- self._discoverer = gst.extend.discoverer.Discoverer(
- self._discovery_path)
- self._discoverer.connect('discovered', self.__on_discovered)
- self._discoverer.discover()
-
- self._loop.run() # Run MainLoop
-
- if self._failed:
- raise self._failed
-
- # Once MainLoop has returned, return discovery data
- return getattr(self, '_discovery_data', False)
-
- def __on_discovered(self, data, is_media):
- if not is_media:
- self._failed = BadMediaFail()
- _log.error('Could not discover {0}'.format(self._src_path))
- self.halt()
-
- _log.debug('Discovered: {0}'.format(data.__dict__))
-
- self._discovery_data = data
-
- # Gracefully shut down MainLoop
- self.halt()
-
- def transcode(self, src, dst, **kw):
+ def transcode(self, src, dst, mux_name='webmmux',quality=0.3,
+ progress_callback=None, **kw):
+ def _on_pad_added(element, pad, connect_to):
+ caps = pad.query_caps(None)
+ name = caps.to_string()
+ _log.debug('on_pad_added: {0}'.format(name))
+ if name.startswith('audio') and not connect_to.is_linked():
+ pad.link(connect_to)
_log.info('Transcoding {0} into {1}'.format(src, dst))
- self._discovery_data = kw.get('data', self.discover(src))
-
- self.__on_progress = kw.get('progress_callback')
-
- quality = kw.get('quality', 0.3)
-
- mux_string = kw.get(
- 'mux_string',
- 'vorbisenc quality={0} ! webmmux'.format(quality))
-
+ self.__on_progress = progress_callback
# Set up pipeline
- self.pipeline = gst.parse_launch(
- 'filesrc location="{src}" ! '
- 'decodebin2 ! queue ! audiorate tolerance={tolerance} ! '
- 'audioconvert ! audio/x-raw-float,channels=2 ! '
- '{mux_string} ! '
- 'progressreport silent=true ! '
- 'filesink location="{dst}"'.format(
- src=src,
- tolerance=80000000,
- mux_string=mux_string,
- dst=dst))
-
+ tolerance = 80000000
+ self.pipeline = Gst.Pipeline()
+ filesrc = Gst.ElementFactory.make('filesrc', 'filesrc')
+ filesrc.set_property('location', src)
+ decodebin = Gst.ElementFactory.make('decodebin', 'decodebin')
+ queue = Gst.ElementFactory.make('queue', 'queue')
+ decodebin.connect('pad-added', _on_pad_added,
+ queue.get_static_pad('sink'))
+ audiorate = Gst.ElementFactory.make('audiorate', 'audiorate')
+ audiorate.set_property('tolerance', tolerance)
+ audioconvert = Gst.ElementFactory.make('audioconvert', 'audioconvert')
+ caps_struct = Gst.Structure.new_empty('audio/x-raw')
+ caps_struct.set_value('channels', 2)
+ caps = Gst.Caps.new_empty()
+ caps.append_structure(caps_struct)
+ capsfilter = Gst.ElementFactory.make('capsfilter', 'capsfilter')
+ capsfilter.set_property('caps', caps)
+ enc = Gst.ElementFactory.make('vorbisenc', 'enc')
+ enc.set_property('quality', quality)
+ mux = Gst.ElementFactory.make(mux_name, 'mux')
+ progressreport = Gst.ElementFactory.make('progressreport', 'progress')
+ progressreport.set_property('silent', True)
+ sink = Gst.ElementFactory.make('filesink', 'sink')
+ sink.set_property('location', dst)
+ # add to pipeline
+ for e in [filesrc, decodebin, queue, audiorate, audioconvert,
+ capsfilter, enc, mux, progressreport, sink]:
+ self.pipeline.add(e)
+ # link elements
+ filesrc.link(decodebin)
+ decodebin.link(queue)
+ queue.link(audiorate)
+ audiorate.link(audioconvert)
+ audioconvert.link(capsfilter)
+ capsfilter.link(enc)
+ enc.link(mux)
+ mux.link(progressreport)
+ progressreport.link(sink)
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)
-
+ # run
+ 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.get('percent'))
-
- _log.info('{0}% done...'.format(
- data.get('percent')))
- elif message.type == gst.MESSAGE_EOS:
+ _log.debug(message.type)
+ if (message.type == Gst.MessageType.ELEMENT
+ and message.has_name('progress')):
+ structure = message.get_structure()
+ (success, percent) = structure.get_int('percent')
+ if self.__on_progress and success:
+ self.__on_progress(percent)
+ _log.info('{0}% done...'.format(percent))
+ elif message.type == Gst.MessageType.EOS:
_log.info('Done')
self.halt()
+ elif message.type == Gst.MessageType.ERROR:
+ _log.error(message.parse_error())
+ self.halt()
def halt(self):
if getattr(self, 'pipeline', False):
- self.pipeline.set_state(gst.STATE_NULL)
+ self.pipeline.set_state(Gst.State.NULL)
del self.pipeline
_log.info('Quitting MainLoop gracefully...')
- gobject.idle_add(self._loop.quit)
+ GObject.idle_add(self._loop.quit)
if __name__ == '__main__':
import sys
--- /dev/null
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2013 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 tempfile
+import shutil
+import os
+import pytest
+from contextlib import contextmanager
+import logging
+import imghdr
+
+#os.environ['GST_DEBUG'] = '4,python:4'
+
+#TODO: this should be skipped if video plugin is not enabled
+import gi
+gi.require_version('Gst', '1.0')
+from gi.repository import Gst
+Gst.init(None)
+
+from mediagoblin.media_types.audio.transcoders import (AudioTranscoder,
+ AudioThumbnailer)
+from mediagoblin.media_types.tools import discover
+
+
+@contextmanager
+def create_audio():
+ audio = tempfile.NamedTemporaryFile()
+ src = Gst.ElementFactory.make('audiotestsrc', None)
+ src.set_property('num-buffers', 50)
+ enc = Gst.ElementFactory.make('flacenc', None)
+ dst = Gst.ElementFactory.make('filesink', None)
+ dst.set_property('location', audio.name)
+ pipeline = Gst.Pipeline()
+ pipeline.add(src)
+ pipeline.add(enc)
+ pipeline.add(dst)
+ src.link(enc)
+ enc.link(dst)
+ pipeline.set_state(Gst.State.PLAYING)
+ state = pipeline.get_state(3 * Gst.SECOND)
+ assert state[0] == Gst.StateChangeReturn.SUCCESS
+ bus = pipeline.get_bus()
+ bus.timed_pop_filtered(
+ 3 * Gst.SECOND,
+ Gst.MessageType.ERROR | Gst.MessageType.EOS)
+ pipeline.set_state(Gst.State.NULL)
+ yield (audio.name)
+
+
+@contextmanager
+def create_data_for_test():
+ with create_audio() as audio_name:
+ second_file = tempfile.NamedTemporaryFile()
+ yield (audio_name, second_file.name)
+
+
+def test_transcoder():
+ '''
+ Tests AudioTransocder's transcode method
+ '''
+ transcoder = AudioTranscoder()
+ with create_data_for_test() as (audio_name, result_name):
+ transcoder.transcode(audio_name, result_name, quality=0.3,
+ progress_callback=None)
+ info = discover(result_name)
+ assert len(info.get_audio_streams()) == 1
+ transcoder.transcode(audio_name, result_name, quality=0.3,
+ mux_name='oggmux', progress_callback=None)
+ info = discover(result_name)
+ assert len(info.get_audio_streams()) == 1
+
+
+def test_thumbnails():
+ '''Test thumbnails generation.
+
+ The code below heavily repeats
+ audio.processing.CommonAudioProcessor.create_spectrogram
+ 1. Create test audio
+ 2. Convert it to OGG source for spectogram using transcoder
+ 3. Create spectogram in jpg
+
+ '''
+ thumbnailer = AudioThumbnailer()
+ transcoder = AudioTranscoder()
+ with create_data_for_test() as (audio_name, new_name):
+ transcoder.transcode(audio_name, new_name, mux_name='oggmux')
+ thumbnail = tempfile.NamedTemporaryFile(suffix='.jpg')
+ # fft_size below is copypasted from config_spec.ini
+ thumbnailer.spectrogram(new_name, thumbnail.name, width=100,
+ fft_size=4096)
+ assert imghdr.what(thumbnail.name) == 'jpeg'