Commit | Line | Data |
---|---|---|
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 | 17 | import logging |
d0e9f843 AL |
18 | try: |
19 | from PIL import Image | |
20 | except ImportError: | |
21 | import Image | |
5a34a80d JW |
22 | |
23 | from mediagoblin.processing import BadMediaFail | |
10085b77 | 24 | from mediagoblin.media_types.audio import audioprocessing |
5a34a80d JW |
25 | |
26 | ||
27 | _log = logging.getLogger(__name__) | |
28 | ||
c56d4b55 | 29 | CPU_COUNT = 2 # Just assuming for now |
5a34a80d JW |
30 | |
31 | # IMPORT MULTIPROCESSING | |
32 | try: | |
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') | |
39 | except ImportError: | |
40 | _log.warning('Could not import multiprocessing, assuming 2 CPU cores') | |
41 | ||
42 | # IMPORT GOBJECT | |
43 | try: | |
44 | import gobject | |
45 | gobject.threads_init() | |
46 | except ImportError: | |
47 | raise Exception('gobject could not be found') | |
48 | ||
49 | # IMPORT PYGST | |
50 | try: | |
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 | |
60 | except ImportError: | |
61 | raise Exception('gst/pygst >= 0.10 could not be imported') | |
62 | ||
10085b77 JW |
63 | import numpy |
64 | ||
c56d4b55 | 65 | |
10085b77 JW |
66 | class 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 |
130 | class 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 | ||
225 | if __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) |