Inferencer commited on
Commit
eec3550
·
verified ·
1 Parent(s): e04e4d5

Upload 11 files

Browse files
tests/__init__.py ADDED
File without changes
tests/test_cli.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import subprocess
2
+ import sys
3
+ import pytest
4
+
5
+ from facefusion.download import conditional_download
6
+
7
+
8
+ @pytest.fixture(scope = 'module', autouse = True)
9
+ def before_all() -> None:
10
+ conditional_download('.assets/examples',
11
+ [
12
+ 'https://github.com/facefusion/facefusion-assets/releases/download/examples/source.jpg',
13
+ 'https://github.com/facefusion/facefusion-assets/releases/download/examples/target-1080p.mp4'
14
+ ])
15
+ subprocess.run([ 'ffmpeg', '-i', '.assets/examples/target-1080p.mp4', '-vframes', '1', '.assets/examples/target-1080p.jpg' ])
16
+
17
+
18
+ def test_image_to_image() -> None:
19
+ commands = [ sys.executable, 'run.py', '-s', '.assets/examples/source.jpg', '-t', '.assets/examples/target-1080p.jpg', '-o', '.assets/examples', '--headless' ]
20
+ run = subprocess.run(commands, stdout = subprocess.PIPE, stderr = subprocess.STDOUT)
21
+
22
+ assert run.returncode == 0
23
+ assert 'image succeed' in run.stdout.decode()
24
+
25
+
26
+ def test_image_to_video() -> None:
27
+ commands = [ sys.executable, 'run.py', '-s', '.assets/examples/source.jpg', '-t', '.assets/examples/target-1080p.mp4', '-o', '.assets/examples', '--trim-frame-end', '10', '--headless' ]
28
+ run = subprocess.run(commands, stdout = subprocess.PIPE, stderr = subprocess.STDOUT)
29
+
30
+ assert run.returncode == 0
31
+ assert 'video succeed' in run.stdout.decode()
tests/test_common_helper.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from facefusion.common_helper import create_metavar, create_int_range, create_float_range
2
+
3
+
4
+ def test_create_metavar() -> None:
5
+ assert create_metavar([ 1, 2, 3, 4, 5 ]) == '[1-5]'
6
+
7
+
8
+ def test_create_int_range() -> None:
9
+ assert create_int_range(0, 2, 1) == [ 0, 1, 2 ]
10
+ assert create_float_range(0, 1, 1) == [ 0, 1 ]
11
+
12
+
13
+ def test_create_float_range() -> None:
14
+ assert create_float_range(0.0, 1.0, 0.5) == [ 0.0, 0.5, 1.0 ]
15
+ assert create_float_range(0.0, 0.2, 0.05) == [ 0.0, 0.05, 0.10, 0.15, 0.20 ]
tests/test_config.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from configparser import ConfigParser
2
+ import pytest
3
+
4
+ from facefusion import config
5
+
6
+
7
+ @pytest.fixture(scope = 'module', autouse = True)
8
+ def before_all() -> None:
9
+ config.CONFIG = ConfigParser()
10
+ config.CONFIG.read_dict(
11
+ {
12
+ 'str':
13
+ {
14
+ 'valid': 'a',
15
+ 'unset': ''
16
+ },
17
+ 'int':
18
+ {
19
+ 'valid': '1',
20
+ 'unset': ''
21
+ },
22
+ 'float':
23
+ {
24
+ 'valid': '1.0',
25
+ 'unset': ''
26
+ },
27
+ 'bool':
28
+ {
29
+ 'valid': 'True',
30
+ 'unset': ''
31
+ },
32
+ 'str_list':
33
+ {
34
+ 'valid': 'a b c',
35
+ 'unset': ''
36
+ },
37
+ 'int_list':
38
+ {
39
+ 'valid': '1 2 3',
40
+ 'unset': ''
41
+ },
42
+ 'float_list':
43
+ {
44
+ 'valid': '1.0 2.0 3.0',
45
+ 'unset': ''
46
+ }
47
+ })
48
+
49
+
50
+ def test_get_str_value() -> None:
51
+ assert config.get_str_value('str.valid') == 'a'
52
+ assert config.get_str_value('str.unset', 'b') == 'b'
53
+ assert config.get_str_value('str.unset') is None
54
+ assert config.get_str_value('str.invalid') is None
55
+
56
+
57
+ def test_get_int_value() -> None:
58
+ assert config.get_int_value('int.valid') == 1
59
+ assert config.get_int_value('int.unset', '1') == 1
60
+ assert config.get_int_value('int.unset') is None
61
+ assert config.get_int_value('int.invalid') is None
62
+
63
+
64
+ def test_get_float_value() -> None:
65
+ assert config.get_float_value('float.valid') == 1.0
66
+ assert config.get_float_value('float.unset', '1.0') == 1.0
67
+ assert config.get_float_value('float.unset') is None
68
+ assert config.get_float_value('float.invalid') is None
69
+
70
+
71
+ def test_get_bool_value() -> None:
72
+ assert config.get_bool_value('bool.valid') is True
73
+ assert config.get_bool_value('bool.unset', 'False') is False
74
+ assert config.get_bool_value('bool.unset') is None
75
+ assert config.get_bool_value('bool.invalid') is None
76
+
77
+
78
+ def test_get_str_list() -> None:
79
+ assert config.get_str_list('str_list.valid') == [ 'a', 'b', 'c' ]
80
+ assert config.get_str_list('str_list.unset', 'c b a') == [ 'c', 'b', 'a' ]
81
+ assert config.get_str_list('str_list.unset') is None
82
+ assert config.get_str_list('str_list.invalid') is None
83
+
84
+
85
+ def test_get_int_list() -> None:
86
+ assert config.get_int_list('int_list.valid') == [ 1, 2, 3 ]
87
+ assert config.get_int_list('int_list.unset', '3 2 1') == [ 3, 2, 1 ]
88
+ assert config.get_int_list('int_list.unset') is None
89
+ assert config.get_int_list('int_list.invalid') is None
90
+
91
+
92
+ def test_get_float_list() -> None:
93
+ assert config.get_float_list('float_list.valid') == [ 1.0, 2.0, 3.0 ]
94
+ assert config.get_float_list('float_list.unset', '3.0 2.0 1.0') == [ 3.0, 2.0, 1.0 ]
95
+ assert config.get_float_list('float_list.unset') is None
96
+ assert config.get_float_list('float_list.invalid') is None
tests/test_download.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+
3
+ from facefusion.download import conditional_download, get_download_size, is_download_done
4
+
5
+
6
+ @pytest.fixture(scope = 'module', autouse = True)
7
+ def before_all() -> None:
8
+ conditional_download('.assets/examples',
9
+ [
10
+ 'https://github.com/facefusion/facefusion-assets/releases/download/examples/target-240p.mp4'
11
+ ])
12
+
13
+
14
+ def test_get_download_size() -> None:
15
+ assert get_download_size('https://github.com/facefusion/facefusion-assets/releases/download/examples/target-240p.mp4') == 191675
16
+ assert get_download_size('https://github.com/facefusion/facefusion-assets/releases/download/examples/target-360p.mp4') == 370732
17
+ assert get_download_size('invalid') == 0
18
+
19
+
20
+ def test_is_download_done() -> None:
21
+ assert is_download_done('https://github.com/facefusion/facefusion-assets/releases/download/examples/target-240p.mp4', '.assets/examples/target-240p.mp4') is True
22
+ assert is_download_done('https://github.com/facefusion/facefusion-assets/releases/download/examples/target-240p.mp4','invalid') is False
23
+ assert is_download_done('invalid', 'invalid') is False
tests/test_execution_helper.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from facefusion.execution_helper import encode_execution_providers, decode_execution_providers, apply_execution_provider_options, map_torch_backend
2
+
3
+
4
+ def test_encode_execution_providers() -> None:
5
+ assert encode_execution_providers([ 'CPUExecutionProvider' ]) == [ 'cpu' ]
6
+
7
+
8
+ def test_decode_execution_providers() -> None:
9
+ assert decode_execution_providers([ 'cpu' ]) == [ 'CPUExecutionProvider' ]
10
+
11
+
12
+ def test_multiple_execution_providers() -> None:
13
+ execution_provider_with_options =\
14
+ [
15
+ 'CPUExecutionProvider',
16
+ ('CUDAExecutionProvider',
17
+ {
18
+ 'cudnn_conv_algo_search': 'DEFAULT'
19
+ })
20
+ ]
21
+ assert apply_execution_provider_options([ 'CPUExecutionProvider', 'CUDAExecutionProvider' ]) == execution_provider_with_options
22
+
23
+
24
+ def test_map_device() -> None:
25
+ assert map_torch_backend([ 'CPUExecutionProvider' ]) == 'cpu'
26
+ assert map_torch_backend([ 'CPUExecutionProvider', 'CUDAExecutionProvider' ]) == 'cuda'
tests/test_ffmpeg.py ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import glob
2
+ import subprocess
3
+ import pytest
4
+
5
+ import facefusion.globals
6
+ from facefusion.filesystem import get_temp_directory_path, create_temp, clear_temp
7
+ from facefusion.download import conditional_download
8
+ from facefusion.ffmpeg import extract_frames
9
+
10
+
11
+ @pytest.fixture(scope = 'module', autouse = True)
12
+ def before_all() -> None:
13
+ conditional_download('.assets/examples',
14
+ [
15
+ 'https://github.com/facefusion/facefusion-assets/releases/download/examples/source.jpg',
16
+ 'https://github.com/facefusion/facefusion-assets/releases/download/examples/target-240p.mp4'
17
+ ])
18
+ subprocess.run([ 'ffmpeg', '-i', '.assets/examples/target-240p.mp4', '-vf', 'fps=25', '.assets/examples/target-240p-25fps.mp4' ])
19
+ subprocess.run([ 'ffmpeg', '-i', '.assets/examples/target-240p.mp4', '-vf', 'fps=30', '.assets/examples/target-240p-30fps.mp4' ])
20
+ subprocess.run([ 'ffmpeg', '-i', '.assets/examples/target-240p.mp4', '-vf', 'fps=60', '.assets/examples/target-240p-60fps.mp4' ])
21
+
22
+
23
+ @pytest.fixture(scope = 'function', autouse = True)
24
+ def before_each() -> None:
25
+ facefusion.globals.trim_frame_start = None
26
+ facefusion.globals.trim_frame_end = None
27
+ facefusion.globals.temp_frame_quality = 80
28
+ facefusion.globals.temp_frame_format = 'jpg'
29
+
30
+
31
+ def test_extract_frames() -> None:
32
+ target_paths =\
33
+ [
34
+ '.assets/examples/target-240p-25fps.mp4',
35
+ '.assets/examples/target-240p-30fps.mp4',
36
+ '.assets/examples/target-240p-60fps.mp4'
37
+ ]
38
+ for target_path in target_paths:
39
+ temp_directory_path = get_temp_directory_path(target_path)
40
+ create_temp(target_path)
41
+
42
+ assert extract_frames(target_path, '452x240', 30.0) is True
43
+ assert len(glob.glob1(temp_directory_path, '*.jpg')) == 324
44
+
45
+ clear_temp(target_path)
46
+
47
+
48
+ def test_extract_frames_with_trim_start() -> None:
49
+ facefusion.globals.trim_frame_start = 224
50
+ data_provider =\
51
+ [
52
+ ('.assets/examples/target-240p-25fps.mp4', 55),
53
+ ('.assets/examples/target-240p-30fps.mp4', 100),
54
+ ('.assets/examples/target-240p-60fps.mp4', 212)
55
+ ]
56
+ for target_path, frame_total in data_provider:
57
+ temp_directory_path = get_temp_directory_path(target_path)
58
+ create_temp(target_path)
59
+
60
+ assert extract_frames(target_path, '452x240', 30.0) is True
61
+ assert len(glob.glob1(temp_directory_path, '*.jpg')) == frame_total
62
+
63
+ clear_temp(target_path)
64
+
65
+
66
+ def test_extract_frames_with_trim_start_and_trim_end() -> None:
67
+ facefusion.globals.trim_frame_start = 124
68
+ facefusion.globals.trim_frame_end = 224
69
+ data_provider =\
70
+ [
71
+ ('.assets/examples/target-240p-25fps.mp4', 120),
72
+ ('.assets/examples/target-240p-30fps.mp4', 100),
73
+ ('.assets/examples/target-240p-60fps.mp4', 50)
74
+ ]
75
+ for target_path, frame_total in data_provider:
76
+ temp_directory_path = get_temp_directory_path(target_path)
77
+ create_temp(target_path)
78
+
79
+ assert extract_frames(target_path, '452x240', 30.0) is True
80
+ assert len(glob.glob1(temp_directory_path, '*.jpg')) == frame_total
81
+
82
+ clear_temp(target_path)
83
+
84
+
85
+ def test_extract_frames_with_trim_end() -> None:
86
+ facefusion.globals.trim_frame_end = 100
87
+ data_provider =\
88
+ [
89
+ ('.assets/examples/target-240p-25fps.mp4', 120),
90
+ ('.assets/examples/target-240p-30fps.mp4', 100),
91
+ ('.assets/examples/target-240p-60fps.mp4', 50)
92
+ ]
93
+ for target_path, frame_total in data_provider:
94
+ temp_directory_path = get_temp_directory_path(target_path)
95
+ create_temp(target_path)
96
+
97
+ assert extract_frames(target_path, '426x240', 30.0) is True
98
+ assert len(glob.glob1(temp_directory_path, '*.jpg')) == frame_total
99
+
100
+ clear_temp(target_path)
tests/test_filesystem.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+
3
+ from facefusion.download import conditional_download
4
+ from facefusion.filesystem import is_file, is_directory, is_image, are_images, is_video, list_directory
5
+
6
+
7
+ @pytest.fixture(scope = 'module', autouse = True)
8
+ def before_all() -> None:
9
+ conditional_download('.assets/examples',
10
+ [
11
+ 'https://github.com/facefusion/facefusion-assets/releases/download/examples/source.jpg'
12
+ ])
13
+
14
+
15
+ def test_is_file() -> None:
16
+ assert is_file('.assets/examples/source.jpg') is True
17
+ assert is_file('.assets/examples') is False
18
+ assert is_file('invalid') is False
19
+
20
+
21
+ def test_is_directory() -> None:
22
+ assert is_directory('.assets/examples') is True
23
+ assert is_directory('.assets/examples/source.jpg') is False
24
+ assert is_directory('invalid') is False
25
+
26
+
27
+ def test_is_image() -> None:
28
+ assert is_image('.assets/examples/source.jpg') is True
29
+ assert is_image('.assets/examples/target-240p.mp4') is False
30
+ assert is_image('invalid') is False
31
+
32
+
33
+ def test_are_images() -> None:
34
+ assert are_images([ '.assets/examples/source.jpg' ]) is True
35
+ assert are_images([ '.assets/examples/source.jpg', '.assets/examples/target-240p.mp4' ]) is False
36
+ assert are_images([ 'invalid' ]) is False
37
+
38
+
39
+ def test_is_video() -> None:
40
+ assert is_video('.assets/examples/target-240p.mp4') is True
41
+ assert is_video('.assets/examples/source.jpg') is False
42
+ assert is_video('invalid') is False
43
+
44
+
45
+ def test_list_directory() -> None:
46
+ assert list_directory('.assets/examples')
47
+ assert list_directory('.assets/examples/source.jpg') is None
48
+ assert list_directory('invalid') is None
tests/test_memory.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import platform
2
+
3
+ from facefusion.memory import limit_system_memory
4
+
5
+
6
+ def test_limit_system_memory() -> None:
7
+ assert limit_system_memory(4) is True
8
+ if platform.system().lower() == 'darwin' or platform.system().lower() == 'linux':
9
+ assert limit_system_memory(1024) is False
tests/test_normalizer.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import platform
2
+
3
+ from facefusion.normalizer import normalize_output_path, normalize_padding, normalize_fps
4
+
5
+
6
+ def test_normalize_output_path() -> None:
7
+ if platform.system().lower() != 'windows':
8
+ assert normalize_output_path([ '.assets/examples/source.jpg' ], None, '.assets/examples/target-240p.mp4') == '.assets/examples/target-240p.mp4'
9
+ assert normalize_output_path(None, '.assets/examples/target-240p.mp4', '.assets/examples/target-240p.mp4') == '.assets/examples/target-240p.mp4'
10
+ assert normalize_output_path(None, '.assets/examples/target-240p.mp4', '.assets/examples') == '.assets/examples/target-240p.mp4'
11
+ assert normalize_output_path([ '.assets/examples/source.jpg' ], '.assets/examples/target-240p.mp4', '.assets/examples') == '.assets/examples/source-target-240p.mp4'
12
+ assert normalize_output_path(None, '.assets/examples/target-240p.mp4', '.assets/examples/output.mp4') == '.assets/examples/output.mp4'
13
+ assert normalize_output_path(None, '.assets/examples/target-240p.mp4', '.assets/output.mov') == '.assets/output.mp4'
14
+ assert normalize_output_path(None, '.assets/examples/target-240p.mp4', '.assets/examples/invalid') is None
15
+ assert normalize_output_path(None, '.assets/examples/target-240p.mp4', '.assets/invalid/output.mp4') is None
16
+ assert normalize_output_path(None, '.assets/examples/target-240p.mp4', 'invalid') is None
17
+ assert normalize_output_path([ '.assets/examples/source.jpg' ], '.assets/examples/target-240p.mp4', None) is None
18
+
19
+
20
+ def test_normalize_padding() -> None:
21
+ assert normalize_padding([ 0, 0, 0, 0 ]) == (0, 0, 0, 0)
22
+ assert normalize_padding([ 1 ]) == (1, 1, 1, 1)
23
+ assert normalize_padding([ 1, 2 ]) == (1, 2, 1, 2)
24
+ assert normalize_padding([ 1, 2, 3 ]) == (1, 2, 3, 2)
25
+ assert normalize_padding(None) is None
26
+
27
+
28
+ def test_normalize_fps() -> None:
29
+ assert normalize_fps(0.0) == 1.0
30
+ assert normalize_fps(25.0) == 25.0
31
+ assert normalize_fps(61.0) == 60.0
32
+ assert normalize_fps(None) is None
tests/test_vision.py ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import subprocess
2
+ import pytest
3
+
4
+ from facefusion.download import conditional_download
5
+ from facefusion.vision import get_video_frame, count_video_frame_total, detect_video_fps, detect_video_resolution, pack_resolution, unpack_resolution, create_video_resolutions
6
+
7
+
8
+ @pytest.fixture(scope = 'module', autouse = True)
9
+ def before_all() -> None:
10
+ conditional_download('.assets/examples',
11
+ [
12
+ 'https://github.com/facefusion/facefusion-assets/releases/download/examples/source.jpg',
13
+ 'https://github.com/facefusion/facefusion-assets/releases/download/examples/target-240p.mp4',
14
+ 'https://github.com/facefusion/facefusion-assets/releases/download/examples/target-1080p.mp4'
15
+ ])
16
+ subprocess.run([ 'ffmpeg', '-i', '.assets/examples/target-240p.mp4', '-vf', 'fps=25', '.assets/examples/target-240p-25fps.mp4' ])
17
+ subprocess.run([ 'ffmpeg', '-i', '.assets/examples/target-240p.mp4', '-vf', 'fps=30', '.assets/examples/target-240p-30fps.mp4' ])
18
+ subprocess.run([ 'ffmpeg', '-i', '.assets/examples/target-240p.mp4', '-vf', 'fps=60', '.assets/examples/target-240p-60fps.mp4' ])
19
+ subprocess.run([ 'ffmpeg', '-i', '.assets/examples/target-240p.mp4', '-vf', 'transpose=0', '.assets/examples/target-240p-90deg.mp4' ])
20
+ subprocess.run([ 'ffmpeg', '-i', '.assets/examples/target-1080p.mp4', '-vf', 'transpose=0', '.assets/examples/target-1080p-90deg.mp4' ])
21
+
22
+
23
+ def test_get_video_frame() -> None:
24
+ assert get_video_frame('.assets/examples/target-240p-25fps.mp4') is not None
25
+ assert get_video_frame('invalid') is None
26
+
27
+
28
+ def test_count_video_frame_total() -> None:
29
+ assert count_video_frame_total('.assets/examples/target-240p-25fps.mp4') == 270
30
+ assert count_video_frame_total('.assets/examples/target-240p-30fps.mp4') == 324
31
+ assert count_video_frame_total('.assets/examples/target-240p-60fps.mp4') == 648
32
+ assert count_video_frame_total('invalid') == 0
33
+
34
+
35
+ def test_detect_video_fps() -> None:
36
+ assert detect_video_fps('.assets/examples/target-240p-25fps.mp4') == 25.0
37
+ assert detect_video_fps('.assets/examples/target-240p-30fps.mp4') == 30.0
38
+ assert detect_video_fps('.assets/examples/target-240p-60fps.mp4') == 60.0
39
+ assert detect_video_fps('invalid') is None
40
+
41
+
42
+ def test_detect_video_resolution() -> None:
43
+ assert detect_video_resolution('.assets/examples/target-240p.mp4') == (426.0, 226.0)
44
+ assert detect_video_resolution('.assets/examples/target-1080p.mp4') == (2048.0, 1080.0)
45
+ assert detect_video_resolution('invalid') is None
46
+
47
+
48
+ def test_pack_resolution() -> None:
49
+ assert pack_resolution((1.0, 1.0)) == '0x0'
50
+ assert pack_resolution((2.0, 2.0)) == '2x2'
51
+
52
+
53
+ def test_unpack_resolution() -> None:
54
+ assert unpack_resolution('0x0') == (0, 0)
55
+ assert unpack_resolution('2x2') == (2, 2)
56
+
57
+
58
+ def test_create_video_resolutions() -> None:
59
+ assert create_video_resolutions('.assets/examples/target-240p.mp4') == [ '426x226', '452x240', '678x360', '904x480', '1018x540', '1358x720', '2036x1080', '2714x1440', '4072x2160' ]
60
+ assert create_video_resolutions('.assets/examples/target-240p-90deg.mp4') == [ '226x426', '240x452', '360x678', '480x904', '540x1018', '720x1358', '1080x2036', '1440x2714', '2160x4072' ]
61
+ assert create_video_resolutions('.assets/examples/target-1080p.mp4') == [ '456x240', '682x360', '910x480', '1024x540', '1366x720', '2048x1080', '2730x1440', '4096x2160' ]
62
+ assert create_video_resolutions('.assets/examples/target-1080p-90deg.mp4') == [ '240x456', '360x682', '480x910', '540x1024', '720x1366', '1080x2048', '1440x2730', '2160x4096' ]
63
+ assert create_video_resolutions('invalid') is None