1 # GNU MediaGoblin -- federated, autonomous media hosting
2 # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
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.
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.
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/>.
23 from mediagoblin
import mg_globals
as mgg
24 from mediagoblin
.processing
import (
25 BadMediaFail
, FilenameBuilder
,
26 ProgressCallback
, MediaProcessor
, ProcessingManager
,
27 request_from_args
, get_process_filename
,
28 store_public
, copy_original
)
30 from mediagoblin
.media_types
.audio
.transcoders
import (
31 AudioTranscoder
, AudioThumbnailer
)
32 from mediagoblin
.media_types
.tools
import discover
34 _log
= logging
.getLogger(__name__
)
36 MEDIA_TYPE
= 'mediagoblin.media_types.audio'
39 def sniff_handler(media_file
, filename
):
40 _log
.info('Sniffing {0}'.format(MEDIA_TYPE
))
42 data
= discover(media_file
.name
)
43 except Exception as e
:
44 _log
.info(six
.text_type(e
))
46 if data
and data
.get_audio_streams() and not data
.get_video_streams():
51 class CommonAudioProcessor(MediaProcessor
):
53 Provides a base for various audio processing steps
55 acceptable_files
= ['original', 'best_quality', 'webm_audio']
57 def common_setup(self
):
59 Setup the workbench directory and pull down the original file, add
60 the audio_config, transcoder, thumbnailer and spectrogram_tmp path
62 self
.audio_config
= mgg \
63 .global_config
['plugins']['mediagoblin.media_types.audio']
65 # Pull down and set up the processing file
66 self
.process_filename
= get_process_filename(
67 self
.entry
, self
.workbench
, self
.acceptable_files
)
68 self
.name_builder
= FilenameBuilder(self
.process_filename
)
70 self
.transcoder
= AudioTranscoder()
71 self
.thumbnailer
= AudioThumbnailer()
73 def copy_original(self
):
74 if self
.audio_config
['keep_original']:
76 self
.entry
, self
.process_filename
,
77 self
.name_builder
.fill('{basename}{ext}'))
81 If there is no original, keep the best file that we have
83 if not self
.entry
.media_files
.get('best_quality'):
84 # Save the best quality file if no original?
85 if not self
.entry
.media_files
.get('original') and \
86 self
.entry
.media_files
.get('webm_audio'):
87 self
.entry
.media_files
['best_quality'] = self
.entry \
88 .media_files
['webm_audio']
90 def _skip_processing(self
, keyname
, **kwargs
):
91 file_metadata
= self
.entry
.get_file_metadata(keyname
)
97 if keyname
== 'webm_audio':
98 if kwargs
.get('quality') != file_metadata
.get('quality'):
100 elif keyname
== 'spectrogram':
101 if kwargs
.get('max_width') != file_metadata
.get('max_width'):
103 elif kwargs
.get('fft_size') != file_metadata
.get('fft_size'):
105 elif keyname
== 'thumb':
106 if kwargs
.get('size') != file_metadata
.get('size'):
111 def transcode(self
, quality
=None):
113 quality
= self
.audio_config
['quality']
115 if self
._skip
_processing
('webm_audio', quality
=quality
):
118 progress_callback
= ProgressCallback(self
.entry
)
119 webm_audio_tmp
= os
.path
.join(self
.workbench
.dir,
120 self
.name_builder
.fill(
123 self
.transcoder
.transcode(
124 self
.process_filename
,
127 progress_callback
=progress_callback
)
131 _log
.debug('Saving medium...')
132 store_public(self
.entry
, 'webm_audio', webm_audio_tmp
,
133 self
.name_builder
.fill('{basename}.medium.webm'))
135 self
.entry
.set_file_metadata('webm_audio', **{'quality': quality
})
137 def create_spectrogram(self
, max_width
=None, fft_size
=None):
139 max_width
= mgg
.global_config
['media:medium']['max_width']
141 fft_size
= self
.audio_config
['spectrogram_fft_size']
143 if self
._skip
_processing
('spectrogram', max_width
=max_width
,
146 wav_tmp
= os
.path
.join(self
.workbench
.dir, self
.name_builder
.fill(
148 _log
.info('Creating OGG source for spectrogram')
149 self
.transcoder
.transcode(self
.process_filename
, wav_tmp
,
151 spectrogram_tmp
= os
.path
.join(self
.workbench
.dir,
152 self
.name_builder
.fill(
153 '{basename}-spectrogram.jpg'))
154 self
.thumbnailer
.spectrogram(
160 _log
.debug('Saving spectrogram...')
161 store_public(self
.entry
, 'spectrogram', spectrogram_tmp
,
162 self
.name_builder
.fill('{basename}.spectrogram.jpg'))
164 file_metadata
= {'max_width': max_width
,
165 'fft_size': fft_size
}
166 self
.entry
.set_file_metadata('spectrogram', **file_metadata
)
168 def generate_thumb(self
, size
=None):
170 max_width
= mgg
.global_config
['media:thumb']['max_width']
171 max_height
= mgg
.global_config
['media:thumb']['max_height']
172 size
= (max_width
, max_height
)
174 if self
._skip
_processing
('thumb', size
=size
):
177 thumb_tmp
= os
.path
.join(self
.workbench
.dir, self
.name_builder
.fill(
178 '{basename}-thumbnail.jpg'))
180 # We need the spectrogram to create a thumbnail
181 spectrogram
= self
.entry
.media_files
.get('spectrogram')
183 _log
.info('No spectrogram found, we will create one.')
184 self
.create_spectrogram()
185 spectrogram
= self
.entry
.media_files
['spectrogram']
187 spectrogram_filepath
= mgg
.public_store
.get_local_path(spectrogram
)
189 self
.thumbnailer
.thumbnail_spectrogram(
190 spectrogram_filepath
,
194 store_public(self
.entry
, 'thumb', thumb_tmp
,
195 self
.name_builder
.fill('{basename}.thumbnail.jpg'))
197 self
.entry
.set_file_metadata('thumb', **{'size': size
})
200 class InitialProcessor(CommonAudioProcessor
):
202 Initial processing steps for new audio
205 description
= "Initial processing"
208 def media_is_eligible(cls
, entry
=None, state
=None):
210 Determine if this media type is eligible for processing
215 "unprocessed", "failed")
218 def generate_parser(cls
):
219 parser
= argparse
.ArgumentParser(
220 description
=cls
.description
,
226 help='vorbisenc quality. Range: -0.1..1')
231 help='spectrogram fft size')
236 metavar
=('max_width', 'max_height'),
238 help='minimum size is 100 x 100')
243 help='The width of the spectogram')
248 def args_to_request(cls
, args
):
249 return request_from_args(
250 args
, ['quality', 'fft_size',
251 'thumb_size', 'medium_width'])
253 def process(self
, quality
=None, fft_size
=None, thumb_size
=None,
257 self
.transcode(quality
=quality
)
260 self
.create_spectrogram(max_width
=medium_width
, fft_size
=fft_size
)
261 self
.generate_thumb(size
=thumb_size
)
263 self
.delete_queue_file()
266 class Resizer(CommonAudioProcessor
):
268 Thumbnail and spectogram resizing process steps for processed audio
271 description
= 'Resize thumbnail or spectogram'
272 thumb_size
= 'thumb_size'
275 def media_is_eligible(cls
, entry
=None, state
=None):
277 Determine if this media entry is eligible for processing
281 return state
in 'processed'
284 def generate_parser(cls
):
285 parser
= argparse
.ArgumentParser(
286 description
=cls
.description
,
292 help='spectrogram fft size')
297 metavar
=('max_width', 'max_height'),
299 help='minimum size is 100 x 100')
304 help='The width of the spectogram')
308 choices
=['thumb', 'spectrogram'])
313 def args_to_request(cls
, args
):
314 return request_from_args(
315 args
, ['thumb_size', 'file', 'fft_size', 'medium_width'])
317 def process(self
, file, thumb_size
=None, fft_size
=None,
322 self
.generate_thumb(size
=thumb_size
)
323 elif file == 'spectrogram':
324 self
.create_spectrogram(max_width
=medium_width
, fft_size
=fft_size
)
327 class Transcoder(CommonAudioProcessor
):
329 Transcoding processing steps for processed audio
332 description
= 'Re-transcode audio'
335 def media_is_eligible(cls
, entry
=None, state
=None):
338 return state
in 'processed'
341 def generate_parser(cls
):
342 parser
= argparse
.ArgumentParser(
343 description
=cls
.description
,
348 help='vorbisenc quality. Range: -0.1..1')
353 def args_to_request(cls
, args
):
354 return request_from_args(
357 def process(self
, quality
=None):
359 self
.transcode(quality
=quality
)
362 class AudioProcessingManager(ProcessingManager
):
364 super(AudioProcessingManager
, self
).__init
__()
365 self
.add_processor(InitialProcessor
)
366 self
.add_processor(Resizer
)
367 self
.add_processor(Transcoder
)