"""Test detect_video_location_and_dimensions_within_black_border.""" from unittest.mock import call import ffmpeg import pytest from video.connectors import s3 as s3_connector from video.constants import job_io_fields from video.logic.activities import \ detect_video_location_and_dimensions_within_black_border from video.logic.activities.detect_video_location_and_dimensions_within_black_border import Crop # noqa: E501 OUTER_WIDTH = 1920 OUTER_HEIGHT = 1080 def _fake_stderr_output( *, outer_width, left_offset, right_offset, outer_height, top_offset, bottom_offset, ): inner_width = outer_width - left_offset - right_offset inner_height = outer_height - top_offset - bottom_offset return ( f""" [Parsed_cropdetect_0 @ 0x7fb24c700ac0] x1:2019 x2:0 y1:867 y2:0 w:-2016 h:-864 x:2020 y:868 pts:1024 t:0.080000 crop=-2016:-864:2020:868 [Parsed_cropdetect_0 @ 0x7fb24c700ac0] x1:144 x2:1464 y1:135 y2:559 w:1312 h:416 x:148 y:140 pts:9728 t:0.760000 crop=1312:416:148:140 [Parsed_cropdetect_0 @ 0x7fb24c700ac0] x1:144 x2:1464 y1:135 y2:560 w:1312 h:416 x:148 y:140 pts:16384 t:1.280000 crop=1312:416:148:140 applesauce bananas [Parsed_cropdetect_0 @ 0x7fb24c700ac0] x1:117 x2:1487 y1:124 y2:643 w:1360 h:512 x:124 y:128 pts:26624 t:2.080000 crop=1360:512:124:128 [Parsed_cropdetect_0 @ 0x7fb24c700ac0] x1:117 x2:1611 y1:50 y2:817 w:1488 h:768 x:122 y:50 pts:43008 t:3.360000 crop=1488:768:122:50 [Parsed_cropdetect_0 @ 0x7fb24c700ac0] x1:117 x2:1791 y1:50 y2:817 w:1664 h:768 x:124 y:50 pts:88064 t:6.880000 crop=1664:768:124:50 [Parsed_cropdetect_0 @ 0x7fb24c700ac0] x1:50 x2:1969 y1:50 y2:817 w:1920 h:768 x:50 y:50 pts:324096 t:25.320000 crop=1920:768:50:50 applesauce bananas [Parsed_cropdetect_0 @ 0x7fb24c700ac0] x1:50 x2:1969 y1:50 y2:817 w:1920 h:768 x:50 y:50 pts:324608 t:25.360000 crop=1920:768:50:50 [Parsed_cropdetect_0 @ 0x7fb24c700ac0] x1:50 x2:1969 y1:50 y2:817 w:1920 h:768 x:50 y:50 pts:325120 t:25.400000 crop=1920:768:50:50 [Parsed_cropdetect_0 @ 0x7fd83ea04400] x1:84 x2:1919 y1:0 y2:1079 w:1836 h:1080 x:84 y:0 pts:6102000 t:244.080000 crop={inner_width}:{inner_height}:{left_offset}:{top_offset} applesauce bananas applesauce bananas applesauce bananas applesauce bananas """.encode() # noqa ) def _test_detect_video_location_and_dimensions_within_black_border_case_params( *, test_description, outer_width, outer_height, looser_crop_stderr, tighter_crop_stderr, expected_left_offset, expected_inner_width, expected_top_offset, expected_inner_height, ): return ( test_description, outer_width, outer_height, looser_crop_stderr, tighter_crop_stderr, expected_left_offset, expected_inner_width, expected_top_offset, expected_inner_height, ) @pytest.mark.parametrize(( 'test_description', 'outer_width', 'outer_height', 'looser_crop_stderr', 'tighter_crop_stderr', 'expected_left_offset', 'expected_inner_width', 'expected_top_offset', 'expected_inner_height', ), [ _test_detect_video_location_and_dimensions_within_black_border_case_params( test_description='no crop needed', outer_width=OUTER_WIDTH, outer_height=OUTER_HEIGHT, looser_crop_stderr=_fake_stderr_output( outer_width=OUTER_WIDTH, left_offset=0, right_offset=0, outer_height=OUTER_HEIGHT, top_offset=0, bottom_offset=0, ), tighter_crop_stderr=_fake_stderr_output( outer_width=OUTER_WIDTH, left_offset=0, right_offset=0, outer_height=OUTER_HEIGHT, top_offset=0, bottom_offset=0, ), expected_left_offset=0, expected_inner_width=OUTER_WIDTH, expected_top_offset=0, expected_inner_height=OUTER_HEIGHT, ), _test_detect_video_location_and_dimensions_within_black_border_case_params( test_description='crop 50 from left and right', outer_width=OUTER_WIDTH, outer_height=OUTER_HEIGHT, looser_crop_stderr=_fake_stderr_output( outer_width=OUTER_WIDTH, left_offset=50, right_offset=50, outer_height=OUTER_HEIGHT, top_offset=0, bottom_offset=0, ), tighter_crop_stderr=_fake_stderr_output( outer_width=OUTER_WIDTH, left_offset=50, right_offset=50, outer_height=OUTER_HEIGHT, top_offset=0, bottom_offset=0, ), expected_left_offset=50, expected_inner_width=OUTER_WIDTH - 50 - 50, expected_top_offset=0, expected_inner_height=OUTER_HEIGHT, ), _test_detect_video_location_and_dimensions_within_black_border_case_params( test_description='crop 10 from top and bottom', outer_width=OUTER_WIDTH, outer_height=OUTER_HEIGHT, looser_crop_stderr=_fake_stderr_output( outer_width=OUTER_WIDTH, left_offset=0, right_offset=0, outer_height=OUTER_HEIGHT, top_offset=10, bottom_offset=10, ), tighter_crop_stderr=_fake_stderr_output( outer_width=OUTER_WIDTH, left_offset=0, right_offset=0, outer_height=OUTER_HEIGHT, top_offset=10, bottom_offset=10, ), expected_left_offset=0, expected_inner_width=OUTER_WIDTH, expected_top_offset=10, expected_inner_height=OUTER_HEIGHT - 10 - 10, ), _test_detect_video_location_and_dimensions_within_black_border_case_params( test_description='asymmetric crop with shift up and left within tolerance', # noqa: E501 outer_width=OUTER_WIDTH, outer_height=OUTER_HEIGHT, looser_crop_stderr=_fake_stderr_output( outer_width=OUTER_WIDTH, left_offset=2 + Crop.CROP_TOLERANCE_PIXELS, right_offset=2, outer_height=OUTER_HEIGHT, top_offset=2 + Crop.CROP_TOLERANCE_PIXELS, bottom_offset=2, ), tighter_crop_stderr=_fake_stderr_output( outer_width=OUTER_WIDTH, left_offset=2 + Crop.CROP_TOLERANCE_PIXELS, right_offset=2, outer_height=OUTER_HEIGHT, top_offset=2 + Crop.CROP_TOLERANCE_PIXELS, bottom_offset=2, ), expected_left_offset=2 + Crop.CROP_TOLERANCE_PIXELS, expected_inner_width=OUTER_WIDTH - (2 + Crop.CROP_TOLERANCE_PIXELS) - 2, # noqa: E501 expected_top_offset=2 + Crop.CROP_TOLERANCE_PIXELS, expected_inner_height=OUTER_HEIGHT - (2 + Crop.CROP_TOLERANCE_PIXELS) - 2, # noqa: E501 ), _test_detect_video_location_and_dimensions_within_black_border_case_params( test_description='asymmetric crop with shift up and left outside tolerance', # noqa: E501 outer_width=OUTER_WIDTH, outer_height=OUTER_HEIGHT, looser_crop_stderr=_fake_stderr_output( outer_width=OUTER_WIDTH, left_offset=2 + (Crop.CROP_TOLERANCE_PIXELS + 2), right_offset=2, outer_height=OUTER_HEIGHT, top_offset=2 + (Crop.CROP_TOLERANCE_PIXELS + 2), bottom_offset=2, ), tighter_crop_stderr=_fake_stderr_output( outer_width=OUTER_WIDTH, left_offset=2 + (Crop.CROP_TOLERANCE_PIXELS + 2), right_offset=2, outer_height=OUTER_HEIGHT, top_offset=2 + (Crop.CROP_TOLERANCE_PIXELS + 2), bottom_offset=2, ), expected_left_offset=2, expected_inner_width=OUTER_WIDTH - 2 - 2, expected_top_offset=2, expected_inner_height=OUTER_HEIGHT - 2 - 2, ), _test_detect_video_location_and_dimensions_within_black_border_case_params( test_description='asymmetric crop with shift down and right within tolerance', # noqa: E501 outer_width=OUTER_WIDTH, outer_height=OUTER_HEIGHT, looser_crop_stderr=_fake_stderr_output( outer_width=OUTER_WIDTH, left_offset=2, right_offset=2 + Crop.CROP_TOLERANCE_PIXELS, outer_height=OUTER_HEIGHT, top_offset=2, bottom_offset=2 + Crop.CROP_TOLERANCE_PIXELS, ), tighter_crop_stderr=_fake_stderr_output( outer_width=OUTER_WIDTH, left_offset=2, right_offset=2 + Crop.CROP_TOLERANCE_PIXELS, outer_height=OUTER_HEIGHT, top_offset=2, bottom_offset=2 + Crop.CROP_TOLERANCE_PIXELS, ), expected_left_offset=2, expected_inner_width=OUTER_WIDTH - (2 + Crop.CROP_TOLERANCE_PIXELS) - 2, # noqa: E501 expected_top_offset=2, expected_inner_height=OUTER_HEIGHT - (2 + Crop.CROP_TOLERANCE_PIXELS) - 2, # noqa: E501 ), _test_detect_video_location_and_dimensions_within_black_border_case_params( test_description='asymmetric crop with shift down and right outside tolerance', # noqa: E501 outer_width=OUTER_WIDTH, outer_height=OUTER_HEIGHT, looser_crop_stderr=_fake_stderr_output( outer_width=OUTER_WIDTH, left_offset=2, right_offset=2 + (Crop.CROP_TOLERANCE_PIXELS + 2), outer_height=OUTER_HEIGHT, top_offset=2, bottom_offset=2 + (Crop.CROP_TOLERANCE_PIXELS + 2), ), tighter_crop_stderr=_fake_stderr_output( outer_width=OUTER_WIDTH, left_offset=2, right_offset=2 + (Crop.CROP_TOLERANCE_PIXELS + 2), outer_height=OUTER_HEIGHT, top_offset=2, bottom_offset=2 + (Crop.CROP_TOLERANCE_PIXELS + 2), ), expected_left_offset=2, expected_inner_width=OUTER_WIDTH - 2 - 2, expected_top_offset=2, expected_inner_height=OUTER_HEIGHT - 2 - 2, ), _test_detect_video_location_and_dimensions_within_black_border_case_params( test_description='tighter crop used when its distance from looser crop is within tolerance', # noqa: E501 outer_width=OUTER_WIDTH, outer_height=OUTER_HEIGHT, looser_crop_stderr=_fake_stderr_output( outer_width=OUTER_WIDTH, left_offset=0, right_offset=0, outer_height=OUTER_HEIGHT, top_offset=0, bottom_offset=0, ), tighter_crop_stderr=_fake_stderr_output( outer_width=OUTER_WIDTH, left_offset=Crop.CROP_TOLERANCE_PIXELS, right_offset=Crop.CROP_TOLERANCE_PIXELS, outer_height=OUTER_HEIGHT, top_offset=Crop.CROP_TOLERANCE_PIXELS, bottom_offset=Crop.CROP_TOLERANCE_PIXELS, ), expected_left_offset=Crop.CROP_TOLERANCE_PIXELS, expected_inner_width=( OUTER_WIDTH - Crop.CROP_TOLERANCE_PIXELS - Crop.CROP_TOLERANCE_PIXELS # noqa: E501 ), expected_top_offset=Crop.CROP_TOLERANCE_PIXELS, expected_inner_height=( OUTER_HEIGHT - Crop.CROP_TOLERANCE_PIXELS - Crop.CROP_TOLERANCE_PIXELS # noqa: E501 ), ), _test_detect_video_location_and_dimensions_within_black_border_case_params( test_description='looser crop is used when its distance from the tighter crop is outside tolerance', # noqa: E501 outer_width=OUTER_WIDTH, outer_height=OUTER_HEIGHT, looser_crop_stderr=_fake_stderr_output( outer_width=OUTER_WIDTH, left_offset=0, right_offset=0, outer_height=OUTER_HEIGHT, top_offset=0, bottom_offset=0, ), tighter_crop_stderr=_fake_stderr_output( outer_width=OUTER_WIDTH, left_offset=Crop.CROP_TOLERANCE_PIXELS + 2, right_offset=Crop.CROP_TOLERANCE_PIXELS, outer_height=OUTER_HEIGHT, top_offset=Crop.CROP_TOLERANCE_PIXELS, bottom_offset=Crop.CROP_TOLERANCE_PIXELS + 2, ), expected_left_offset=0, expected_inner_width=OUTER_WIDTH - Crop.CROP_TOLERANCE_PIXELS, expected_top_offset=Crop.CROP_TOLERANCE_PIXELS, expected_inner_height=OUTER_HEIGHT - Crop.CROP_TOLERANCE_PIXELS, ), ]) def test_detect_video_location_and_dimensions_within_black_border( mocker, test_description, looser_crop_stderr, tighter_crop_stderr, outer_width, outer_height, expected_left_offset, expected_inner_width, expected_top_offset, expected_inner_height, ): """Test detect_video_location_and_dimensions_within_black_border.""" mocker.patch.object( s3_connector, 'get_url_for_s3_object', return_value='test_url', autospec=True, ) mocker.patch.object( ffmpeg, 'input', autospec=True, ) input_mock = ffmpeg.input filter_mock = input_mock.return_value.filter output_mock = filter_mock.return_value.output run_mock = output_mock.return_value.run run_mock.side_effect = [ ('test stdout 1'.encode(), looser_crop_stderr), ('test stdout 2'.encode(), tighter_crop_stderr), ] inputs = { job_io_fields.DETECT_VIDEO_LOCATION_AND_DIMENSIONS_WITHIN_BLACK_BORDER_JOB_ID: 123, # noqa: E501 job_io_fields.INPUT_VIDEO_S3_BUCKET: 'test_bucket', job_io_fields.INPUT_VIDEO_S3_KEY: 'test_key/123.mov', job_io_fields.VIDEO_STREAM_WIDTH_PIXELS: outer_width, job_io_fields.VIDEO_STREAM_HEIGHT_PIXELS: outer_height, } expected_outputs = { job_io_fields.VIDEO_INNER_TOP_LEFT_CORNER_X_PIXELS: expected_left_offset, job_io_fields.VIDEO_INNER_TOP_LEFT_CORNER_Y_PIXELS: expected_top_offset, job_io_fields.VIDEO_INNER_WIDTH_PIXELS: expected_inner_width, job_io_fields.VIDEO_INNER_HEIGHT_PIXELS: expected_inner_height } outputs = ( detect_video_location_and_dimensions_within_black_border .detect_video_location_and_dimensions_within_black_border(inputs) ) assert expected_outputs == outputs assert input_mock.call_args_list == [call('test_url'), call('test_url')] assert filter_mock.call_args_list == [ call( 'cropdetect', limit=16 / 255, round=2, reset=0 ), call( 'cropdetect', limit=24 / 255, round=2, reset=0 ), ] assert output_mock.call_args_list == [ call( 'pipe:', format='null', **{'an': None}, ), call( 'pipe:', format='null', **{'an': None}, ), ] assert run_mock.call_args_list == [ call(capture_stderr=True), call(capture_stderr=True)] def test_detect_video_location_and_dimensions_within_black_border_crop_info_not_found( # noqa: E501 mocker, ): """Test detect_video_location_and_dimensions_within_black_border.""" mocker.patch.object( s3_connector, 'get_url_for_s3_object', return_value='test_url', autospec=True, ) mocker.patch.object( ffmpeg, 'input', autospec=True, ) input_mock = ffmpeg.input filter_mock = input_mock.return_value.filter output_mock = filter_mock.return_value.output run_mock = output_mock.return_value.run run_mock.side_effect = [ ('test stdout 1'.encode(), b'applesauce bananas'), ] inputs = { job_io_fields.DETECT_VIDEO_LOCATION_AND_DIMENSIONS_WITHIN_BLACK_BORDER_JOB_ID: 123, # noqa job_io_fields.INPUT_VIDEO_S3_BUCKET: 'test_bucket', job_io_fields.INPUT_VIDEO_S3_KEY: 'test_key/123.mov', job_io_fields.VIDEO_STREAM_WIDTH_PIXELS: 1920, job_io_fields.VIDEO_STREAM_HEIGHT_PIXELS: 1080, } expected_outputs = { job_io_fields.VIDEO_INNER_TOP_LEFT_CORNER_X_PIXELS: None, job_io_fields.VIDEO_INNER_TOP_LEFT_CORNER_Y_PIXELS: None, job_io_fields.VIDEO_INNER_WIDTH_PIXELS: None, job_io_fields.VIDEO_INNER_HEIGHT_PIXELS: None } outputs = ( detect_video_location_and_dimensions_within_black_border .detect_video_location_and_dimensions_within_black_border(inputs) ) assert expected_outputs == outputs assert input_mock.call_args_list == [call('test_url')] assert filter_mock.call_args_list == [ call( 'cropdetect', limit=16 / 255, round=2, reset=0 ), ] assert output_mock.call_args_list == [ call( 'pipe:', format='null', **{'an': None}, ), ] assert run_mock.call_args_list == [call(capture_stderr=True)]