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
)
31 _log
= logging
.getLogger(__name__
)
33 MEDIA_TYPE
= 'mediagoblin.media_types.audio'
36 def sniff_handler(media_file
, **kw
):
37 _log
.info('Sniffing {0}'.format(MEDIA_TYPE
))
39 transcoder
= AudioTranscoder()
40 data
= transcoder
.discover(media_file
.name
)
42 _log
.debug('Audio discovery raised BadMediaFail')
45 if data
.is_audio
is True and data
.is_video
is False:
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
['media_type: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 transcode(self
, quality
=None):
92 quality
= self
.audio_config
['quality']
94 progress_callback
= ProgressCallback(self
.entry
)
95 webm_audio_tmp
= os
.path
.join(self
.workbench
.dir,
96 self
.name_builder
.fill(
99 self
.transcoder
.transcode(
100 self
.process_filename
,
103 progress_callback
=progress_callback
)
105 self
.transcoder
.discover(webm_audio_tmp
)
109 _log
.debug('Saving medium...')
110 store_public(self
.entry
, 'webm_audio', webm_audio_tmp
,
111 self
.name_builder
.fill('{basename}.medium.webm'))
113 def create_spectrogram(self
, max_width
=None, fft_size
=None):
115 max_width
= mgg
.global_config
['media:medium']['max_width']
117 fft_size
= self
.audio_config
['spectrogram_fft_size']
119 wav_tmp
= os
.path
.join(self
.workbench
.dir, self
.name_builder
.fill(
122 _log
.info('Creating OGG source for spectrogram')
123 self
.transcoder
.transcode(
124 self
.process_filename
,
126 mux_string
='vorbisenc quality={0} ! oggmux'.format(
127 self
.audio_config
['quality']))
129 spectrogram_tmp
= os
.path
.join(self
.workbench
.dir,
130 self
.name_builder
.fill(
131 '{basename}-spectrogram.jpg'))
133 self
.thumbnailer
.spectrogram(
139 _log
.debug('Saving spectrogram...')
140 store_public(self
.entry
, 'spectrogram', spectrogram_tmp
,
141 self
.name_builder
.fill('{basename}.spectrogram.jpg'))
143 def generate_thumb(self
, size
=None):
145 max_width
= mgg
.global_config
['media:thumb']['max_width']
146 max_height
= mgg
.global_config
['media:thumb']['max_height']
147 size
= (max_width
, max_height
)
149 thumb_tmp
= os
.path
.join(self
.workbench
.dir, self
.name_builder
.fill(
150 '{basename}-thumbnail.jpg'))
152 # We need the spectrogram to create a thumbnail
153 spectrogram
= self
.entry
.media_files
.get('spectrogram')
155 _log
.info('No spectrogram found, we will create one.')
156 self
.create_spectrogram()
157 spectrogram
= self
.entry
.media_files
['spectrogram']
159 spectrogram_filepath
= mgg
.public_store
.get_local_path(spectrogram
)
161 self
.thumbnailer
.thumbnail_spectrogram(
162 spectrogram_filepath
,
166 store_public(self
.entry
, 'thumb', thumb_tmp
,
167 self
.name_builder
.fill('{basename}.thumbnail.jpg'))
170 class InitialProcessor(CommonAudioProcessor
):
172 Initial processing steps for new audio
175 description
= "Initial processing"
178 def media_is_eligible(cls
, entry
=None, state
=None):
180 Determine if this media type is eligible for processing
185 "unprocessed", "failed")
188 def generate_parser(cls
):
189 parser
= argparse
.ArgumentParser(
190 description
=cls
.description
,
196 help='vorbisenc quality. Range: -0.1..1')
201 help='spectrogram fft size')
206 metavar
=('max_width', 'max_height'),
208 help='minimum size is 100 x 100')
213 help='The width of the spectogram')
216 '--create_spectrogram',
218 help='Create spectogram and thumbnail, will default to config')
223 def args_to_request(cls
, args
):
224 return request_from_args(
225 args
, ['create_spectrogram', 'quality', 'fft_size',
226 'thumb_size', 'medium_width'])
228 def process(self
, quality
=None, fft_size
=None, thumb_size
=None,
229 create_spectrogram
=None, medium_width
=None):
232 if not create_spectrogram
:
233 create_spectrogram
= self
.audio_config
['create_spectrogram']
235 self
.transcode(quality
=quality
)
238 if create_spectrogram
:
239 self
.create_spectrogram(max_width
=medium_width
, fft_size
=fft_size
)
240 self
.generate_thumb(size
=thumb_size
)
241 self
.delete_queue_file()
244 class Resizer(CommonAudioProcessor
):
246 Thumbnail and spectogram resizing process steps for processed audio
249 description
= 'Resize thumbnail or spectogram'
250 thumb_size
= 'thumb_size'
253 def media_is_eligible(cls
, entry
=None, state
=None):
255 Determine if this media entry is eligible for processing
259 return state
in 'processed'
262 def generate_parser(cls
):
263 parser
= argparse
.ArgumentParser(
264 description
=cls
.description
,
270 help='spectrogram fft size')
275 metavar
=('max_width', 'max_height'),
277 help='minimum size is 100 x 100')
282 help='The width of the spectogram')
286 choices
=['thumb', 'spectrogram'])
291 def args_to_request(cls
, args
):
292 return request_from_args(
293 args
, ['thumb_size', 'file', 'fft_size', 'medium_width'])
295 def process(self
, file, thumb_size
=None, fft_size
=None,
300 self
.generate_thumb(size
=thumb_size
)
301 elif file == 'spectrogram':
302 self
.create_spectrogram(max_width
=medium_width
, fft_size
=fft_size
)
305 class Transcoder(CommonAudioProcessor
):
307 Transcoding processing steps for processed audio
310 description
= 'Re-transcode audio'
313 def media_is_eligible(cls
, entry
=None, state
=None):
316 return state
in 'processed'
319 def generate_parser(cls
):
320 parser
= argparse
.ArgumentParser(
321 description
=cls
.description
,
326 help='vorbisenc quality. Range: -0.1..1')
331 def args_to_request(cls
, args
):
332 return request_from_args(
335 def process(self
, quality
=None):
337 self
.transcode(quality
=quality
)
340 class AudioProcessingManager(ProcessingManager
):
342 super(self
.__class
__, self
).__init
__()
343 self
.add_processor(InitialProcessor
)
344 self
.add_processor(Resizer
)
345 self
.add_processor(Transcoder
)