diff options
author | Rodney Ewing <ewing.rj@gmail.com> | 2013-08-13 13:19:52 -0700 |
---|---|---|
committer | Rodney Ewing <ewing.rj@gmail.com> | 2013-08-16 15:30:19 -0700 |
commit | 347ef583829f0943abf18a8b42d953b185ae2a46 (patch) | |
tree | b035b63e1ae3bd57ddd6eff65fed045775f689d7 | |
parent | 0c509b1b7e6f7c8ba52aec4860dc0cae1dc0de80 (diff) | |
download | mediagoblin-347ef583829f0943abf18a8b42d953b185ae2a46.tar.lz mediagoblin-347ef583829f0943abf18a8b42d953b185ae2a46.tar.xz mediagoblin-347ef583829f0943abf18a8b42d953b185ae2a46.zip |
Added Initial processor for video
-rw-r--r-- | mediagoblin/media_types/audio/processing.py | 4 | ||||
-rw-r--r-- | mediagoblin/media_types/video/processing.py | 280 |
2 files changed, 180 insertions, 104 deletions
diff --git a/mediagoblin/media_types/audio/processing.py b/mediagoblin/media_types/audio/processing.py index 7d8fd2d2..f7c0a234 100644 --- a/mediagoblin/media_types/audio/processing.py +++ b/mediagoblin/media_types/audio/processing.py @@ -113,8 +113,8 @@ class CommonAudioProcessor(MediaProcessor): self.audio_config['quality'])) spectrogram_tmp = os.path.join(self.workbench.dir, - self.name_builder.fill( - '{basename}-spectrogram.jpg')) + self.name_builder.fill( + '{basename}-spectrogram.jpg')) self.thumbnailer.spectrogram( wav_tmp, diff --git a/mediagoblin/media_types/video/processing.py b/mediagoblin/media_types/video/processing.py index 857c1647..b9725401 100644 --- a/mediagoblin/media_types/video/processing.py +++ b/mediagoblin/media_types/video/processing.py @@ -19,8 +19,12 @@ import logging import datetime from mediagoblin import mg_globals as mgg -from mediagoblin.processing import \ - create_pub_filepath, FilenameBuilder, BaseProcessingFail, ProgressCallback +from mediagoblin.processing import ( + FilenameBuilder, BaseProcessingFail, + ProgressCallback, MediaProcessor, + ProcessingManager, request_from_args, + get_orig_filename, store_public, + copy_original) from mediagoblin.tools.translate import lazy_pass_to_ugettext as _ from . import transcoders @@ -57,106 +61,6 @@ def sniff_handler(media_file, **kw): return None -def process_video(proc_state): - """ - Process a video entry, transcode the queued media files (originals) and - create a thumbnail for the entry. - - A Workbench() represents a local tempory dir. It is automatically - cleaned up when this function exits. - """ - entry = proc_state.entry - workbench = proc_state.workbench - video_config = mgg.global_config['media_type:mediagoblin.media_types.video'] - - queued_filepath = entry.queued_media_file - queued_filename = proc_state.get_queued_filename() - name_builder = FilenameBuilder(queued_filename) - - medium_basename = name_builder.fill('{basename}-640p.webm') - medium_filepath = create_pub_filepath(entry, medium_basename) - - thumbnail_basename = name_builder.fill('{basename}.thumbnail.jpg') - thumbnail_filepath = create_pub_filepath(entry, thumbnail_basename) - - # Create a temporary file for the video destination (cleaned up with workbench) - tmp_dst = os.path.join(workbench.dir, medium_basename) - # Transcode queued file to a VP8/vorbis file that fits in a 640x640 square - progress_callback = ProgressCallback(entry) - - dimensions = ( - mgg.global_config['media:medium']['max_width'], - mgg.global_config['media:medium']['max_height']) - - # Extract metadata and keep a record of it - metadata = transcoders.VideoTranscoder().discover(queued_filename) - store_metadata(entry, metadata) - - # Figure out whether or not we need to transcode this video or - # if we can skip it - if skip_transcode(metadata): - _log.debug('Skipping transcoding') - - dst_dimensions = metadata['videowidth'], metadata['videoheight'] - - # Push original file to public storage - _log.debug('Saving original...') - proc_state.copy_original(queued_filepath[-1]) - - did_transcode = False - else: - transcoder = transcoders.VideoTranscoder() - - transcoder.transcode(queued_filename, tmp_dst, - vp8_quality=video_config['vp8_quality'], - vp8_threads=video_config['vp8_threads'], - vorbis_quality=video_config['vorbis_quality'], - progress_callback=progress_callback, - dimensions=dimensions) - - dst_dimensions = transcoder.dst_data.videowidth,\ - transcoder.dst_data.videoheight - - # Push transcoded video to public storage - _log.debug('Saving medium...') - mgg.public_store.copy_local_to_storage(tmp_dst, medium_filepath) - _log.debug('Saved medium') - - entry.media_files['webm_640'] = medium_filepath - - did_transcode = True - - # Save the width and height of the transcoded video - entry.media_data_init( - width=dst_dimensions[0], - height=dst_dimensions[1]) - - # Temporary file for the video thumbnail (cleaned up with workbench) - tmp_thumb = os.path.join(workbench.dir, thumbnail_basename) - - # Create a thumbnail.jpg that fits in a 180x180 square - transcoders.VideoThumbnailerMarkII( - queued_filename, - tmp_thumb, - 180) - - # Push the thumbnail to public storage - _log.debug('Saving thumbnail...') - mgg.public_store.copy_local_to_storage(tmp_thumb, thumbnail_filepath) - entry.media_files['thumb'] = thumbnail_filepath - - # save the original... but only if we did a transcoding - # (if we skipped transcoding and just kept the original anyway as the main - # media, then why would we save the original twice?) - if video_config['keep_original'] and did_transcode: - # Push original file to public storage - _log.debug('Saving original...') - proc_state.copy_original(queued_filepath[-1]) - - # Remove queued media file from storage and database - proc_state.delete_queue_file() - - def store_metadata(media_entry, metadata): """ Store metadata from this video for this media entry. @@ -211,3 +115,175 @@ def store_metadata(media_entry, metadata): if len(stored_metadata): media_entry.media_data_init( orig_metadata=stored_metadata) + + +class CommonVideoProcessor(MediaProcessor): + """ + Provides a base for various video processing steps + """ + + def common_setup(self): + self.video_config = mgg \ + .global_config['media_type:mediagoblin.media_types.audio'] + + # Pull down and set up the original file + self.orig_filename = get_orig_filename( + self.entry, self.workbench) + self.name_builder = FilenameBuilder(self.orig_filename) + + self.transcoder = transcoders.VideoTranscoder() + self.did_transcode = False + + def copy_original(self): + # If we didn't transcode, then we need to keep the original + if not self.did_transcode or \ + (self.video_config['keep_original'] and self.did_transcode): + copy_original( + self.entry, self.orig_filename, + self.name_builder.fill('{basename}{ext}')) + + def transcode(self, medium_size=None, vp8_quality=None, vp8_threads=None, + vorbis_quality=None): + progress_callback = ProgressCallback(entry) + tmp_dst = os.path.join(self.workbench.dir, + self.name_builder.fill('{basename}-640p.webm')) + + if not medium_size: + medium_size = ( + mgg.global_config['media:medium']['max_width'], + mgg.global_config['media:medium']['max_height']) + if not vp8_quality: + vp8_quality = self.video_config['vp8_quality'] + if not vp8_threads: + vp8_threads = self.video_config['vp8_threads'] + if not vorbis_quality: + vorbis_quality = self.video_config['vorbis_quality'] + + # Extract metadata and keep a record of it + metadata = self.transcoder.discover(self.orig_filename) + store_metadata(self.entry, metadata) + + # Figure out whether or not we need to transcode this video or + # if we can skip it + if skip_transcode(metadata): + _log.debug('Skipping transcoding') + + dst_dimensions = metadata['videowidth'], metadata['videoheight'] + + else: + self.transcoder.transcode(self.orig_filename, tmp_dst, + vp8_quality=vp8_quality, + vp8_threads=vp8_threads, + vorbis_quality=vorbis_quality, + progress_callback=progress_callback, + dimensions=medium_size) + + dst_dimensions = self.transcoder.dst_data.videowidth,\ + self.transcoder.dst_data.videoheight + + # Push transcoded video to public storage + _log.debug('Saving medium...') + store_public(self.entry, 'webm_640', tmp_dst, + self.name_builder.fill('{basename}-640p.webm')) + _log.debug('Saved medium') + + self.did_transcode = True + + # Save the width and height of the transcoded video + self.entry.media_data_init( + width=dst_dimensions[0], + height=dst_dimensions[1]) + + def generate_thumb(self, thumb_size=None): + # Temporary file for the video thumbnail (cleaned up with workbench) + tmp_thumb = os.path.join(self.workbench.dir, + self.name_builder.fill( + '{basename}.thumbnail.jpg')) + + if not thumb_size: + thumb_size = (mgg.global_config['media:thumb']['max_width'], + mgg.global_config['media:thumb']['max_height']) + + transcoders.VideoThumbnailerMarkII( + self.orig_filename, + tmp_thumb, + thumb_size[0], + thumb_size[1]) + + # Push the thumbnail to public storage + _log.debug('Saving thumbnail...') + store_public(self.entry, 'thumb', tmp_thumb, + self.name_builder.fill('{basename}.thumbnail.jpg')) + + +class InitialProcessor(CommonVideoProcessor): + """ + Initial processing steps for new video + """ + name = "initial" + description = "Initial processing" + + @classmethod + def media_is_eligible(cls, entry=None, state=None): + if not state: + state = entry.state + return state in ( + "unprocessed", "failed") + + @classmethod + def generate_parser(cls): + parser = argparse.ArgumentParser( + description=cls.description, + prog=cls.name) + + parser.add_argument( + '--medium_size', + nargs=2, + metavar=('max_width', 'max_height'), + type=int) + + parser.add_argument( + '--vp8_quality', + type=int, + help='Range 0..10') + + parser.add_argument( + '--vp8_threads', + type=int, + help='0 means number_of_CPUs - 1') + + parser.add_argument( + '--vorbis_quality', + type=float, + help='Range -0.1..1') + + parser.add_argument( + '--thumb_size', + nargs=2, + metavar=('max_width', 'max_height'), + type=int) + + return parser + + @classmethod + def args_to_request(cls, args): + return request_from_args( + args, ['medium_size', 'vp8_quality', 'vp8_threads', + 'vorbis_quality', 'thumb_size']) + + def process(self, medium_size=None, vp8_threads=None, vp8_quality=None, + vorbis_quality=None, thumb_size=None): + self.common_setup() + + self.transcode(medium_size=medium_size, vp8_quality=vp8_quality, + vp8_threads=vp8_threads, vorbis_quality=vorbis_quality) + + self.copy_original() + self.generate_thumb(thumb_size=thumb_size) + self.delete_queue_file() + + +class VideoProcessingManager(ProcessingManager): + def __init__(self): + super(self.__class__, self).__init__() + self.add_processor(InitialProcessor) |