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/>.
21 from mediagoblin
import mg_globals
as mgg
22 from mediagoblin
.processing
import (
23 BadMediaFail
, FilenameBuilder
,
24 ProgressCallback
, MediaProcessor
, ProcessingManager
,
25 request_from_args
, get_process_filename
,
26 store_public
, copy_original
)
28 from mediagoblin
.media_types
.audio
.transcoders
import (
29 AudioTranscoder
, AudioThumbnailer
)
30 from mediagoblin
.media_types
.tools
import discover
32 _log
= logging
.getLogger(__name__
)
34 MEDIA_TYPE
= 'mediagoblin.media_types.audio'
37 def sniff_handler(media_file
, filename
):
38 _log
.info('Sniffing {0}'.format(MEDIA_TYPE
))
39 data
= discover(media_file
.name
)
40 if data
and data
.get_audio_streams() and not data
.get_video_streams():
45 class CommonAudioProcessor(MediaProcessor
):
47 Provides a base for various audio processing steps
49 acceptable_files
= ['original', 'best_quality', 'webm_audio']
51 def common_setup(self
):
53 Setup the workbench directory and pull down the original file, add
54 the audio_config, transcoder, thumbnailer and spectrogram_tmp path
56 self
.audio_config
= mgg \
57 .global_config
['plugins']['mediagoblin.media_types.audio']
59 # Pull down and set up the processing file
60 self
.process_filename
= get_process_filename(
61 self
.entry
, self
.workbench
, self
.acceptable_files
)
62 self
.name_builder
= FilenameBuilder(self
.process_filename
)
64 self
.transcoder
= AudioTranscoder()
65 self
.thumbnailer
= AudioThumbnailer()
67 def copy_original(self
):
68 if self
.audio_config
['keep_original']:
70 self
.entry
, self
.process_filename
,
71 self
.name_builder
.fill('{basename}{ext}'))
75 If there is no original, keep the best file that we have
77 if not self
.entry
.media_files
.get('best_quality'):
78 # Save the best quality file if no original?
79 if not self
.entry
.media_files
.get('original') and \
80 self
.entry
.media_files
.get('webm_audio'):
81 self
.entry
.media_files
['best_quality'] = self
.entry \
82 .media_files
['webm_audio']
84 def _skip_processing(self
, keyname
, **kwargs
):
85 file_metadata
= self
.entry
.get_file_metadata(keyname
)
91 if keyname
== 'webm_audio':
92 if kwargs
.get('quality') != file_metadata
.get('quality'):
94 elif keyname
== 'spectrogram':
95 if kwargs
.get('max_width') != file_metadata
.get('max_width'):
97 elif kwargs
.get('fft_size') != file_metadata
.get('fft_size'):
99 elif keyname
== 'thumb':
100 if kwargs
.get('size') != file_metadata
.get('size'):
105 def transcode(self
, quality
=None):
107 quality
= self
.audio_config
['quality']
109 if self
._skip
_processing
('webm_audio', quality
=quality
):
112 progress_callback
= ProgressCallback(self
.entry
)
113 webm_audio_tmp
= os
.path
.join(self
.workbench
.dir,
114 self
.name_builder
.fill(
117 self
.transcoder
.transcode(
118 self
.process_filename
,
121 progress_callback
=progress_callback
)
125 _log
.debug('Saving medium...')
126 store_public(self
.entry
, 'webm_audio', webm_audio_tmp
,
127 self
.name_builder
.fill('{basename}.medium.webm'))
129 self
.entry
.set_file_metadata('webm_audio', **{'quality': quality
})
131 def create_spectrogram(self
, max_width
=None, fft_size
=None):
133 max_width
= mgg
.global_config
['media:medium']['max_width']
135 fft_size
= self
.audio_config
['spectrogram_fft_size']
137 if self
._skip
_processing
('spectrogram', max_width
=max_width
,
140 wav_tmp
= os
.path
.join(self
.workbench
.dir, self
.name_builder
.fill(
142 _log
.info('Creating OGG source for spectrogram')
143 self
.transcoder
.transcode(self
.process_filename
, wav_tmp
,
145 spectrogram_tmp
= os
.path
.join(self
.workbench
.dir,
146 self
.name_builder
.fill(
147 '{basename}-spectrogram.jpg'))
148 self
.thumbnailer
.spectrogram(
154 _log
.debug('Saving spectrogram...')
155 store_public(self
.entry
, 'spectrogram', spectrogram_tmp
,
156 self
.name_builder
.fill('{basename}.spectrogram.jpg'))
158 file_metadata
= {'max_width': max_width
,
159 'fft_size': fft_size
}
160 self
.entry
.set_file_metadata('spectrogram', **file_metadata
)
162 def generate_thumb(self
, size
=None):
164 max_width
= mgg
.global_config
['media:thumb']['max_width']
165 max_height
= mgg
.global_config
['media:thumb']['max_height']
166 size
= (max_width
, max_height
)
168 if self
._skip
_processing
('thumb', size
=size
):
171 thumb_tmp
= os
.path
.join(self
.workbench
.dir, self
.name_builder
.fill(
172 '{basename}-thumbnail.jpg'))
174 # We need the spectrogram to create a thumbnail
175 spectrogram
= self
.entry
.media_files
.get('spectrogram')
177 _log
.info('No spectrogram found, we will create one.')
178 self
.create_spectrogram()
179 spectrogram
= self
.entry
.media_files
['spectrogram']
181 spectrogram_filepath
= mgg
.public_store
.get_local_path(spectrogram
)
183 self
.thumbnailer
.thumbnail_spectrogram(
184 spectrogram_filepath
,
188 store_public(self
.entry
, 'thumb', thumb_tmp
,
189 self
.name_builder
.fill('{basename}.thumbnail.jpg'))
191 self
.entry
.set_file_metadata('thumb', **{'size': size
})
194 class InitialProcessor(CommonAudioProcessor
):
196 Initial processing steps for new audio
199 description
= "Initial processing"
202 def media_is_eligible(cls
, entry
=None, state
=None):
204 Determine if this media type is eligible for processing
209 "unprocessed", "failed")
212 def generate_parser(cls
):
213 parser
= argparse
.ArgumentParser(
214 description
=cls
.description
,
220 help='vorbisenc quality. Range: -0.1..1')
225 help='spectrogram fft size')
230 metavar
=('max_width', 'max_height'),
232 help='minimum size is 100 x 100')
237 help='The width of the spectogram')
242 def args_to_request(cls
, args
):
243 return request_from_args(
244 args
, ['quality', 'fft_size',
245 'thumb_size', 'medium_width'])
247 def process(self
, quality
=None, fft_size
=None, thumb_size
=None,
251 self
.transcode(quality
=quality
)
254 self
.create_spectrogram(max_width
=medium_width
, fft_size
=fft_size
)
255 self
.generate_thumb(size
=thumb_size
)
257 self
.delete_queue_file()
260 class Resizer(CommonAudioProcessor
):
262 Thumbnail and spectogram resizing process steps for processed audio
265 description
= 'Resize thumbnail or spectogram'
266 thumb_size
= 'thumb_size'
269 def media_is_eligible(cls
, entry
=None, state
=None):
271 Determine if this media entry is eligible for processing
275 return state
in 'processed'
278 def generate_parser(cls
):
279 parser
= argparse
.ArgumentParser(
280 description
=cls
.description
,
286 help='spectrogram fft size')
291 metavar
=('max_width', 'max_height'),
293 help='minimum size is 100 x 100')
298 help='The width of the spectogram')
302 choices
=['thumb', 'spectrogram'])
307 def args_to_request(cls
, args
):
308 return request_from_args(
309 args
, ['thumb_size', 'file', 'fft_size', 'medium_width'])
311 def process(self
, file, thumb_size
=None, fft_size
=None,
316 self
.generate_thumb(size
=thumb_size
)
317 elif file == 'spectrogram':
318 self
.create_spectrogram(max_width
=medium_width
, fft_size
=fft_size
)
321 class Transcoder(CommonAudioProcessor
):
323 Transcoding processing steps for processed audio
326 description
= 'Re-transcode audio'
329 def media_is_eligible(cls
, entry
=None, state
=None):
332 return state
in 'processed'
335 def generate_parser(cls
):
336 parser
= argparse
.ArgumentParser(
337 description
=cls
.description
,
342 help='vorbisenc quality. Range: -0.1..1')
347 def args_to_request(cls
, args
):
348 return request_from_args(
351 def process(self
, quality
=None):
353 self
.transcode(quality
=quality
)
356 class AudioProcessingManager(ProcessingManager
):
358 super(AudioProcessingManager
, self
).__init
__()
359 self
.add_processor(InitialProcessor
)
360 self
.add_processor(Resizer
)
361 self
.add_processor(Transcoder
)