use name_builder with store_public, not create_pub_filepath
[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
125 if th.size[0] > th_w or th.size[1] > th_h:
126 th.thumbnail(thumb_size, Image.ANTIALIAS)
127
128 th.save(dst)
129
130
5a34a80d
JW
131class AudioTranscoder(object):
132 def __init__(self):
133 _log.info('Initializing {0}'.format(self.__class__.__name__))
134
135 # Instantiate MainLoop
136 self._loop = gobject.MainLoop()
ec4261a4 137 self._failed = None
5a34a80d
JW
138
139 def discover(self, src):
ec4261a4 140 self._src_path = src
5a34a80d
JW
141 _log.info('Discovering {0}'.format(src))
142 self._discovery_path = src
143
144 self._discoverer = gst.extend.discoverer.Discoverer(
145 self._discovery_path)
146 self._discoverer.connect('discovered', self.__on_discovered)
147 self._discoverer.discover()
148
149 self._loop.run() # Run MainLoop
150
ec4261a4
JW
151 if self._failed:
152 raise self._failed
153
5a34a80d 154 # Once MainLoop has returned, return discovery data
ec4261a4 155 return getattr(self, '_discovery_data', False)
5a34a80d
JW
156
157 def __on_discovered(self, data, is_media):
158 if not is_media:
ec4261a4 159 self._failed = BadMediaFail()
5a34a80d 160 _log.error('Could not discover {0}'.format(self._src_path))
ec4261a4 161 self.halt()
5a34a80d
JW
162
163 _log.debug('Discovered: {0}'.format(data.__dict__))
164
165 self._discovery_data = data
166
167 # Gracefully shut down MainLoop
168 self.halt()
169
170 def transcode(self, src, dst, **kw):
ec4261a4 171 _log.info('Transcoding {0} into {1}'.format(src, dst))
5a34a80d
JW
172 self._discovery_data = kw.get('data', self.discover(src))
173
174 self.__on_progress = kw.get('progress_callback')
175
176 quality = kw.get('quality', 0.3)
177
10085b77
JW
178 mux_string = kw.get(
179 'mux_string',
180 'vorbisenc quality={0} ! webmmux'.format(quality))
181
5a34a80d
JW
182 # Set up pipeline
183 self.pipeline = gst.parse_launch(
c56d4b55 184 'filesrc location="{src}" ! '
5a34a80d
JW
185 'decodebin2 ! queue ! audiorate tolerance={tolerance} ! '
186 'audioconvert ! audio/x-raw-float,channels=2 ! '
10085b77 187 '{mux_string} ! '
5a34a80d
JW
188 'progressreport silent=true ! '
189 'filesink location="{dst}"'.format(
190 src=src,
191 tolerance=80000000,
10085b77 192 mux_string=mux_string,
5a34a80d
JW
193 dst=dst))
194
195 self.bus = self.pipeline.get_bus()
196 self.bus.add_signal_watch()
197 self.bus.connect('message', self.__on_bus_message)
198
199 self.pipeline.set_state(gst.STATE_PLAYING)
200
201 self._loop.run()
202
203 def __on_bus_message(self, bus, message):
204 _log.debug(message)
205
206 if (message.type == gst.MESSAGE_ELEMENT
207 and message.structure.get_name() == 'progress'):
208 data = dict(message.structure)
209
210 if self.__on_progress:
64712915 211 self.__on_progress(data.get('percent'))
5a34a80d
JW
212
213 _log.info('{0}% done...'.format(
214 data.get('percent')))
215 elif message.type == gst.MESSAGE_EOS:
216 _log.info('Done')
217 self.halt()
218
219 def halt(self):
10085b77
JW
220 if getattr(self, 'pipeline', False):
221 self.pipeline.set_state(gst.STATE_NULL)
222 del self.pipeline
5a34a80d
JW
223 _log.info('Quitting MainLoop gracefully...')
224 gobject.idle_add(self._loop.quit)
225
226if __name__ == '__main__':
227 import sys
228 logging.basicConfig()
229 _log.setLevel(logging.INFO)
230
10085b77
JW
231 #transcoder = AudioTranscoder()
232 #data = transcoder.discover(sys.argv[1])
233 #res = transcoder.transcode(*sys.argv[1:3])
234
235 thumbnailer = AudioThumbnailer()
236
237 thumbnailer.spectrogram(*sys.argv[1:], width=640)