trac#687: Add unit tests for `redirect` and `redirect_obj`.
[larjonas-mediagoblin.git] / mediagoblin / media_types / audio / transcoders.py
blobf86528dee3d6f6e72a532eac0547d83026299218
1 # GNU MediaGoblin -- federated, autonomous media hosting
2 # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU Affero General Public License for more details.
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
17 import logging
18 try:
19 from PIL import Image
20 except ImportError:
21 import Image
23 from mediagoblin.media_types.audio import audioprocessing
25 _log = logging.getLogger(__name__)
27 CPU_COUNT = 2 # Just assuming for now
29 # IMPORT MULTIPROCESSING
30 try:
31 import multiprocessing
32 try:
33 CPU_COUNT = multiprocessing.cpu_count()
34 except NotImplementedError:
35 _log.warning('multiprocessing.cpu_count not implemented!\n'
36 'Assuming 2 CPU cores')
37 except ImportError:
38 _log.warning('Could not import multiprocessing, assuming 2 CPU cores')
40 # uncomment this to get a lot of logs from gst
41 # import os;os.environ['GST_DEBUG'] = '5,python:5'
43 import gi
44 gi.require_version('Gst', '1.0')
45 from gi.repository import GObject, Gst
46 Gst.init(None)
48 import numpy
51 class AudioThumbnailer(object):
52 def __init__(self):
53 _log.info('Initializing {0}'.format(self.__class__.__name__))
55 def spectrogram(self, src, dst, **kw):
56 width = kw['width']
57 height = int(kw.get('height', float(width) * 0.3))
58 fft_size = kw.get('fft_size', 2048)
59 callback = kw.get('progress_callback')
60 processor = audioprocessing.AudioProcessor(
61 src,
62 fft_size,
63 numpy.hanning)
65 samples_per_pixel = processor.audio_file.nframes / float(width)
67 spectrogram = audioprocessing.SpectrogramImage(width, height, fft_size)
69 for x in range(width):
70 if callback and x % (width / 10) == 0:
71 callback((x * 100) / width)
73 seek_point = int(x * samples_per_pixel)
75 (spectral_centroid, db_spectrum) = processor.spectral_centroid(
76 seek_point)
78 spectrogram.draw_spectrum(x, db_spectrum)
80 if callback:
81 callback(100)
83 spectrogram.save(dst)
85 def thumbnail_spectrogram(self, src, dst, thumb_size):
86 '''
87 Takes a spectrogram and creates a thumbnail from it
88 '''
89 if not (type(thumb_size) == tuple and len(thumb_size) == 2):
90 raise Exception('thumb_size argument should be a tuple(width, height)')
92 im = Image.open(src)
94 im_w, im_h = [float(i) for i in im.size]
95 th_w, th_h = [float(i) for i in thumb_size]
97 wadsworth_position = im_w * 0.3
99 start_x = max((
100 wadsworth_position - ((im_h * (th_w / th_h)) / 2.0),
101 0.0))
103 stop_x = start_x + (im_h * (th_w / th_h))
105 th = im.crop((
106 int(start_x), 0,
107 int(stop_x), int(im_h)))
109 th.thumbnail(thumb_size, Image.ANTIALIAS)
111 th.save(dst)
114 class AudioTranscoder(object):
115 def __init__(self):
116 _log.info('Initializing {0}'.format(self.__class__.__name__))
118 # Instantiate MainLoop
119 self._loop = GObject.MainLoop()
120 self._failed = None
122 def transcode(self, src, dst, mux_name='webmmux',quality=0.3,
123 progress_callback=None, **kw):
124 def _on_pad_added(element, pad, connect_to):
125 caps = pad.query_caps(None)
126 name = caps.to_string()
127 _log.debug('on_pad_added: {0}'.format(name))
128 if name.startswith('audio') and not connect_to.is_linked():
129 pad.link(connect_to)
130 _log.info('Transcoding {0} into {1}'.format(src, dst))
131 self.__on_progress = progress_callback
132 # Set up pipeline
133 tolerance = 80000000
134 self.pipeline = Gst.Pipeline()
135 filesrc = Gst.ElementFactory.make('filesrc', 'filesrc')
136 filesrc.set_property('location', src)
137 decodebin = Gst.ElementFactory.make('decodebin', 'decodebin')
138 queue = Gst.ElementFactory.make('queue', 'queue')
139 decodebin.connect('pad-added', _on_pad_added,
140 queue.get_static_pad('sink'))
141 audiorate = Gst.ElementFactory.make('audiorate', 'audiorate')
142 audiorate.set_property('tolerance', tolerance)
143 audioconvert = Gst.ElementFactory.make('audioconvert', 'audioconvert')
144 caps_struct = Gst.Structure.new_empty('audio/x-raw')
145 caps_struct.set_value('channels', 2)
146 caps = Gst.Caps.new_empty()
147 caps.append_structure(caps_struct)
148 capsfilter = Gst.ElementFactory.make('capsfilter', 'capsfilter')
149 capsfilter.set_property('caps', caps)
150 enc = Gst.ElementFactory.make('vorbisenc', 'enc')
151 enc.set_property('quality', quality)
152 mux = Gst.ElementFactory.make(mux_name, 'mux')
153 progressreport = Gst.ElementFactory.make('progressreport', 'progress')
154 progressreport.set_property('silent', True)
155 sink = Gst.ElementFactory.make('filesink', 'sink')
156 sink.set_property('location', dst)
157 # add to pipeline
158 for e in [filesrc, decodebin, queue, audiorate, audioconvert,
159 capsfilter, enc, mux, progressreport, sink]:
160 self.pipeline.add(e)
161 # link elements
162 filesrc.link(decodebin)
163 decodebin.link(queue)
164 queue.link(audiorate)
165 audiorate.link(audioconvert)
166 audioconvert.link(capsfilter)
167 capsfilter.link(enc)
168 enc.link(mux)
169 mux.link(progressreport)
170 progressreport.link(sink)
171 self.bus = self.pipeline.get_bus()
172 self.bus.add_signal_watch()
173 self.bus.connect('message', self.__on_bus_message)
174 # run
175 self.pipeline.set_state(Gst.State.PLAYING)
176 self._loop.run()
178 def __on_bus_message(self, bus, message):
179 _log.debug(message.type)
180 if (message.type == Gst.MessageType.ELEMENT
181 and message.has_name('progress')):
182 structure = message.get_structure()
183 (success, percent) = structure.get_int('percent')
184 if self.__on_progress and success:
185 self.__on_progress(percent)
186 _log.info('{0}% done...'.format(percent))
187 elif message.type == Gst.MessageType.EOS:
188 _log.info('Done')
189 self.halt()
190 elif message.type == Gst.MessageType.ERROR:
191 _log.error(message.parse_error())
192 self.halt()
194 def halt(self):
195 if getattr(self, 'pipeline', False):
196 self.pipeline.set_state(Gst.State.NULL)
197 del self.pipeline
198 _log.info('Quitting MainLoop gracefully...')
199 GObject.idle_add(self._loop.quit)
201 if __name__ == '__main__':
202 import sys
203 logging.basicConfig()
204 _log.setLevel(logging.INFO)
206 #transcoder = AudioTranscoder()
207 #data = transcoder.discover(sys.argv[1])
208 #res = transcoder.transcode(*sys.argv[1:3])
210 thumbnailer = AudioThumbnailer()
212 thumbnailer.spectrogram(*sys.argv[1:], width=640)