Merge branch 'master' into merge-python3-port
[mediagoblin.git] / mediagoblin / media_types / audio / transcoders.py
CommitLineData
5a34a80d
JW
1# GNU MediaGoblin -- federated, autonomous media hosting
2# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
3#
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.
8#
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.
13#
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/>.
16
5a34a80d 17import logging
d0e9f843
AL
18try:
19 from PIL import Image
20except ImportError:
21 import Image
5a34a80d
JW
22
23from mediagoblin.processing import BadMediaFail
10085b77 24from mediagoblin.media_types.audio import audioprocessing
5a34a80d
JW
25
26
27_log = logging.getLogger(__name__)
28
c56d4b55 29CPU_COUNT = 2 # Just assuming for now
5a34a80d
JW
30
31# IMPORT MULTIPROCESSING
32try:
33 import multiprocessing
34 try:
35 CPU_COUNT = multiprocessing.cpu_count()
36 except NotImplementedError:
37 _log.warning('multiprocessing.cpu_count not implemented!\n'
38 'Assuming 2 CPU cores')
39except ImportError:
40 _log.warning('Could not import multiprocessing, assuming 2 CPU cores')
41
42# IMPORT GOBJECT
43try:
44 import gobject
45 gobject.threads_init()
46except ImportError:
47 raise Exception('gobject could not be found')
48
49# IMPORT PYGST
50try:
51 import pygst
52
53 # We won't settle for less. For now, this is an arbitrary limit
54 # as we have not tested with > 0.10
55 pygst.require('0.10')
56
57 import gst
58
59 import gst.extend.discoverer
60except ImportError:
61 raise Exception('gst/pygst >= 0.10 could not be imported')
62
10085b77
JW
63import numpy
64
c56d4b55 65
10085b77
JW
66class AudioThumbnailer(object):
67 def __init__(self):
68 _log.info('Initializing {0}'.format(self.__class__.__name__))
69
70 def spectrogram(self, src, dst, **kw):
71 width = kw['width']
72 height = int(kw.get('height', float(width) * 0.3))
73 fft_size = kw.get('fft_size', 2048)
74 callback = kw.get('progress_callback')
75
76 processor = audioprocessing.AudioProcessor(
77 src,
78 fft_size,
79 numpy.hanning)
80
81 samples_per_pixel = processor.audio_file.nframes / float(width)
82
83 spectrogram = audioprocessing.SpectrogramImage(width, height, fft_size)
84
85 for x in range(width):
86 if callback and x % (width / 10) == 0:
87 callback((x * 100) / width)
88
89 seek_point = int(x * samples_per_pixel)
90
91 (spectral_centroid, db_spectrum) = processor.spectral_centroid(
92 seek_point)
93
94 spectrogram.draw_spectrum(x, db_spectrum)
95
96 if callback:
97 callback(100)
98
99 spectrogram.save(dst)
100
101 def thumbnail_spectrogram(self, src, dst, thumb_size):
102 '''
103 Takes a spectrogram and creates a thumbnail from it
104 '''
105 if not (type(thumb_size) == tuple and len(thumb_size) == 2):
4f4f2531 106 raise Exception('thumb_size argument should be a tuple(width, height)')
10085b77
JW
107
108 im = Image.open(src)
109
110 im_w, im_h = [float(i) for i in im.size]
111 th_w, th_h = [float(i) for i in thumb_size]
112
113 wadsworth_position = im_w * 0.3
114
115 start_x = max((
4f4f2531 116 wadsworth_position - ((im_h * (th_w / th_h)) / 2.0),
10085b77
JW
117 0.0))
118
119 stop_x = start_x + (im_h * (th_w / th_h))
120
121 th = im.crop((
122 int(start_x), 0,
123 int(stop_x), int(im_h)))
124
100a73a2 125 th.thumbnail(thumb_size, Image.ANTIALIAS)
10085b77
JW
126
127 th.save(dst)
128
129
5a34a80d
JW
130class AudioTranscoder(object):
131 def __init__(self):
132 _log.info('Initializing {0}'.format(self.__class__.__name__))
133
134 # Instantiate MainLoop
135 self._loop = gobject.MainLoop()
ec4261a4 136 self._failed = None
5a34a80d
JW
137
138 def discover(self, src):
ec4261a4 139 self._src_path = src
5a34a80d
JW
140 _log.info('Discovering {0}'.format(src))
141 self._discovery_path = src
142
143 self._discoverer = gst.extend.discoverer.Discoverer(
144 self._discovery_path)
145 self._discoverer.connect('discovered', self.__on_discovered)
146 self._discoverer.discover()
147
148 self._loop.run() # Run MainLoop
149
ec4261a4
JW
150 if self._failed:
151 raise self._failed
152
5a34a80d 153 # Once MainLoop has returned, return discovery data
ec4261a4 154 return getattr(self, '_discovery_data', False)
5a34a80d
JW
155
156 def __on_discovered(self, data, is_media):
157 if not is_media:
ec4261a4 158 self._failed = BadMediaFail()
5a34a80d 159 _log.error('Could not discover {0}'.format(self._src_path))
ec4261a4 160 self.halt()
5a34a80d
JW
161
162 _log.debug('Discovered: {0}'.format(data.__dict__))
163
164 self._discovery_data = data
165
166 # Gracefully shut down MainLoop
167 self.halt()
168
169 def transcode(self, src, dst, **kw):
ec4261a4 170 _log.info('Transcoding {0} into {1}'.format(src, dst))
5a34a80d
JW
171 self._discovery_data = kw.get('data', self.discover(src))
172
173 self.__on_progress = kw.get('progress_callback')
174
175 quality = kw.get('quality', 0.3)
176
10085b77
JW
177 mux_string = kw.get(
178 'mux_string',
179 'vorbisenc quality={0} ! webmmux'.format(quality))
180
5a34a80d
JW
181 # Set up pipeline
182 self.pipeline = gst.parse_launch(
c56d4b55 183 'filesrc location="{src}" ! '
5a34a80d
JW
184 'decodebin2 ! queue ! audiorate tolerance={tolerance} ! '
185 'audioconvert ! audio/x-raw-float,channels=2 ! '
10085b77 186 '{mux_string} ! '
5a34a80d
JW
187 'progressreport silent=true ! '
188 'filesink location="{dst}"'.format(
189 src=src,
190 tolerance=80000000,
10085b77 191 mux_string=mux_string,
5a34a80d
JW
192 dst=dst))
193
194 self.bus = self.pipeline.get_bus()
195 self.bus.add_signal_watch()
196 self.bus.connect('message', self.__on_bus_message)
197
198 self.pipeline.set_state(gst.STATE_PLAYING)
199
200 self._loop.run()
201
202 def __on_bus_message(self, bus, message):
203 _log.debug(message)
204
205 if (message.type == gst.MESSAGE_ELEMENT
206 and message.structure.get_name() == 'progress'):
207 data = dict(message.structure)
208
209 if self.__on_progress:
64712915 210 self.__on_progress(data.get('percent'))
5a34a80d
JW
211
212 _log.info('{0}% done...'.format(
213 data.get('percent')))
214 elif message.type == gst.MESSAGE_EOS:
215 _log.info('Done')
216 self.halt()
217
218 def halt(self):
10085b77
JW
219 if getattr(self, 'pipeline', False):
220 self.pipeline.set_state(gst.STATE_NULL)
221 del self.pipeline
5a34a80d
JW
222 _log.info('Quitting MainLoop gracefully...')
223 gobject.idle_add(self._loop.quit)
224
225if __name__ == '__main__':
226 import sys
227 logging.basicConfig()
228 _log.setLevel(logging.INFO)
229
10085b77
JW
230 #transcoder = AudioTranscoder()
231 #data = transcoder.discover(sys.argv[1])
232 #res = transcoder.transcode(*sys.argv[1:3])
233
234 thumbnailer = AudioThumbnailer()
235
236 thumbnailer.spectrogram(*sys.argv[1:], width=640)