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 | ||
5ac1fe80 | 17 | import argparse |
5a34a80d | 18 | import logging |
25e39842 | 19 | from tempfile import NamedTemporaryFile |
5a34a80d JW |
20 | import os |
21 | ||
22 | from mediagoblin import mg_globals as mgg | |
5ac1fe80 RE |
23 | from mediagoblin.processing import ( |
24 | create_pub_filepath, BadMediaFail, FilenameBuilder, | |
25 | ProgressCallback, MediaProcessor, ProcessingManager, | |
26 | request_from_args, get_orig_filename, | |
27 | store_public, copy_original) | |
5a34a80d | 28 | |
5ac1fe80 RE |
29 | from mediagoblin.media_types.audio.transcoders import ( |
30 | AudioTranscoder,AudioThumbnailer) | |
5a34a80d | 31 | |
10085b77 | 32 | _log = logging.getLogger(__name__) |
5a34a80d | 33 | |
df68438a RE |
34 | MEDIA_TYPE = 'mediagoblin.media_types.audio' |
35 | ||
64712915 | 36 | |
ec4261a4 | 37 | def sniff_handler(media_file, **kw): |
df68438a | 38 | _log.info('Sniffing {0}'.format(MEDIA_TYPE)) |
196a5181 | 39 | try: |
4f4f2531 | 40 | transcoder = AudioTranscoder() |
ec4261a4 | 41 | data = transcoder.discover(media_file.name) |
4f4f2531 JW |
42 | except BadMediaFail: |
43 | _log.debug('Audio discovery raised BadMediaFail') | |
df68438a | 44 | return None |
ec4261a4 | 45 | |
4f4f2531 | 46 | if data.is_audio == True and data.is_video == False: |
df68438a | 47 | return MEDIA_TYPE |
10085b77 | 48 | |
df68438a | 49 | return None |
5a34a80d | 50 | |
64712915 | 51 | |
fb46fa66 | 52 | def process_audio(proc_state): |
45ab3e07 | 53 | """Code to process uploaded audio. Will be run by celery. |
5a34a80d | 54 | |
45ab3e07 SS |
55 | A Workbench() represents a local tempory dir. It is automatically |
56 | cleaned up when this function exits. | |
57 | """ | |
fb46fa66 E |
58 | entry = proc_state.entry |
59 | workbench = proc_state.workbench | |
45ab3e07 | 60 | audio_config = mgg.global_config['media_type:mediagoblin.media_types.audio'] |
5a34a80d JW |
61 | |
62 | queued_filepath = entry.queued_media_file | |
63 | queued_filename = workbench.localized_file( | |
64 | mgg.queue_store, queued_filepath, | |
65 | 'source') | |
b781c3c9 | 66 | name_builder = FilenameBuilder(queued_filename) |
5a34a80d | 67 | |
b781c3c9 | 68 | webm_audio_filepath = create_pub_filepath( |
5a34a80d JW |
69 | entry, |
70 | '{original}.webm'.format( | |
71 | original=os.path.splitext( | |
72 | queued_filepath[-1])[0])) | |
73 | ||
b781c3c9 JK |
74 | if audio_config['keep_original']: |
75 | with open(queued_filename, 'rb') as queued_file: | |
76 | original_filepath = create_pub_filepath( | |
77 | entry, name_builder.fill('{basename}{ext}')) | |
78 | ||
79 | with mgg.public_store.get_file(original_filepath, 'wb') as \ | |
80 | original_file: | |
81 | _log.debug('Saving original...') | |
82 | original_file.write(queued_file.read()) | |
83 | ||
84 | entry.media_files['original'] = original_filepath | |
85 | ||
10085b77 | 86 | transcoder = AudioTranscoder() |
5a34a80d | 87 | |
25e39842 | 88 | with NamedTemporaryFile(dir=workbench.dir) as webm_audio_tmp: |
64712915 | 89 | progress_callback = ProgressCallback(entry) |
5a34a80d JW |
90 | |
91 | transcoder.transcode( | |
92 | queued_filename, | |
b781c3c9 | 93 | webm_audio_tmp.name, |
64712915 JW |
94 | quality=audio_config['quality'], |
95 | progress_callback=progress_callback) | |
5a34a80d | 96 | |
a855e92a | 97 | transcoder.discover(webm_audio_tmp.name) |
5a34a80d JW |
98 | |
99 | _log.debug('Saving medium...') | |
b781c3c9 JK |
100 | mgg.public_store.get_file(webm_audio_filepath, 'wb').write( |
101 | webm_audio_tmp.read()) | |
5a34a80d | 102 | |
b781c3c9 | 103 | entry.media_files['webm_audio'] = webm_audio_filepath |
5a34a80d | 104 | |
c7cf6235 | 105 | # entry.media_data_init(length=int(data.audiolength)) |
5a34a80d | 106 | |
10085b77 JW |
107 | if audio_config['create_spectrogram']: |
108 | spectrogram_filepath = create_pub_filepath( | |
109 | entry, | |
110 | '{original}-spectrogram.jpg'.format( | |
111 | original=os.path.splitext( | |
112 | queued_filepath[-1])[0])) | |
113 | ||
25e39842 | 114 | with NamedTemporaryFile(dir=workbench.dir, suffix='.ogg') as wav_tmp: |
549000d9 | 115 | _log.info('Creating OGG source for spectrogram') |
10085b77 JW |
116 | transcoder.transcode( |
117 | queued_filename, | |
118 | wav_tmp.name, | |
549000d9 JW |
119 | mux_string='vorbisenc quality={0} ! oggmux'.format( |
120 | audio_config['quality'])) | |
10085b77 JW |
121 | |
122 | thumbnailer = AudioThumbnailer() | |
123 | ||
25e39842 | 124 | with NamedTemporaryFile(dir=workbench.dir, suffix='.jpg') as spectrogram_tmp: |
10085b77 JW |
125 | thumbnailer.spectrogram( |
126 | wav_tmp.name, | |
127 | spectrogram_tmp.name, | |
196a5181 JW |
128 | width=mgg.global_config['media:medium']['max_width'], |
129 | fft_size=audio_config['spectrogram_fft_size']) | |
10085b77 JW |
130 | |
131 | _log.debug('Saving spectrogram...') | |
132 | mgg.public_store.get_file(spectrogram_filepath, 'wb').write( | |
133 | spectrogram_tmp.read()) | |
134 | ||
135 | entry.media_files['spectrogram'] = spectrogram_filepath | |
136 | ||
25e39842 | 137 | with NamedTemporaryFile(dir=workbench.dir, suffix='.jpg') as thumb_tmp: |
10085b77 JW |
138 | thumbnailer.thumbnail_spectrogram( |
139 | spectrogram_tmp.name, | |
140 | thumb_tmp.name, | |
141 | (mgg.global_config['media:thumb']['max_width'], | |
142 | mgg.global_config['media:thumb']['max_height'])) | |
143 | ||
144 | thumb_filepath = create_pub_filepath( | |
145 | entry, | |
146 | '{original}-thumbnail.jpg'.format( | |
147 | original=os.path.splitext( | |
148 | queued_filepath[-1])[0])) | |
149 | ||
150 | mgg.public_store.get_file(thumb_filepath, 'wb').write( | |
151 | thumb_tmp.read()) | |
152 | ||
153 | entry.media_files['thumb'] = thumb_filepath | |
154 | else: | |
5a34a80d | 155 | entry.media_files['thumb'] = ['fake', 'thumb', 'path.jpg'] |
196a5181 | 156 | |
36ae6bcb SS |
157 | # Remove queued media file from storage and database. |
158 | # queued_filepath is in the task_id directory which should | |
159 | # be removed too, but fail if the directory is not empty to be on | |
160 | # the super-safe side. | |
161 | mgg.queue_store.delete_file(queued_filepath) # rm file | |
162 | mgg.queue_store.delete_dir(queued_filepath[:-1]) # rm dir | |
163 | entry.queued_media_file = [] | |
5ac1fe80 RE |
164 | |
165 | ||
166 | class CommonAudioProcessor(MediaProcessor): | |
167 | """ | |
168 | Provides a base for various audio processing steps | |
169 | """ | |
170 | ||
171 | def common_setup(self): | |
172 | """ | |
173 | """ | |
174 | self.audio_config = mgg \ | |
175 | .global_config['media_type:mediagoblin.media_types.audio'] | |
176 | ||
177 | # Pull down and set up the original file | |
178 | self.orig_filename = get_orig_filename( | |
179 | self.entry, self.workbench) | |
180 | self.name_builder = FilenameBuilder(self.orig_filename) | |
181 | ||
182 | self.spectrogram_tmp = os.path.join(self.workbench.dir, | |
183 | self.name_builder.fill( | |
184 | '{basename}-spectrogram.jpg')) | |
185 | ||
186 | self.transcoder = AudioTranscoder() | |
187 | self.thumbnailer = AudioThumbnailer() | |
188 | ||
189 | def copy_original(self): | |
190 | if self.audio_config['keep_original']: | |
191 | copy_original( | |
192 | self.entry, self.orig_filename, | |
193 | self.name_builder.fill('{basename}{ext}')) | |
194 | ||
195 | def transcode(self, quality=None): | |
196 | if not quality: | |
197 | quality = self.audio_config['quality'] | |
198 | ||
199 | progress_callback = ProgressCallback(self.entry) | |
200 | webm_audio_tmp = os.path.join(self.workbench.dir, | |
201 | self.name_builder.fill( | |
202 | '{basename}{ext}')) | |
203 | ||
c6eaa555 RE |
204 | #webm_audio_filepath = create_pub_filepath( |
205 | # self.entry, | |
206 | # '{original}.webm'.format( | |
207 | # original=os.path.splitext( | |
208 | # self.orig_filename[-1])[0])) | |
5ac1fe80 RE |
209 | |
210 | self.transcoder.transcode( | |
211 | self.orig_filename, | |
212 | webm_audio_tmp, | |
213 | quality=quality, | |
214 | progress_callback=progress_callback) | |
215 | ||
216 | self.transcoder.discover(webm_audio_tmp) | |
217 | ||
218 | _log.debug('Saving medium...') | |
219 | store_public(self.entry, 'medium', webm_audio_tmp, | |
c6eaa555 | 220 | self.name_builder.fill('{basename}.medium{ext}')) |
5ac1fe80 RE |
221 | |
222 | def create_spectrogram(self, quality=None, max_width=None, fft_size=None): | |
223 | if not quality: | |
224 | quality = self.audio_config['quality'] | |
225 | if not max_width: | |
226 | max_width = mgg.global_config['media:medium']['max_width'] | |
227 | if not fft_size: | |
228 | fft_size = self.audio_config['spectrogram_fft_size'] | |
229 | ||
c6eaa555 RE |
230 | #spectrogram_filepath = create_pub_filepath( |
231 | # self.entry, | |
232 | # '{original}-spectrogram.jpg'.format( | |
233 | # original=os.path.splitext( | |
234 | # self.orig_filename[-1])[0])) | |
5ac1fe80 RE |
235 | |
236 | wav_tmp = os.path.join(self.workbench.dir, self.name_builder.fill( | |
237 | '{basename}.ogg')) | |
238 | ||
239 | _log.info('Creating OGG source for spectrogram') | |
240 | self.transcoder.transcode( | |
241 | self.orig_filename, | |
242 | wav_tmp, | |
243 | mux_string='vorbisenc quality={0} ! oggmux'.format(quality)) | |
244 | ||
245 | self.thumbnailer.spectrogram( | |
246 | wav_tmp, | |
247 | self.spectrogram_tmp, | |
248 | width=max_width, | |
249 | fft_size=fft_size) | |
250 | ||
251 | _log.debug('Saving spectrogram...') | |
252 | store_public(self.entry, 'spectrogram', self.spectrogram_tmp, | |
c6eaa555 | 253 | self.name_builder.fill('{basename}.spectrogram.jpg')) |
5ac1fe80 RE |
254 | |
255 | def generate_thumb(self, size=None): | |
256 | if not size: | |
257 | max_width = mgg.global_config['medium:thumb']['max_width'] | |
258 | max_height = mgg.global_config['medium:thumb']['max_height'] | |
259 | size = (max_width, max_height) | |
260 | ||
261 | thumb_tmp = os.path.join(self.workbench.dir, self.name_builder.fill( | |
262 | '{basename}-thumbnail.jpg')) | |
263 | ||
264 | self.thumbnailer.thumbnail_spectrogram( | |
265 | self.spectrogram_tmp, | |
266 | thumb_tmp, | |
267 | size) | |
268 | ||
c6eaa555 RE |
269 | #thumb_filepath = create_pub_filepath( |
270 | # self.entry, | |
271 | # '{original}-thumbnail.jpg'.format( | |
272 | # original=os.path.splitext( | |
273 | # self.orig_filename[-1])[0])) | |
5ac1fe80 | 274 | |
c6eaa555 RE |
275 | store_public(self.entry, 'thumb', thumb_tmp, |
276 | self.name_builder.fill('{basename}.thumbnail.jpg')) | |
5ac1fe80 RE |
277 | |
278 | ||
279 | class InitialProcessor(CommonAudioProcessor): | |
280 | """ | |
281 | Initial processing steps for new audio | |
282 | """ | |
283 | name = "initial" | |
284 | description = "Initial processing" | |
285 | ||
286 | @classmethod | |
287 | def media_is_eligible(cls, entry=None, state=None): | |
288 | """ | |
289 | Determine if this media type is eligible for processing | |
290 | """ | |
291 | if not state: | |
292 | state = entry.state | |
293 | return state in ( | |
294 | "unprocessed", "failed") | |
295 | ||
296 | @classmethod | |
297 | def generate_parser(cls): | |
298 | parser = argparse.ArgumentParser( | |
299 | description=cls.description, | |
300 | prog=cls.name) | |
301 | ||
302 | parser.add_argument( | |
303 | '--quality', | |
304 | help='vorbisenc quality') | |
305 | ||
306 | parser.add_argument( | |
307 | '--fft_size', | |
308 | type=int, | |
309 | help='spectrogram fft size') | |
310 | ||
311 | parser.add_argument( | |
312 | '--thumb_size', | |
313 | metavar=('max_width', 'max_height'), | |
314 | type=int) | |
315 | ||
316 | parser.add_argument( | |
317 | '--medium_width', | |
318 | type=int, | |
319 | help='The width of the spectogram') | |
320 | ||
321 | parser.add_argument( | |
322 | '--create_spectrogram', | |
323 | action='store_true', | |
324 | help='Create spectogram and thumbnail') | |
325 | ||
326 | return parser | |
327 | ||
328 | @classmethod | |
329 | def args_to_request(cls, args): | |
330 | return request_from_args( | |
331 | args, ['create_spectrogram', 'quality', 'fft_size', | |
332 | 'thumb_size', 'medium_width']) | |
333 | ||
334 | def process(self, quality=None, fft_size=None, thumb_size=None, | |
335 | create_spectrogram=None, medium_width=None): | |
550af89f RE |
336 | self.common_setup() |
337 | ||
5ac1fe80 RE |
338 | if not create_spectrogram: |
339 | create_spectrogram = self.audio_config['create_spectrogram'] | |
340 | ||
5ac1fe80 RE |
341 | self.transcode(quality=quality) |
342 | self.copy_original() | |
343 | ||
344 | if create_spectrogram: | |
345 | self.create_spectrogram(quality=quality, max_width=medium_width, | |
346 | fft_size=fft_size) | |
347 | self.generate_thumb(size=thumb_size) | |
348 | self.delete_queue_file() | |
349 | ||
350 | ||
351 | class AudioProcessingManager(ProcessingManager): | |
352 | def __init__(self): | |
353 | super(self.__class__, self).__init__() | |
354 | self.add_processor(InitialProcessor) |