"""Interface for the ows-video microservice.""" from decimal import Decimal from http.client import OK import json import math import backoff from owsrequest import request from video import config from video.constants import job_io_fields from video.constants import services from video.utils import exception as exception_utils @backoff.on_exception( backoff.expo, exception_utils.RetryableError, max_time=config.HTTP_REQUEST_RETRY_PERIOD_SECONDS) def get_job(job_id): """Get a single job.""" request_params = { 'application': config.SERVICE_NAME, 'environment': config.OWS_ENVIRONMENT, 'method': 'GET', 'service_name': services.OWS_VIDEO, 'path': '/job/{}'.format(job_id), } result = request.process(**request_params) exception_utils.raise_exception_if_request_failed( request_params=request_params, response_status=result.status_code, response_text=result.text, ) return result.json() @backoff.on_exception( backoff.expo, exception_utils.RetryableError, max_time=config.HTTP_REQUEST_RETRY_PERIOD_SECONDS) def get_jobs(query): """Get multiple jobs.""" request_params = { 'application': config.SERVICE_NAME, 'environment': config.OWS_ENVIRONMENT, 'method': 'GET', 'service_name': services.OWS_VIDEO, 'path': '/jobs', 'params': query } result = request.process(**request_params) exception_utils.raise_exception_if_request_failed( request_params=request_params, response_status=result.status_code, response_text=result.text, ) return result.json() def change_job_status(job_id, status): """Change job status.""" set_job_fields({ 'id': job_id, 'status': status, }) def add_job_inputs(job_id, inputs): """Add job inputs.""" set_job_fields({ 'id': job_id, 'inputs': inputs, }) def add_job_outputs(job_id, outputs): """Add job outputs.""" set_job_fields({ 'id': job_id, 'outputs': outputs, }) @backoff.on_exception( backoff.expo, exception_utils.RetryableError, max_time=config.HTTP_REQUEST_RETRY_PERIOD_SECONDS) def set_job_fields(job_fields): """Set job fields.""" request_params = { 'application': config.SERVICE_NAME, 'environment': config.OWS_ENVIRONMENT, 'method': 'POST', 'service_name': services.OWS_VIDEO, 'path': '/job', 'json': job_fields } result = request.process(**request_params) exception_utils.raise_exception_if_request_failed( request_params=request_params, response_status=result.status_code, response_text=result.text, ) @backoff.on_exception( backoff.expo, exception_utils.RetryableError, max_time=config.HTTP_REQUEST_RETRY_PERIOD_SECONDS) def get_product_metadata(product_id): """Get product metadata.""" request_params = { 'application': config.SERVICE_NAME, 'environment': config.OWS_ENVIRONMENT, 'method': 'GET', 'service_name': services.OWS_VIDEO, 'path': '/metadata/{}'.format(product_id), } result = request.process(**request_params) exception_utils.raise_exception_if_request_failed( request_params=request_params, response_status=result.status_code, response_text=result.text, ) return result.json() @backoff.on_exception( backoff.expo, exception_utils.RetryableError, max_time=config.HTTP_REQUEST_RETRY_PERIOD_SECONDS) def is_asset_ready_for_delivery(upc, asset_type): """Get video asset delivery status.""" if config.OWS_ENVIRONMENT == config.ENVIRONMENT_QA: return True request_params = { 'application': config.SERVICE_NAME, 'environment': config.OWS_ENVIRONMENT, 'method': 'GET', 'service_name': services.OWS_VIDEO, 'path': '/delivery/{}/status'.format(upc), 'params': {'asset_type': asset_type} } result = request.process(**request_params) return result.status_code == OK def get_closest_number_option(number, number_options): """Get closest number option.""" min_distance = math.inf closest_number_option = None for number_option in number_options: distance = math.fabs(number_option - number) if distance > min_distance: continue min_distance = distance closest_number_option = number_option return closest_number_option def get_track_video_resolution(data): """Get track video resolution.""" width = Decimal(data[job_io_fields.VIDEO_STREAM_WIDTH_PIXELS]) height = Decimal(data[job_io_fields.VIDEO_STREAM_HEIGHT_PIXELS]) num_pixels = width * height track_video_resolutions = { 414720: 'SD', 921600: '720HD', 3686400: '1080HD', 8847360: '4K', } resolution_options = { np: res for np, res in track_video_resolutions.items() if np >= num_pixels } closest_resolution = get_closest_number_option( num_pixels, list(resolution_options.keys())) return track_video_resolutions[closest_resolution] def get_track_video_aspect_ratio(data): """Get track video aspect ratio.""" width = Decimal(data[job_io_fields.VIDEO_STREAM_WIDTH_PIXELS]) height = Decimal(data[job_io_fields.VIDEO_STREAM_HEIGHT_PIXELS]) track_video_aspect_ratio_enums = [ '1.85:1', '10:7', '16:9', '20:11', '20:13', '20:7', '20:9', '3:2', '40:17', '40:19', '40:21', '40:23', '40:27', '40:29', '4:2', '4:3', '5:2', '5:3', '5:4', '8:3', '8:5', ] track_video_aspect_ratios = { Decimal(ar_enum.split(':')[0]) / Decimal( ar_enum.split(':')[1]): ar_enum for ar_enum in track_video_aspect_ratio_enums } aspect_ratio = width / height closest_aspect_ratio = get_closest_number_option( aspect_ratio, list(track_video_aspect_ratios.keys())) return track_video_aspect_ratios[closest_aspect_ratio] def get_track_video_frame_rate(data): """Get track video frame rate.""" frame_rate = data[job_io_fields.VIDEO_STREAM_FRAME_RATE_FRAMES_PER_SECOND] track_video_frame_rates = [30, 25, 24, 15, 12] return get_closest_number_option(frame_rate, track_video_frame_rates) def get_track_video_duration_minutes(data): """Get track video length minutes.""" audio_seconds = Decimal(data[job_io_fields.AUDIO_STREAM_DURATION_SECONDS]) video_seconds = Decimal(data[job_io_fields.VIDEO_STREAM_DURATION_SECONDS]) max_seconds = max(audio_seconds, video_seconds) return math.floor(max_seconds / 60) def get_track_video_duration_seconds(data): """Get track video length seconds.""" audio_seconds = Decimal(data[job_io_fields.AUDIO_STREAM_DURATION_SECONDS]) video_seconds = Decimal(data[job_io_fields.VIDEO_STREAM_DURATION_SECONDS]) max_seconds = max(audio_seconds, video_seconds) return math.ceil(max_seconds % 60) def get_track_video_duration(data): """Get track video length seconds.""" mezzanine_metadata = json.loads(data) audio_seconds = float( mezzanine_metadata[job_io_fields.AUDIO_STREAM_DURATION_SECONDS] ) video_seconds = float( mezzanine_metadata[job_io_fields.VIDEO_STREAM_DURATION_SECONDS] ) max_duration_seconds = max(audio_seconds, video_seconds) return max_duration_seconds @backoff.on_exception( backoff.expo, exception_utils.RetryableError, max_time=config.HTTP_REQUEST_RETRY_PERIOD_SECONDS) def set_track_metadata(data): """Set track metadata.""" request_params = { 'application': config.SERVICE_NAME, 'environment': config.OWS_ENVIRONMENT, 'method': 'POST', 'service_name': services.OWS_VIDEO, 'path': '/metadata/{}/track'.format(data[job_io_fields.PRODUCT_ID]), 'json': { 'fps': str(get_track_video_frame_rate(data)), 'resolution': get_track_video_resolution(data), 'aspect_ratio': get_track_video_aspect_ratio(data), 'color': 'color', 'channel': '2', 'duration_minutes': get_track_video_duration_minutes(data), 'duration_seconds': get_track_video_duration_seconds(data), 'closed_caption_exists': 0, 'closed_caption_reason_id': 1, } } result = request.process(**request_params) exception_utils.raise_exception_if_request_failed( request_params=request_params, response_status=result.status_code, response_text=result.text, ) return result.json() def get_video_resizing_rules(): """Get video resizing rules.""" request_params = { 'application': config.SERVICE_NAME, 'environment': config.OWS_ENVIRONMENT, 'method': 'GET', 'service_name': services.OWS_VIDEO, 'path': '/sizing_rules', } result = request.process(**request_params) exception_utils.raise_exception_if_request_failed( request_params=request_params, response_status=result.status_code, response_text=result.text, ) return result.json() def get_supported_outer_resolutions(): """Get supported outer resolutions.""" request_params = { 'application': config.SERVICE_NAME, 'environment': config.OWS_ENVIRONMENT, 'method': 'GET', 'service_name': services.OWS_VIDEO, 'path': '/output_video_outer_resolutions', } result = request.process(**request_params) exception_utils.raise_exception_if_request_failed( request_params=request_params, response_status=result.status_code, response_text=result.text, ) return result.json() @backoff.on_exception( backoff.expo, exception_utils.RetryableError, max_time=config.HTTP_REQUEST_RETRY_PERIOD_SECONDS) def upsert_video_asset(video_asset): """Upsert a video asset.""" request_params = { 'application': config.SERVICE_NAME, 'environment': config.OWS_ENVIRONMENT, 'method': 'POST', 'service_name': services.OWS_VIDEO, 'path': '/video-asset', 'json': video_asset } result = request.process(**request_params) exception_utils.raise_exception_if_request_failed( request_params=request_params, response_status=result.status_code, response_text=result.text, ) @backoff.on_exception( backoff.expo, exception_utils.RetryableError, max_time=config.HTTP_REQUEST_RETRY_PERIOD_SECONDS) def set_product_status(product_id, upc, isrc, user_id): """Set product status to in_content.""" request_params = { 'application': config.SERVICE_NAME, 'environment': config.OWS_ENVIRONMENT, 'method': 'POST', 'service_name': services.OWS_VIDEO, 'path': '/product/{}/approval'.format(product_id), 'json': { 'approval_type': 'final', 'value': True, 'approval_in_progress': False, 'isrc': isrc, 'upc': upc, }, 'headers': { 'Orchard-User-Id': user_id } } result = request.process(**request_params) exception_utils.raise_exception_if_request_failed( request_params=request_params, response_status=result.status_code, response_text=result.text, )