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/>.
19 from tempfile
import NamedTemporaryFile
22 from mediagoblin
import mg_globals
as mgg
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
)
29 from mediagoblin
.media_types
.audio
.transcoders
import (
30 AudioTranscoder
,AudioThumbnailer
)
32 _log
= logging
.getLogger(__name__
)
34 MEDIA_TYPE
= 'mediagoblin.media_types.audio'
37 def sniff_handler(media_file
, **kw
):
38 _log
.info('Sniffing {0}'.format(MEDIA_TYPE
))
40 transcoder
= AudioTranscoder()
41 data
= transcoder
.discover(media_file
.name
)
43 _log
.debug('Audio discovery raised BadMediaFail')
46 if data
.is_audio
== True and data
.is_video
== False:
52 def process_audio(proc_state
):
53 """Code to process uploaded audio. Will be run by celery.
55 A Workbench() represents a local tempory dir. It is automatically
56 cleaned up when this function exits.
58 entry
= proc_state
.entry
59 workbench
= proc_state
.workbench
60 audio_config
= mgg
.global_config
['media_type:mediagoblin.media_types.audio']
62 queued_filepath
= entry
.queued_media_file
63 queued_filename
= workbench
.localized_file(
64 mgg
.queue_store
, queued_filepath
,
66 name_builder
= FilenameBuilder(queued_filename
)
68 webm_audio_filepath
= create_pub_filepath(
70 '{original}.webm'.format(
71 original
=os
.path
.splitext(
72 queued_filepath
[-1])[0]))
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}'))
79 with mgg
.public_store
.get_file(original_filepath
, 'wb') as \
81 _log
.debug('Saving original...')
82 original_file
.write(queued_file
.read())
84 entry
.media_files
['original'] = original_filepath
86 transcoder
= AudioTranscoder()
88 with
NamedTemporaryFile(dir=workbench
.dir) as webm_audio_tmp
:
89 progress_callback
= ProgressCallback(entry
)
94 quality
=audio_config
['quality'],
95 progress_callback
=progress_callback
)
97 transcoder
.discover(webm_audio_tmp
.name
)
99 _log
.debug('Saving medium...')
100 mgg
.public_store
.get_file(webm_audio_filepath
, 'wb').write(
101 webm_audio_tmp
.read())
103 entry
.media_files
['webm_audio'] = webm_audio_filepath
105 # entry.media_data_init(length=int(data.audiolength))
107 if audio_config
['create_spectrogram']:
108 spectrogram_filepath
= create_pub_filepath(
110 '{original}-spectrogram.jpg'.format(
111 original
=os
.path
.splitext(
112 queued_filepath
[-1])[0]))
114 with
NamedTemporaryFile(dir=workbench
.dir, suffix
='.ogg') as wav_tmp
:
115 _log
.info('Creating OGG source for spectrogram')
116 transcoder
.transcode(
119 mux_string
='vorbisenc quality={0} ! oggmux'.format(
120 audio_config
['quality']))
122 thumbnailer
= AudioThumbnailer()
124 with
NamedTemporaryFile(dir=workbench
.dir, suffix
='.jpg') as spectrogram_tmp
:
125 thumbnailer
.spectrogram(
127 spectrogram_tmp
.name
,
128 width
=mgg
.global_config
['media:medium']['max_width'],
129 fft_size
=audio_config
['spectrogram_fft_size'])
131 _log
.debug('Saving spectrogram...')
132 mgg
.public_store
.get_file(spectrogram_filepath
, 'wb').write(
133 spectrogram_tmp
.read())
135 entry
.media_files
['spectrogram'] = spectrogram_filepath
137 with
NamedTemporaryFile(dir=workbench
.dir, suffix
='.jpg') as thumb_tmp
:
138 thumbnailer
.thumbnail_spectrogram(
139 spectrogram_tmp
.name
,
141 (mgg
.global_config
['media:thumb']['max_width'],
142 mgg
.global_config
['media:thumb']['max_height']))
144 thumb_filepath
= create_pub_filepath(
146 '{original}-thumbnail.jpg'.format(
147 original
=os
.path
.splitext(
148 queued_filepath
[-1])[0]))
150 mgg
.public_store
.get_file(thumb_filepath
, 'wb').write(
153 entry
.media_files
['thumb'] = thumb_filepath
155 entry
.media_files
['thumb'] = ['fake', 'thumb', 'path.jpg']
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
= []
166 class CommonAudioProcessor(MediaProcessor
):
168 Provides a base for various audio processing steps
171 def common_setup(self
):
174 self
.audio_config
= mgg \
175 .global_config
['media_type:mediagoblin.media_types.audio']
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
)
182 self
.spectrogram_tmp
= os
.path
.join(self
.workbench
.dir,
183 self
.name_builder
.fill(
184 '{basename}-spectrogram.jpg'))
186 self
.transcoder
= AudioTranscoder()
187 self
.thumbnailer
= AudioThumbnailer()
189 def copy_original(self
):
190 if self
.audio_config
['keep_original']:
192 self
.entry
, self
.orig_filename
,
193 self
.name_builder
.fill('{basename}{ext}'))
195 def transcode(self
, quality
=None):
197 quality
= self
.audio_config
['quality']
199 progress_callback
= ProgressCallback(self
.entry
)
200 webm_audio_tmp
= os
.path
.join(self
.workbench
.dir,
201 self
.name_builder
.fill(
204 #webm_audio_filepath = create_pub_filepath(
206 # '{original}.webm'.format(
207 # original=os.path.splitext(
208 # self.orig_filename[-1])[0]))
210 self
.transcoder
.transcode(
214 progress_callback
=progress_callback
)
216 self
.transcoder
.discover(webm_audio_tmp
)
218 _log
.debug('Saving medium...')
219 store_public(self
.entry
, 'medium', webm_audio_tmp
,
220 self
.name_builder
.fill('{basename}.medium{ext}'))
222 def create_spectrogram(self
, quality
=None, max_width
=None, fft_size
=None):
224 quality
= self
.audio_config
['quality']
226 max_width
= mgg
.global_config
['media:medium']['max_width']
228 fft_size
= self
.audio_config
['spectrogram_fft_size']
230 #spectrogram_filepath = create_pub_filepath(
232 # '{original}-spectrogram.jpg'.format(
233 # original=os.path.splitext(
234 # self.orig_filename[-1])[0]))
236 wav_tmp
= os
.path
.join(self
.workbench
.dir, self
.name_builder
.fill(
239 _log
.info('Creating OGG source for spectrogram')
240 self
.transcoder
.transcode(
243 mux_string
='vorbisenc quality={0} ! oggmux'.format(quality
))
245 self
.thumbnailer
.spectrogram(
247 self
.spectrogram_tmp
,
251 _log
.debug('Saving spectrogram...')
252 store_public(self
.entry
, 'spectrogram', self
.spectrogram_tmp
,
253 self
.name_builder
.fill('{basename}.spectrogram.jpg'))
255 def generate_thumb(self
, size
=None):
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
)
261 thumb_tmp
= os
.path
.join(self
.workbench
.dir, self
.name_builder
.fill(
262 '{basename}-thumbnail.jpg'))
264 self
.thumbnailer
.thumbnail_spectrogram(
265 self
.spectrogram_tmp
,
269 #thumb_filepath = create_pub_filepath(
271 # '{original}-thumbnail.jpg'.format(
272 # original=os.path.splitext(
273 # self.orig_filename[-1])[0]))
275 store_public(self
.entry
, 'thumb', thumb_tmp
,
276 self
.name_builder
.fill('{basename}.thumbnail.jpg'))
279 class InitialProcessor(CommonAudioProcessor
):
281 Initial processing steps for new audio
284 description
= "Initial processing"
287 def media_is_eligible(cls
, entry
=None, state
=None):
289 Determine if this media type is eligible for processing
294 "unprocessed", "failed")
297 def generate_parser(cls
):
298 parser
= argparse
.ArgumentParser(
299 description
=cls
.description
,
304 help='vorbisenc quality')
309 help='spectrogram fft size')
313 metavar
=('max_width', 'max_height'),
319 help='The width of the spectogram')
322 '--create_spectrogram',
324 help='Create spectogram and thumbnail')
329 def args_to_request(cls
, args
):
330 return request_from_args(
331 args
, ['create_spectrogram', 'quality', 'fft_size',
332 'thumb_size', 'medium_width'])
334 def process(self
, quality
=None, fft_size
=None, thumb_size
=None,
335 create_spectrogram
=None, medium_width
=None):
338 if not create_spectrogram
:
339 create_spectrogram
= self
.audio_config
['create_spectrogram']
341 self
.transcode(quality
=quality
)
344 if create_spectrogram
:
345 self
.create_spectrogram(quality
=quality
, max_width
=medium_width
,
347 self
.generate_thumb(size
=thumb_size
)
348 self
.delete_queue_file()
351 class AudioProcessingManager(ProcessingManager
):
353 super(self
.__class
__, self
).__init
__()
354 self
.add_processor(InitialProcessor
)