typos
[mediagoblin.git] / mediagoblin / media_types / audio / processing.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
5ac1fe80 17import argparse
5a34a80d 18import logging
5a34a80d
JW
19import os
20
21from mediagoblin import mg_globals as mgg
5ac1fe80 22from mediagoblin.processing import (
440e33aa 23 BadMediaFail, FilenameBuilder,
5ac1fe80
RE
24 ProgressCallback, MediaProcessor, ProcessingManager,
25 request_from_args, get_orig_filename,
26 store_public, copy_original)
5a34a80d 27
5ac1fe80 28from mediagoblin.media_types.audio.transcoders import (
440e33aa 29 AudioTranscoder, AudioThumbnailer)
5a34a80d 30
10085b77 31_log = logging.getLogger(__name__)
5a34a80d 32
df68438a
RE
33MEDIA_TYPE = 'mediagoblin.media_types.audio'
34
64712915 35
ec4261a4 36def sniff_handler(media_file, **kw):
df68438a 37 _log.info('Sniffing {0}'.format(MEDIA_TYPE))
196a5181 38 try:
4f4f2531 39 transcoder = AudioTranscoder()
ec4261a4 40 data = transcoder.discover(media_file.name)
4f4f2531
JW
41 except BadMediaFail:
42 _log.debug('Audio discovery raised BadMediaFail')
df68438a 43 return None
ec4261a4 44
440e33aa 45 if data.is_audio is True and data.is_video is False:
df68438a 46 return MEDIA_TYPE
10085b77 47
df68438a 48 return None
5a34a80d 49
64712915 50
5ac1fe80
RE
51class CommonAudioProcessor(MediaProcessor):
52 """
53 Provides a base for various audio processing steps
54 """
55
56 def common_setup(self):
57 """
440e33aa
RE
58 Setup the workbench directory and pull down the original file, add
59 the audio_config, transcoder, thumbnailer and spectrogram_tmp path
5ac1fe80
RE
60 """
61 self.audio_config = mgg \
62 .global_config['media_type:mediagoblin.media_types.audio']
63
64 # Pull down and set up the original file
65 self.orig_filename = get_orig_filename(
66 self.entry, self.workbench)
67 self.name_builder = FilenameBuilder(self.orig_filename)
68
5ac1fe80
RE
69 self.transcoder = AudioTranscoder()
70 self.thumbnailer = AudioThumbnailer()
71
72 def copy_original(self):
73 if self.audio_config['keep_original']:
74 copy_original(
75 self.entry, self.orig_filename,
76 self.name_builder.fill('{basename}{ext}'))
77
78 def transcode(self, quality=None):
79 if not quality:
80 quality = self.audio_config['quality']
81
82 progress_callback = ProgressCallback(self.entry)
83 webm_audio_tmp = os.path.join(self.workbench.dir,
84 self.name_builder.fill(
85 '{basename}{ext}'))
86
5ac1fe80
RE
87 self.transcoder.transcode(
88 self.orig_filename,
89 webm_audio_tmp,
90 quality=quality,
91 progress_callback=progress_callback)
92
93 self.transcoder.discover(webm_audio_tmp)
94
95 _log.debug('Saving medium...')
9448a98e
RE
96 store_public(self.entry, 'webm_audio', webm_audio_tmp,
97 self.name_builder.fill('{basename}.medium.webm'))
5ac1fe80 98
ad80fc8a 99 def create_spectrogram(self, max_width=None, fft_size=None):
5ac1fe80
RE
100 if not max_width:
101 max_width = mgg.global_config['media:medium']['max_width']
102 if not fft_size:
103 fft_size = self.audio_config['spectrogram_fft_size']
104
5ac1fe80
RE
105 wav_tmp = os.path.join(self.workbench.dir, self.name_builder.fill(
106 '{basename}.ogg'))
107
108 _log.info('Creating OGG source for spectrogram')
109 self.transcoder.transcode(
110 self.orig_filename,
111 wav_tmp,
ad80fc8a
RE
112 mux_string='vorbisenc quality={0} ! oggmux'.format(
113 self.audio_config['quality']))
5ac1fe80 114
d8f886dc 115 spectrogram_tmp = os.path.join(self.workbench.dir,
347ef583
RE
116 self.name_builder.fill(
117 '{basename}-spectrogram.jpg'))
d8f886dc 118
5ac1fe80
RE
119 self.thumbnailer.spectrogram(
120 wav_tmp,
d8f886dc 121 spectrogram_tmp,
5ac1fe80
RE
122 width=max_width,
123 fft_size=fft_size)
124
125 _log.debug('Saving spectrogram...')
d8f886dc 126 store_public(self.entry, 'spectrogram', spectrogram_tmp,
c6eaa555 127 self.name_builder.fill('{basename}.spectrogram.jpg'))
5ac1fe80
RE
128
129 def generate_thumb(self, size=None):
130 if not size:
776e4d7a
RE
131 max_width = mgg.global_config['media:thumb']['max_width']
132 max_height = mgg.global_config['media:thumb']['max_height']
5ac1fe80
RE
133 size = (max_width, max_height)
134
135 thumb_tmp = os.path.join(self.workbench.dir, self.name_builder.fill(
136 '{basename}-thumbnail.jpg'))
137
d8f886dc
RE
138 # We need the spectrogram to create a thumbnail
139 spectrogram = self.entry.media_files.get('spectrogram')
140 if not spectrogram:
141 _log.info('No spectrogram found, we will create one.')
142 self.create_spectrogram()
143 spectrogram = self.entry.media_files['spectrogram']
144
145 spectrogram_filepath = mgg.public_store.get_local_path(spectrogram)
146
5ac1fe80 147 self.thumbnailer.thumbnail_spectrogram(
d8f886dc 148 spectrogram_filepath,
5ac1fe80 149 thumb_tmp,
b95cc59b 150 tuple(size))
5ac1fe80 151
c6eaa555
RE
152 store_public(self.entry, 'thumb', thumb_tmp,
153 self.name_builder.fill('{basename}.thumbnail.jpg'))
5ac1fe80
RE
154
155
156class InitialProcessor(CommonAudioProcessor):
157 """
158 Initial processing steps for new audio
159 """
160 name = "initial"
161 description = "Initial processing"
162
163 @classmethod
164 def media_is_eligible(cls, entry=None, state=None):
165 """
166 Determine if this media type is eligible for processing
167 """
168 if not state:
169 state = entry.state
170 return state in (
171 "unprocessed", "failed")
172
173 @classmethod
174 def generate_parser(cls):
175 parser = argparse.ArgumentParser(
176 description=cls.description,
177 prog=cls.name)
178
179 parser.add_argument(
180 '--quality',
52e97704 181 type=float,
d8f886dc 182 help='vorbisenc quality. Range: -0.1..1')
5ac1fe80
RE
183
184 parser.add_argument(
185 '--fft_size',
186 type=int,
187 help='spectrogram fft size')
188
189 parser.add_argument(
190 '--thumb_size',
757376e3 191 nargs=2,
5ac1fe80
RE
192 metavar=('max_width', 'max_height'),
193 type=int)
194
195 parser.add_argument(
196 '--medium_width',
197 type=int,
198 help='The width of the spectogram')
199
200 parser.add_argument(
201 '--create_spectrogram',
202 action='store_true',
b95cc59b 203 help='Create spectogram and thumbnail, will default to config')
5ac1fe80
RE
204
205 return parser
206
207 @classmethod
208 def args_to_request(cls, args):
209 return request_from_args(
210 args, ['create_spectrogram', 'quality', 'fft_size',
211 'thumb_size', 'medium_width'])
212
213 def process(self, quality=None, fft_size=None, thumb_size=None,
214 create_spectrogram=None, medium_width=None):
550af89f
RE
215 self.common_setup()
216
5ac1fe80
RE
217 if not create_spectrogram:
218 create_spectrogram = self.audio_config['create_spectrogram']
219
5ac1fe80
RE
220 self.transcode(quality=quality)
221 self.copy_original()
222
223 if create_spectrogram:
ad80fc8a 224 self.create_spectrogram(max_width=medium_width, fft_size=fft_size)
5ac1fe80
RE
225 self.generate_thumb(size=thumb_size)
226 self.delete_queue_file()
227
228
2e50e4b5
RE
229class Resizer(CommonAudioProcessor):
230 """
231 Thumbnail and spectogram resizing process steps for processed audio
232 """
233 name = 'resize'
ad80fc8a 234 description = 'Resize thumbnail or spectogram'
2e50e4b5
RE
235
236 @classmethod
237 def media_is_eligible(cls, entry=None, state=None):
238 """
239 Determine if this media entry is eligible for processing
240 """
241 if not state:
242 state = entry.state
243 return state in 'processed'
244
245 @classmethod
246 def generate_parser(cls):
247 parser = argparse.ArgumentParser(
248 description=cls.description,
249 prog=cls.name)
250
2e50e4b5
RE
251 parser.add_argument(
252 '--fft_size',
253 type=int,
254 help='spectrogram fft size')
255
256 parser.add_argument(
257 '--thumb_size',
258 nargs=2,
259 metavar=('max_width', 'max_height'),
260 type=int)
261
262 parser.add_argument(
263 '--medium_width',
264 type=int,
265 help='The width of the spectogram')
266
267 parser.add_argument(
268 'file',
269 choices=['thumb', 'spectrogram'])
270
271 return parser
272
273 @classmethod
274 def args_to_request(cls, args):
275 return request_from_args(
ad80fc8a 276 args, ['thumb_size', 'file', 'fft_size', 'medium_width'])
2e50e4b5 277
0c509b1b 278 def process(self, file, thumb_size=None, fft_size=None,
2e50e4b5
RE
279 medium_width=None):
280 self.common_setup()
ad80fc8a 281
2e50e4b5
RE
282 if file == 'thumb':
283 self.generate_thumb(size=thumb_size)
284 elif file == 'spectrogram':
ad80fc8a 285 self.create_spectrogram(max_width=medium_width, fft_size=fft_size)
2e50e4b5
RE
286
287
0c509b1b
RE
288class Transcoder(CommonAudioProcessor):
289 """
290 Transcoding processing steps for processed audio
291 """
292 name = 'transcode'
293 description = 'Re-transcode audio'
294
295 @classmethod
296 def media_is_eligible(cls, entry=None, state=None):
297 if not state:
298 state = entry.state
299 return state in 'processed'
300
301 @classmethod
302 def generate_parser(cls):
303 parser = argparse.ArgumentParser(
304 description=cls.description,
305 prog=cls.name)
306
307 parser.add_argument(
308 '--quality',
309 help='vorbisenc quality. Range: -0.1..1')
310
311 return parser
312
313 @classmethod
314 def args_to_request(cls, args):
315 return request_from_args(
316 args, ['quality'])
317
318 def process(self, quality=None):
319 self.common_setup()
320 self.transcode(quality=quality)
321
322
5ac1fe80
RE
323class AudioProcessingManager(ProcessingManager):
324 def __init__(self):
325 super(self.__class__, self).__init__()
326 self.add_processor(InitialProcessor)
2e50e4b5 327 self.add_processor(Resizer)
0c509b1b 328 self.add_processor(Transcoder)