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