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