"""Create streamable preview, thumbnails and mezzanines.""" from decimal import Decimal import json import math import uuid from video import config from video.connectors import mediaconvert as mediaconvert_connector from video.constants import assets from video.constants import job_io_fields from video.constants import mediaconvert from video.logic.activity_task_logger import activity_task_logger from video.models import ows_video from video.utils import maths def create_mediaconvert_job(inputs, mediaconvert_outputs, job_type): """Create mediaconvert job. Args: inputs (dict): Inputs. mediaconvert_outputs (list): MediaConvert outputs. Options: h264_preview thumbnails_full_size thumbnails_small h264_mezzanine prores_mezzanine job_type (str): Job type. Returns: dict: Outputs. """ input_video_s3_bucket = inputs[job_io_fields.INPUT_VIDEO_S3_BUCKET] input_video_s3_key = inputs[job_io_fields.INPUT_VIDEO_S3_KEY] video_output_outer_width_pixels = Decimal(str( inputs[job_io_fields.VIDEO_OUTPUT_OUTER_WIDTH_PIXELS])) video_output_outer_height_pixels = Decimal(str( inputs[job_io_fields.VIDEO_OUTPUT_OUTER_HEIGHT_PIXELS])) audio_volume_adjustment_db = inputs[ job_io_fields.AUDIO_VOLUME_ADJUSTMENT_DB] input_clip_start_timecode = inputs[job_io_fields.INPUT_CLIP_START_TIMECODE] input_clip_end_timecode = inputs[job_io_fields.INPUT_CLIP_END_TIMECODE] video_inner_top_left_corner_x_pixels = inputs[ job_io_fields.VIDEO_INNER_TOP_LEFT_CORNER_X_PIXELS] video_inner_top_left_corner_y_pixels = inputs[ job_io_fields.VIDEO_INNER_TOP_LEFT_CORNER_Y_PIXELS] video_inner_width_pixels = Decimal(str( inputs[job_io_fields.VIDEO_INNER_WIDTH_PIXELS])) video_inner_height_pixels = Decimal(str( inputs[job_io_fields.VIDEO_INNER_HEIGHT_PIXELS])) video_stream_duration_seconds = math.floor(inputs[ job_io_fields.VIDEO_STREAM_DURATION_SECONDS]) input_framerate = Decimal(str(inputs[ job_io_fields.VIDEO_STREAM_FRAME_RATE_FRAMES_PER_SECOND])) video_output_inner_width_pixels = Decimal(str( inputs[job_io_fields.VIDEO_OUTPUT_INNER_WIDTH_PIXELS])) video_output_inner_height_pixels = Decimal(str( inputs[job_io_fields.VIDEO_OUTPUT_INNER_HEIGHT_PIXELS])) framerate_control = 'INITIALIZE_FROM_SOURCE' framerate_conversion_algorithm = 'DUPLICATE_DROP' output_framerate = input_framerate framerate_numerator = 1 framerate_denominator = 1 if input_framerate in [30, 60]: framerate_control = 'SPECIFIED' output_framerate = 29.97 framerate_numerator = 30000 framerate_denominator = 1001 if input_framerate in [50]: framerate_control = 'SPECIFIED' output_framerate = 25 framerate_numerator = 25 framerate_denominator = 1 codec_settings = { 'ParControl': 'SPECIFIED', 'ParNumerator': 1, 'ParDenominator': 1, 'FramerateControl': framerate_control, 'FramerateConversionAlgorithm': framerate_conversion_algorithm, 'FramerateNumerator': framerate_numerator, 'FramerateDenominator': framerate_denominator, } workflow_job_id = inputs[job_io_fields.WORKFLOW_JOB_ID] output_file_name = inputs.get(job_io_fields.PRODUCT_ID) or uuid.uuid1() s3_url_blank_10_frame_input = ( assets.S3_URL_TEMPLATE .format( bucket=assets.VIDEO_S3_BUCKET, key=assets.BLANK_VIDEO_S3_KEY_TEMPLATE.format( num_frames=10, frame_rate=Decimal(str(output_framerate)).normalize(), w=mediaconvert.MAX_WIDTH_SUPPORTED_BY_MEDIACONVERT, h=mediaconvert.MAX_HEIGHT_SUPPORTED_BY_MEDIACONVERT, ) ) ) s3_url_raw_input = ( assets.S3_URL_TEMPLATE .format( bucket=input_video_s3_bucket, key=input_video_s3_key)) s3_key_prores_mezzanine_output = ( assets.MEZZANINE_S3_KEY_TEMPLATE.format( video_codec='prores', workflow_job_id=workflow_job_id, file_name=output_file_name)) s3_url_prores_mezzanine_output = ( assets.S3_URL_TEMPLATE .format( bucket=assets.VIDEO_S3_BUCKET, key=s3_key_prores_mezzanine_output)) s3_key_h264_mezzanine_output = ( assets.MEZZANINE_S3_KEY_TEMPLATE.format( video_codec='h.264', workflow_job_id=workflow_job_id, file_name=output_file_name)) s3_url_h264_mezzanine_output = ( assets.S3_URL_TEMPLATE .format( bucket=assets.VIDEO_S3_BUCKET, key=assets.MEZZANINE_S3_KEY_TEMPLATE.format( video_codec='h.264', workflow_job_id=workflow_job_id, file_name=output_file_name))) s3_url_h264_preview_output = ( assets.S3_URL_TEMPLATE .format( bucket=assets.VIDEO_S3_BUCKET, key=assets.STREAMABLE_PREVIEW_S3_KEY_TEMPLATE.format( workflow_job_id=workflow_job_id, file_name=output_file_name))) s3_url_original_size_thumbnail_output = ( assets.S3_URL_TEMPLATE .format( bucket=assets.VIDEO_S3_BUCKET, key=assets.THUMBNAILS_S3_KEY_TEMPLATE.format( size='original-size', workflow_job_id=workflow_job_id, file_name=output_file_name))) s3_key_small_thumbnail_output = assets.THUMBNAILS_S3_KEY_TEMPLATE.format( size='small', workflow_job_id=workflow_job_id, file_name=output_file_name) s3_url_small_thumbnail_output = ( assets.S3_URL_TEMPLATE .format( bucket=assets.VIDEO_S3_BUCKET, key=s3_key_small_thumbnail_output)) blank_10_frame_input = { 'FilterEnable': 'AUTO', 'PsiControl': 'USE_PSI', 'FilterStrength': 0, 'DeblockFilter': 'DISABLED', 'DenoiseFilter': 'DISABLED', 'TimecodeSource': 'ZEROBASED', 'VideoSelector': { 'ColorSpace': 'FOLLOW' }, 'AudioSelectors': { 'Audio Selector 1': { 'Offset': 0, 'DefaultSelection': 'DEFAULT', 'ProgramSelection': 1 } }, 'FileInput': s3_url_blank_10_frame_input } raw_input = { 'FilterEnable': 'AUTO', 'PsiControl': 'USE_PSI', 'FilterStrength': 0, 'DeblockFilter': 'DISABLED', 'DenoiseFilter': 'DISABLED', 'TimecodeSource': 'ZEROBASED', 'VideoSelector': { 'ColorSpace': 'FOLLOW' }, 'AudioSelectors': { 'Audio Selector 1': { 'Offset': 0, 'DefaultSelection': 'DEFAULT', 'ProgramSelection': 1 } }, 'FileInput': s3_url_raw_input, } input_clip = {} if input_clip_start_timecode: input_clip['StartTimecode'] = input_clip_start_timecode if input_clip_end_timecode: input_clip['EndTimecode'] = input_clip_end_timecode if input_clip: raw_input['InputClippings'] = [input_clip] output_outer_aspect_ratio = ( video_output_outer_width_pixels / video_output_outer_height_pixels) sizing_settings_for_mezzanines = { 'ScalingBehavior': 'DEFAULT', 'Width': int(video_output_outer_width_pixels), 'Height': int(video_output_outer_height_pixels), 'Crop': { 'X': int(video_inner_top_left_corner_x_pixels), 'Y': int(video_inner_top_left_corner_y_pixels), 'Width': int(video_inner_width_pixels), 'Height': int(video_inner_height_pixels) }, 'Position': { 'X': maths.find_multiple_of_n_nearest_x( 2, (video_output_outer_width_pixels - video_output_inner_width_pixels) / 2), 'Y': maths.find_multiple_of_n_nearest_x( 2, (video_output_outer_height_pixels - video_output_inner_height_pixels) / 2), 'Width': maths.find_multiple_of_n_nearest_x( 2, video_output_inner_width_pixels), 'Height': maths.find_multiple_of_n_nearest_x( 2, video_output_inner_height_pixels), } } streamable_preview_outer_height = 720 streamable_preview_outer_width = maths.find_multiple_of_n_nearest_x( 2, streamable_preview_outer_height * output_outer_aspect_ratio) height_scaling_factor_for_streamable_preview = ( streamable_preview_outer_height / video_output_outer_height_pixels) scaled_inner_width_for_streamable_preview = ( video_output_inner_width_pixels * height_scaling_factor_for_streamable_preview) scaled_inner_height_for_streamable_preview = ( video_output_inner_height_pixels * height_scaling_factor_for_streamable_preview) sizing_settings_for_streamable_preview = { 'ScalingBehavior': 'DEFAULT', 'Width': streamable_preview_outer_width, 'Height': streamable_preview_outer_height, 'Crop': { 'X': int(video_inner_top_left_corner_x_pixels), 'Y': int(video_inner_top_left_corner_y_pixels), 'Width': int(video_inner_width_pixels), 'Height': int(video_inner_height_pixels) }, 'Position': { 'X': maths.find_multiple_of_n_nearest_x( 2, (streamable_preview_outer_width - scaled_inner_width_for_streamable_preview) / 2), 'Y': maths.find_multiple_of_n_nearest_x( 2, (streamable_preview_outer_height - scaled_inner_height_for_streamable_preview) / 2), 'Width': maths.find_multiple_of_n_nearest_x( 2, scaled_inner_width_for_streamable_preview), 'Height': maths.find_multiple_of_n_nearest_x( 2, scaled_inner_height_for_streamable_preview), } } if video_output_outer_height_pixels <= 720: sizing_settings_for_streamable_preview = sizing_settings_for_mezzanines small_thumbnails_outer_height = 288 small_thumbnails_outer_width = maths.find_multiple_of_n_nearest_x( 2, small_thumbnails_outer_height * output_outer_aspect_ratio) height_scaling_factor_for_small_thumbnails = ( small_thumbnails_outer_height / video_output_outer_height_pixels) scaled_inner_width_for_small_thumbnails = ( video_output_inner_width_pixels * height_scaling_factor_for_small_thumbnails) scaled_inner_height_for_small_thumbnails = ( video_output_inner_height_pixels * height_scaling_factor_for_small_thumbnails) sizing_settings_for_small_thumbnails = { 'ScalingBehavior': 'DEFAULT', 'Width': small_thumbnails_outer_width, 'Height': small_thumbnails_outer_height, 'Crop': { 'X': int(video_inner_top_left_corner_x_pixels), 'Y': int(video_inner_top_left_corner_y_pixels), 'Width': int(video_inner_width_pixels), 'Height': int(video_inner_height_pixels) }, 'Position': { 'X': maths.find_multiple_of_n_nearest_x( 2, (small_thumbnails_outer_width - scaled_inner_width_for_small_thumbnails) / 2), 'Y': maths.find_multiple_of_n_nearest_x( 2, (small_thumbnails_outer_height - scaled_inner_height_for_small_thumbnails) / 2), 'Width': maths.find_multiple_of_n_nearest_x( 2, scaled_inner_width_for_small_thumbnails), 'Height': maths.find_multiple_of_n_nearest_x( 2, scaled_inner_height_for_small_thumbnails), } } prores_mezzanine_output = { # noqa 'Name': 'ProRes Mezzanine', 'OutputGroupSettings': { 'Type': 'FILE_GROUP_SETTINGS', 'FileGroupSettings': { 'Destination': s3_url_prores_mezzanine_output } }, 'Outputs': [ { 'VideoDescription': { 'TimecodeInsertion': 'DISABLED', 'AntiAlias': 'ENABLED', 'Sharpness': 50, 'VideoPreprocessors': { 'ColorCorrector': { 'ColorSpaceConversion': 'FORCE_709' } }, 'CodecSettings': { 'Codec': 'PRORES', 'ProresSettings': { 'InterlaceMode': 'PROGRESSIVE', 'CodecProfile': 'APPLE_PRORES_422_HQ', 'SlowPal': 'DISABLED', 'Telecine': 'NONE', **codec_settings, } }, 'AfdSignaling': 'NONE', 'DropFrameTimecode': 'ENABLED', 'RespondToAfd': 'NONE', 'ColorMetadata': 'INSERT', **sizing_settings_for_mezzanines, }, 'AudioDescriptions': [ { 'AudioTypeControl': 'FOLLOW_INPUT', 'CodecSettings': { 'Codec': 'WAV', 'WavSettings': { 'BitDepth': 24, 'Channels': 2, 'SampleRate': 48000, 'Format': 'RIFF', } }, 'LanguageCodeControl': 'FOLLOW_INPUT', 'AudioSourceName': 'Audio Selector 1', 'RemixSettings': { 'ChannelMapping': { 'OutputChannels': [ { 'InputChannelsFineTune': [ audio_volume_adjustment_db, -60 ] }, { 'InputChannelsFineTune': [ -60, audio_volume_adjustment_db ] } ] }, 'ChannelsIn': 2, 'ChannelsOut': 2 } } ], 'ContainerSettings': { 'Container': 'MOV', 'MovSettings': { 'ClapAtom': 'EXCLUDE', 'CslgAtom': 'INCLUDE', 'PaddingControl': 'NONE', 'Reference': 'SELF_CONTAINED', 'Mpeg2FourCCControl': 'MPEG' } }, 'Extension': 'mov' } ] } h264_mezzanine_output = { # noqa 'Name': 'H.264 Mezzanine', 'OutputGroupSettings': { 'Type': 'FILE_GROUP_SETTINGS', 'FileGroupSettings': { 'Destination': s3_url_h264_mezzanine_output } }, 'Outputs': [ { 'VideoDescription': { 'TimecodeInsertion': 'DISABLED', 'AntiAlias': 'ENABLED', 'Sharpness': 50, 'VideoPreprocessors': { 'ColorCorrector': { 'ColorSpaceConversion': 'FORCE_709' } }, 'CodecSettings': { 'Codec': 'H_264', 'H264Settings': { 'InterlaceMode': 'PROGRESSIVE', 'NumberReferenceFrames': 3, 'Syntax': 'DEFAULT', 'Softness': 0, 'GopClosedCadence': 1, 'GopSize': 90, 'Slices': 1, 'GopBReference': 'DISABLED', 'MaxBitrate': 56000000, 'SlowPal': 'DISABLED', 'SpatialAdaptiveQuantization': 'ENABLED', 'TemporalAdaptiveQuantization': 'ENABLED', 'FlickerAdaptiveQuantization': 'ENABLED', 'EntropyEncoding': 'CABAC', 'RateControlMode': 'QVBR', 'QvbrSettings': { 'QvbrQualityLevel': 10, 'MaxAverageBitrate': 44000000 }, 'CodecProfile': 'HIGH', 'Telecine': 'NONE', 'MinIInterval': 0, 'AdaptiveQuantization': 'HIGH', 'CodecLevel': 'AUTO', 'FieldEncoding': 'PAFF', 'SceneChangeDetect': 'ENABLED', 'QualityTuningLevel': 'MULTI_PASS_HQ', 'UnregisteredSeiTimecode': 'DISABLED', 'GopSizeUnits': 'FRAMES', 'NumberBFramesBetweenReferenceFrames': 2, 'RepeatPps': 'DISABLED', 'DynamicSubGop': 'STATIC', **codec_settings, } }, 'AfdSignaling': 'NONE', 'DropFrameTimecode': 'ENABLED', 'RespondToAfd': 'NONE', 'ColorMetadata': 'INSERT', **sizing_settings_for_mezzanines, }, 'AudioDescriptions': [ { 'AudioTypeControl': 'FOLLOW_INPUT', 'CodecSettings': { 'Codec': 'AAC', 'AacSettings': { 'AudioDescriptionBroadcasterMix': 'NORMAL', 'RateControlMode': 'VBR', 'CodecProfile': 'LC', 'CodingMode': 'CODING_MODE_2_0', 'RawFormat': 'NONE', 'SampleRate': 48000, 'Specification': 'MPEG4', 'VbrQuality': 'HIGH', } }, 'LanguageCodeControl': 'FOLLOW_INPUT', 'AudioSourceName': 'Audio Selector 1', 'RemixSettings': { 'ChannelMapping': { 'OutputChannels': [ { 'InputChannelsFineTune': [ audio_volume_adjustment_db, -60 ] }, { 'InputChannelsFineTune': [ -60, audio_volume_adjustment_db ] } ] }, 'ChannelsIn': 2, 'ChannelsOut': 2 } } ], 'ContainerSettings': { 'Container': 'MP4', 'Mp4Settings': { 'CslgAtom': 'INCLUDE', 'FreeSpaceBox': 'EXCLUDE', 'MoovPlacement': 'PROGRESSIVE_DOWNLOAD' } }, 'Extension': 'mp4' } ] } h264_preview_output = { 'Name': 'H.264 Preview', 'OutputGroupSettings': { 'Type': 'FILE_GROUP_SETTINGS', 'FileGroupSettings': { 'Destination': s3_url_h264_preview_output } }, 'Outputs': [ { 'ContainerSettings': { 'Container': 'MP4', 'Mp4Settings': { 'CslgAtom': 'INCLUDE', 'FreeSpaceBox': 'EXCLUDE', 'MoovPlacement': 'PROGRESSIVE_DOWNLOAD' } }, 'Extension': 'mp4', 'VideoDescription': { 'TimecodeInsertion': 'DISABLED', 'AntiAlias': 'ENABLED', 'Sharpness': 50, 'VideoPreprocessors': { 'ColorCorrector': { 'ColorSpaceConversion': 'FORCE_709' } }, 'CodecSettings': { 'Codec': 'H_264', 'H264Settings': { 'InterlaceMode': 'PROGRESSIVE', 'NumberReferenceFrames': 3, 'Syntax': 'DEFAULT', 'Softness': 0, 'GopClosedCadence': 1, 'GopSize': 60, 'Slices': 1, 'GopBReference': 'DISABLED', 'MaxBitrate': 15000000, 'SlowPal': 'DISABLED', 'SpatialAdaptiveQuantization': 'ENABLED', 'TemporalAdaptiveQuantization': 'ENABLED', 'FlickerAdaptiveQuantization': 'ENABLED', 'EntropyEncoding': 'CABAC', 'RateControlMode': 'QVBR', 'QvbrSettings': { 'QvbrQualityLevel': 8 }, 'CodecProfile': 'HIGH', 'Telecine': 'NONE', 'MinIInterval': 0, 'AdaptiveQuantization': 'HIGH', 'CodecLevel': 'AUTO', 'FieldEncoding': 'PAFF', 'SceneChangeDetect': 'ENABLED', 'QualityTuningLevel': 'MULTI_PASS_HQ', 'UnregisteredSeiTimecode': 'DISABLED', 'GopSizeUnits': 'FRAMES', 'NumberBFramesBetweenReferenceFrames': 2, 'RepeatPps': 'DISABLED', 'DynamicSubGop': 'STATIC', **codec_settings, } }, 'AfdSignaling': 'NONE', 'DropFrameTimecode': 'ENABLED', 'RespondToAfd': 'NONE', 'ColorMetadata': 'INSERT', **sizing_settings_for_streamable_preview, }, 'AudioDescriptions': [ { 'AudioTypeControl': 'FOLLOW_INPUT', 'AudioSourceName': 'Audio Selector 1', 'CodecSettings': { 'Codec': 'AAC', 'AacSettings': { 'AudioDescriptionBroadcasterMix': 'NORMAL', 'VbrQuality': 'HIGH', 'RateControlMode': 'VBR', 'CodecProfile': 'LC', 'CodingMode': 'CODING_MODE_2_0', 'RawFormat': 'NONE', 'SampleRate': 48000, 'Specification': 'MPEG4', } }, 'RemixSettings': { 'ChannelMapping': { 'OutputChannels': [ { 'InputChannelsFineTune': [ audio_volume_adjustment_db, -60 ] }, { 'InputChannelsFineTune': [ -60, audio_volume_adjustment_db ] } ] }, 'ChannelsIn': 2, 'ChannelsOut': 2 }, 'LanguageCodeControl': 'FOLLOW_INPUT' } ] } ] } thumbnails_full_size_output = { 'Name': 'Full Size Thumbnails', 'OutputGroupSettings': { 'Type': 'FILE_GROUP_SETTINGS', 'FileGroupSettings': { 'Destination': s3_url_original_size_thumbnail_output } }, 'Outputs': [ { 'VideoDescription': { 'TimecodeInsertion': 'DISABLED', 'AntiAlias': 'ENABLED', 'Sharpness': 50, 'VideoPreprocessors': { 'ColorCorrector': { 'ColorSpaceConversion': 'FORCE_709' } }, 'CodecSettings': { 'Codec': 'FRAME_CAPTURE', 'FrameCaptureSettings': { 'FramerateNumerator': 1, 'FramerateDenominator': 1, 'MaxCaptures': math.ceil(video_stream_duration_seconds), 'Quality': 100 } }, 'AfdSignaling': 'NONE', 'DropFrameTimecode': 'ENABLED', 'RespondToAfd': 'NONE', 'ColorMetadata': 'INSERT', **sizing_settings_for_mezzanines, }, 'ContainerSettings': { 'Container': 'RAW' }, 'Extension': 'jpg' } ] } thumbnails_small_output = { 'Name': 'Small Thumbnails', 'OutputGroupSettings': { 'Type': 'FILE_GROUP_SETTINGS', 'FileGroupSettings': { 'Destination': s3_url_small_thumbnail_output } }, 'Outputs': [ { 'VideoDescription': { 'TimecodeInsertion': 'DISABLED', 'AntiAlias': 'ENABLED', 'Sharpness': 50, 'VideoPreprocessors': { 'ColorCorrector': { 'ColorSpaceConversion': 'FORCE_709' } }, 'CodecSettings': { 'Codec': 'FRAME_CAPTURE', 'FrameCaptureSettings': { 'FramerateNumerator': 1, 'FramerateDenominator': 1, 'MaxCaptures': math.ceil(video_stream_duration_seconds), 'Quality': 100 } }, 'AfdSignaling': 'NONE', 'DropFrameTimecode': 'ENABLED', 'RespondToAfd': 'NONE', 'ColorMetadata': 'INSERT', **sizing_settings_for_small_thumbnails, }, 'ContainerSettings': { 'Container': 'RAW' }, 'Extension': 'jpg' } ] } output_groups = { 'h264_preview': h264_preview_output, 'thumbnails_full_size': thumbnails_full_size_output, 'thumbnails_small': thumbnails_small_output, 'h264_mezzanine': h264_mezzanine_output, 'prores_mezzanine': prores_mezzanine_output, } create_job_response = ( mediaconvert_connector .get_mediaconvert_client() .create_job( Queue=config.MEDIACONVERT_QUEUE, UserMetadata={'applesauce': 'bananas'}, Role=config.MEDIACONVERT_ROLE, Settings={ 'AdAvailOffset': 0, 'Inputs': [ blank_10_frame_input, raw_input, blank_10_frame_input, ], 'OutputGroups': [ output_groups[name] for name in mediaconvert_outputs], 'TimecodeConfig': { 'Source': 'ZEROBASED' }, })) outputs = { getattr( job_io_fields, '{}_MEDIACONVERT_JOB_ID'.format(job_type.upper())): create_job_response['Job']['Id'], } if 'prores_mezzanine' in mediaconvert_outputs: outputs[job_io_fields.PRORES_MEZZANINE_OUTPUT_S3_KEY] = ( '{}.{}'.format(s3_key_prores_mezzanine_output, 'mov')) if 'h264_mezzanine' in mediaconvert_outputs: outputs[job_io_fields.H264_MEZZANINE_OUTPUT_S3_KEY] = ( '{}.{}'.format(s3_key_h264_mezzanine_output, 'mp4')) if 'thumbnails_small' in mediaconvert_outputs: outputs[job_io_fields.SMALL_THUMBNAILS_OUTPUT_S3_KEY] = ( s3_key_small_thumbnail_output) return outputs def is_mediaconvert_job_done(inputs, job_type): """Check if MediaConvert job is done. Args: inputs (dict): Inputs. job_type (str): Job type. Returns: dict: Outputs. """ job_id = inputs[ getattr(job_io_fields, '{}_JOB_ID'.format(job_type.upper()))] mediaconvert_job_id = inputs[ getattr( job_io_fields, '{}_MEDIACONVERT_JOB_ID'.format(job_type.upper()))] mediaconvert_job = ( mediaconvert_connector .get_mediaconvert_client() .get_job(Id=mediaconvert_job_id)) mediaconvert_job_status = mediaconvert_job['Job']['Status'] if mediaconvert_job_status == 'ERROR': return { job_io_fields.ERROR_UNKNOWN: json.dumps({ 'error_code': mediaconvert_job['Job']['ErrorCode'], 'error_message': mediaconvert_job['Job']['ErrorMessage'], 'id': mediaconvert_job['Job']['Id'], 'created_at': str(mediaconvert_job['Job']['CreatedAt']), }), } if mediaconvert_job_status == 'COMPLETE': ows_video.change_job_status(job_id, mediaconvert_job_status) return { getattr(job_io_fields, 'IS_{}_DONE'.format(job_type.upper())): mediaconvert_job_status == 'COMPLETE', } @activity_task_logger([ job_io_fields.INPUT_VIDEO_S3_BUCKET, job_io_fields.INPUT_VIDEO_S3_KEY, job_io_fields.VIDEO_OUTPUT_OUTER_WIDTH_PIXELS, job_io_fields.VIDEO_OUTPUT_OUTER_HEIGHT_PIXELS, job_io_fields.AUDIO_VOLUME_ADJUSTMENT_DB, job_io_fields.INPUT_CLIP_START_TIMECODE, job_io_fields.INPUT_CLIP_END_TIMECODE, job_io_fields.VIDEO_INNER_TOP_LEFT_CORNER_X_PIXELS, job_io_fields.VIDEO_INNER_TOP_LEFT_CORNER_Y_PIXELS, job_io_fields.VIDEO_INNER_WIDTH_PIXELS, job_io_fields.VIDEO_INNER_HEIGHT_PIXELS, job_io_fields.VIDEO_OUTPUT_INNER_WIDTH_PIXELS, job_io_fields.VIDEO_OUTPUT_INNER_HEIGHT_PIXELS, job_io_fields.VIDEO_STREAM_DURATION_SECONDS, job_io_fields.VIDEO_STREAM_FRAME_RATE_FRAMES_PER_SECOND, job_io_fields.WORKFLOW_JOB_ID, job_io_fields.PRODUCT_ID, ], is_last_handler=False) def create_streamable_preview_and_thumbnails(inputs): """Invoke MediaConvert to create preview and thumbnails. Args: inputs (dict): Inputs. Returns: dict: Outputs. """ return create_mediaconvert_job( inputs, [ 'h264_preview', 'thumbnails_full_size', 'thumbnails_small', ], create_streamable_preview_and_thumbnails.__name__, ) @activity_task_logger( [ job_io_fields.CREATE_STREAMABLE_PREVIEW_AND_THUMBNAILS_JOB_ID, job_io_fields.CREATE_STREAMABLE_PREVIEW_AND_THUMBNAILS_MEDIACONVERT_JOB_ID, # noqa ], is_first_handler=False, is_last_handler=False, job_type=create_streamable_preview_and_thumbnails.__name__) def is_create_streamable_preview_and_thumbnails_done(inputs): """Check if MediaConvert create preview and thumbnails done. Args: inputs (dict): Inputs. Returns: dict: Outputs. """ return is_mediaconvert_job_done( inputs, create_streamable_preview_and_thumbnails.__name__) @activity_task_logger([ job_io_fields.INPUT_VIDEO_S3_BUCKET, job_io_fields.INPUT_VIDEO_S3_KEY, job_io_fields.VIDEO_OUTPUT_OUTER_WIDTH_PIXELS, job_io_fields.VIDEO_OUTPUT_OUTER_HEIGHT_PIXELS, job_io_fields.AUDIO_VOLUME_ADJUSTMENT_DB, job_io_fields.INPUT_CLIP_START_TIMECODE, job_io_fields.INPUT_CLIP_END_TIMECODE, job_io_fields.VIDEO_INNER_TOP_LEFT_CORNER_X_PIXELS, job_io_fields.VIDEO_INNER_TOP_LEFT_CORNER_Y_PIXELS, job_io_fields.VIDEO_INNER_WIDTH_PIXELS, job_io_fields.VIDEO_INNER_HEIGHT_PIXELS, job_io_fields.VIDEO_OUTPUT_INNER_WIDTH_PIXELS, job_io_fields.VIDEO_OUTPUT_INNER_HEIGHT_PIXELS, job_io_fields.VIDEO_STREAM_DURATION_SECONDS, job_io_fields.VIDEO_STREAM_FRAME_RATE_FRAMES_PER_SECOND, job_io_fields.WORKFLOW_JOB_ID, job_io_fields.PRODUCT_ID, ], is_first_handler=True, is_last_handler=False) def create_mezzanines(inputs): """Invoke MediaConvert to create mezzanines. Args: inputs (dict): Inputs. Returns: dict: Outputs. """ return create_mediaconvert_job( inputs, [ 'h264_mezzanine', 'prores_mezzanine', ], create_mezzanines.__name__, ) @activity_task_logger([ job_io_fields.WORKFLOW_INGEST_JOB_ID ]) def get_create_mezzanines_inputs(inputs): """Get create_mezzanines inputs. Args: inputs (dict): Inputs. Returns: dict: Outputs. """ workflow_jobs = ows_video.get_jobs( {'job_parent_id': inputs[job_io_fields.WORKFLOW_INGEST_JOB_ID]}) for workflow_job in workflow_jobs['items']: if workflow_job['type'] == ( create_streamable_preview_and_thumbnails.__name__): inputs = workflow_job['inputs'] inputs.pop(job_io_fields.WORKFLOW_JOB_ID, None) # todo: remove the following mappings added to accommodate # outputs from ingestion jobs run before the introduction of # the separate inner and outer dimension fields. # VIDEO_OUTPUT_INNER_WIDTH_PIXELS was already used so no mapping needed inputs[job_io_fields.VIDEO_OUTPUT_OUTER_WIDTH_PIXELS] = ( inputs.get( job_io_fields.VIDEO_OUTPUT_OUTER_WIDTH_PIXELS, inputs.get('video_output_width_pixels'), ) ) inputs.pop('video_output_width_pixels', None) inputs[job_io_fields.VIDEO_OUTPUT_INNER_HEIGHT_PIXELS] = ( inputs.get( job_io_fields.VIDEO_OUTPUT_INNER_HEIGHT_PIXELS, inputs.get('video_output_height_pixels'), ) ) inputs[job_io_fields.VIDEO_OUTPUT_OUTER_HEIGHT_PIXELS] = ( inputs.get( job_io_fields.VIDEO_OUTPUT_OUTER_HEIGHT_PIXELS, inputs.get('video_output_height_pixels'), ) ) inputs.pop('video_output_height_pixels', None) return inputs @activity_task_logger( [ job_io_fields.CREATE_MEZZANINES_JOB_ID, job_io_fields.CREATE_MEZZANINES_MEDIACONVERT_JOB_ID, ], is_first_handler=False, is_last_handler=False, job_type=create_mezzanines.__name__) def is_create_mezzanines_done(inputs): """Check if MediaConvert create mezzanines is done. Args: inputs (dict): Inputs. Returns: dict: Outputs. """ return is_mediaconvert_job_done(inputs, create_mezzanines.__name__)