diff --git a/server/src/services/media.service.spec.ts b/server/src/services/media.service.spec.ts index 51a10a39c2..61940dd91d 100644 --- a/server/src/services/media.service.spec.ts +++ b/server/src/services/media.service.spec.ts @@ -504,13 +504,18 @@ describe(MediaService.name, () => { '/original/path.ext', expect.any(String), expect.objectContaining({ - inputOptions: ['-skip_frame nointra', '-sws_flags accurate_rnd+full_chroma_int'], + inputOptions: ['-skip_frame', 'nointra', '-sws_flags', 'accurate_rnd+full_chroma_int'], outputOptions: [ - '-fps_mode vfr', - '-frames:v 1', - '-update 1', - '-v verbose', - String.raw`-vf fps=12:start_time=0:eof_action=pass:round=down,thumbnail=12,select=gt(scene\,0.1)-eq(prev_selected_n\,n)+isnan(prev_selected_n)+gt(n\,20),trim=end_frame=2,reverse,scale=-2:1440:flags=lanczos+accurate_rnd+full_chroma_int:out_range=pc`, + '-fps_mode', + 'vfr', + '-frames:v', + '1', + '-update', + '1', + '-v', + 'verbose', + '-vf', + String.raw`fps=12:start_time=0:eof_action=pass:round=down,thumbnail=12,select=gt(scene\,0.1)-eq(prev_selected_n\,n)+isnan(prev_selected_n)+gt(n\,20),trim=end_frame=2,reverse,scale=-2:1440:flags=lanczos+accurate_rnd+full_chroma_int:out_range=pc`, ], twoPass: false, }), @@ -546,13 +551,18 @@ describe(MediaService.name, () => { '/original/path.ext', expect.any(String), expect.objectContaining({ - inputOptions: ['-skip_frame nointra', '-sws_flags accurate_rnd+full_chroma_int'], + inputOptions: ['-skip_frame', 'nointra', '-sws_flags', 'accurate_rnd+full_chroma_int'], outputOptions: [ - '-fps_mode vfr', - '-frames:v 1', - '-update 1', - '-v verbose', - String.raw`-vf fps=12:start_time=0:eof_action=pass:round=down,thumbnail=12,select=gt(scene\,0.1)-eq(prev_selected_n\,n)+isnan(prev_selected_n)+gt(n\,20),trim=end_frame=2,reverse,tonemapx=tonemap=hable:desat=0:p=bt709:t=bt709:m=bt709:r=pc:peak=100:format=yuv420p`, + '-fps_mode', + 'vfr', + '-frames:v', + '1', + '-update', + '1', + '-v', + 'verbose', + '-vf', + String.raw`fps=12:start_time=0:eof_action=pass:round=down,thumbnail=12,select=gt(scene\,0.1)-eq(prev_selected_n\,n)+isnan(prev_selected_n)+gt(n\,20),trim=end_frame=2,reverse,tonemapx=tonemap=hable:desat=0:p=bt709:t=bt709:m=bt709:r=pc:peak=100:format=yuv420p`, ], twoPass: false, }), @@ -590,13 +600,18 @@ describe(MediaService.name, () => { '/original/path.ext', expect.any(String), expect.objectContaining({ - inputOptions: ['-skip_frame nointra', '-sws_flags accurate_rnd+full_chroma_int'], + inputOptions: ['-skip_frame', 'nointra', '-sws_flags', 'accurate_rnd+full_chroma_int'], outputOptions: [ - '-fps_mode vfr', - '-frames:v 1', - '-update 1', - '-v verbose', - String.raw`-vf fps=12:start_time=0:eof_action=pass:round=down,thumbnail=12,select=gt(scene\,0.1)-eq(prev_selected_n\,n)+isnan(prev_selected_n)+gt(n\,20),trim=end_frame=2,reverse,tonemapx=tonemap=hable:desat=0:p=bt709:t=bt709:m=bt709:r=pc:peak=100:format=yuv420p`, + '-fps_mode', + 'vfr', + '-frames:v', + '1', + '-update', + '1', + '-v', + 'verbose', + '-vf', + String.raw`fps=12:start_time=0:eof_action=pass:round=down,thumbnail=12,select=gt(scene\,0.1)-eq(prev_selected_n\,n)+isnan(prev_selected_n)+gt(n\,20),trim=end_frame=2,reverse,tonemapx=tonemap=hable:desat=0:p=bt709:t=bt709:m=bt709:r=pc:peak=100:format=yuv420p`, ], twoPass: false, }), @@ -613,7 +628,7 @@ describe(MediaService.name, () => { '/original/path.ext', expect.any(String), expect.objectContaining({ - inputOptions: ['-sws_flags accurate_rnd+full_chroma_int'], + inputOptions: ['-sws_flags', 'accurate_rnd+full_chroma_int'], outputOptions: expect.any(Array), progress: expect.any(Object), twoPass: false, @@ -632,7 +647,8 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.arrayContaining([ - '-bsf:v hevc_metadata=colour_primaries=1:matrix_coefficients=1:transfer_characteristics=1', + '-bsf:v', + 'hevc_metadata=colour_primaries=1:matrix_coefficients=1:transfer_characteristics=1', ]), outputOptions: expect.any(Array), progress: expect.any(Object), @@ -1932,7 +1948,7 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-map 0:1', '-map 0:3']), + outputOptions: expect.arrayContaining(['-map', '0:1', '-map', '0:3']), twoPass: false, }), ); @@ -1952,7 +1968,7 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-map 0:0', '-map 0:2']), + outputOptions: expect.arrayContaining(['-map', '0:0', '-map', '0:2']), twoPass: false, }), ); @@ -2147,7 +2163,7 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-c:v copy', '-c:a aac']), + outputOptions: expect.arrayContaining(['-c:v', 'copy', '-c:a', 'aac']), twoPass: false, }), ); @@ -2168,7 +2184,7 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.any(Array), - outputOptions: expect.not.arrayContaining(['-tag:v hvc1']), + outputOptions: expect.not.arrayContaining(['-tag:v', 'hvc1']), twoPass: false, }), ); @@ -2189,7 +2205,7 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-c:v copy', '-tag:v hvc1']), + outputOptions: expect.arrayContaining(['-c:v', 'copy', '-tag:v', 'hvc1']), twoPass: false, }), ); @@ -2204,7 +2220,7 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-c:v h264', '-c:a copy']), + outputOptions: expect.arrayContaining(['-c:v', 'h264', '-c:a', 'copy']), twoPass: false, }), ); @@ -2218,7 +2234,7 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-c:v copy', '-c:a copy']), + outputOptions: expect.arrayContaining(['-c:v', 'copy', '-c:a', 'copy']), twoPass: false, }), ); @@ -2279,7 +2295,7 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-c:v h264', '-maxrate 4500k', '-bufsize 9000k']), + outputOptions: expect.arrayContaining(['-c:v', 'h264', '-maxrate', '4500k', '-bufsize', '9000k']), twoPass: false, }), ); @@ -2294,7 +2310,7 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-c:v h264', '-maxrate 4500k', '-bufsize 9000k']), + outputOptions: expect.arrayContaining(['-c:v', 'h264', '-maxrate', '4500k', '-bufsize', '9000k']), twoPass: false, }), ); @@ -2309,7 +2325,16 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-c:v h264', '-b:v 3104k', '-minrate 1552k', '-maxrate 4500k']), + outputOptions: expect.arrayContaining([ + '-c:v', + 'h264', + '-b:v', + '3104k', + '-minrate', + '1552k', + '-maxrate', + '4500k', + ]), twoPass: true, }), ); @@ -2324,7 +2349,7 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-c:v h264', '-c:a copy']), + outputOptions: expect.arrayContaining(['-c:v', 'h264', '-c:a', 'copy']), twoPass: false, }), ); @@ -2345,7 +2370,7 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-b:v 3104k', '-minrate 1552k', '-maxrate 4500k']), + outputOptions: expect.arrayContaining(['-b:v', '3104k', '-minrate', '1552k', '-maxrate', '4500k']), twoPass: true, }), ); @@ -2381,7 +2406,7 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-cpu-used 2']), + outputOptions: expect.arrayContaining(['-cpu-used', '2']), twoPass: false, }), ); @@ -2411,7 +2436,7 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-threads 2']), + outputOptions: expect.arrayContaining(['-threads', '2']), twoPass: false, }), ); @@ -2426,7 +2451,7 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-threads 1', '-x264-params frame-threads=1:pools=none']), + outputOptions: expect.arrayContaining(['-threads', '1', '-x264-params', 'frame-threads=1:pools=none']), twoPass: false, }), ); @@ -2456,7 +2481,14 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-c:v hevc', '-threads 1', '-x265-params frame-threads=1:pools=none']), + outputOptions: expect.arrayContaining([ + '-c:v', + 'hevc', + '-threads', + '1', + '-x265-params', + 'frame-threads=1:pools=none', + ]), twoPass: false, }), ); @@ -2487,15 +2519,24 @@ describe(MediaService.name, () => { expect.objectContaining({ inputOptions: expect.any(Array), outputOptions: expect.arrayContaining([ - '-c:v libsvtav1', - '-movflags faststart', - '-fps_mode passthrough', - '-map 0:0', - '-map 0:3', - '-v verbose', - '-vf scale=-2:720', - '-preset 12', - '-crf 23', + '-c:v', + 'libsvtav1', + '-movflags', + 'faststart', + '-fps_mode', + 'passthrough', + '-map', + '0:0', + '-map', + '0:3', + '-v', + 'verbose', + '-vf', + 'scale=-2:720', + '-preset', + '12', + '-crf', + '23', ]), twoPass: false, }), @@ -2511,7 +2552,7 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-preset 4']), + outputOptions: expect.arrayContaining(['-preset', '4']), twoPass: false, }), ); @@ -2526,7 +2567,7 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-svtav1-params mbr=2M']), + outputOptions: expect.arrayContaining(['-svtav1-params', 'mbr=2M']), twoPass: false, }), ); @@ -2541,7 +2582,7 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-svtav1-params lp=4']), + outputOptions: expect.arrayContaining(['-svtav1-params', 'lp=4']), twoPass: false, }), ); @@ -2558,7 +2599,7 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-svtav1-params lp=4:mbr=2M']), + outputOptions: expect.arrayContaining(['-svtav1-params', 'lp=4:mbr=2M']), twoPass: false, }), ); @@ -2615,7 +2656,7 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-c:a libopus']), + outputOptions: expect.arrayContaining(['-c:a', 'libopus']), twoPass: false, }), ); @@ -2645,23 +2686,38 @@ describe(MediaService.name, () => { '/original/path.ext', expect.any(String), expect.objectContaining({ - inputOptions: expect.arrayContaining(['-init_hw_device cuda=cuda:0', '-filter_hw_device cuda']), + inputOptions: expect.arrayContaining(['-init_hw_device', 'cuda=cuda:0', '-filter_hw_device', 'cuda']), outputOptions: expect.arrayContaining([ - '-tune hq', - '-qmin 0', - '-rc-lookahead 20', - '-i_qfactor 0.75', - `-c:v h264_nvenc`, - '-c:a copy', - '-movflags faststart', - '-fps_mode passthrough', - '-map 0:0', - '-map 0:3', - '-g 256', - '-v verbose', - '-vf hwupload_cuda,scale_cuda=-2:720:format=nv12', - '-preset p1', - '-cq:v 23', + '-tune', + 'hq', + '-qmin', + '0', + '-rc-lookahead', + '20', + '-i_qfactor', + '0.75', + '-c:v', + 'h264_nvenc', + '-c:a', + 'copy', + '-movflags', + 'faststart', + '-fps_mode', + 'passthrough', + '-map', + '0:0', + '-map', + '0:3', + '-g', + '256', + '-v', + 'verbose', + '-vf', + 'hwupload_cuda,scale_cuda=-2:720:format=nv12', + '-preset', + 'p1', + '-cq:v', + '23', ]), twoPass: false, }), @@ -2682,7 +2738,7 @@ describe(MediaService.name, () => { '/original/path.ext', expect.any(String), expect.objectContaining({ - inputOptions: expect.arrayContaining(['-init_hw_device cuda=cuda:0', '-filter_hw_device cuda']), + inputOptions: expect.arrayContaining(['-init_hw_device', 'cuda=cuda:0', '-filter_hw_device', 'cuda']), outputOptions: expect.arrayContaining([expect.stringContaining('-multipass')]), twoPass: false, }), @@ -2699,8 +2755,8 @@ describe(MediaService.name, () => { '/original/path.ext', expect.any(String), expect.objectContaining({ - inputOptions: expect.arrayContaining(['-init_hw_device cuda=cuda:0', '-filter_hw_device cuda']), - outputOptions: expect.arrayContaining(['-cq:v 23', '-maxrate 10000k', '-bufsize 6897k']), + inputOptions: expect.arrayContaining(['-init_hw_device', 'cuda=cuda:0', '-filter_hw_device', 'cuda']), + outputOptions: expect.arrayContaining(['-cq:v', '23', '-maxrate', '10000k', '-bufsize', '6897k']), twoPass: false, }), ); @@ -2716,7 +2772,7 @@ describe(MediaService.name, () => { '/original/path.ext', expect.any(String), expect.objectContaining({ - inputOptions: expect.arrayContaining(['-init_hw_device cuda=cuda:0', '-filter_hw_device cuda']), + inputOptions: expect.arrayContaining(['-init_hw_device', 'cuda=cuda:0', '-filter_hw_device', 'cuda']), outputOptions: expect.not.stringContaining('-maxrate'), twoPass: false, }), @@ -2733,7 +2789,7 @@ describe(MediaService.name, () => { '/original/path.ext', expect.any(String), expect.objectContaining({ - inputOptions: expect.arrayContaining(['-init_hw_device cuda=cuda:0', '-filter_hw_device cuda']), + inputOptions: expect.arrayContaining(['-init_hw_device', 'cuda=cuda:0', '-filter_hw_device', 'cuda']), outputOptions: expect.not.arrayContaining([expect.stringContaining('-preset')]), twoPass: false, }), @@ -2748,7 +2804,7 @@ describe(MediaService.name, () => { '/original/path.ext', expect.any(String), expect.objectContaining({ - inputOptions: expect.arrayContaining(['-init_hw_device cuda=cuda:0', '-filter_hw_device cuda']), + inputOptions: expect.arrayContaining(['-init_hw_device', 'cuda=cuda:0', '-filter_hw_device', 'cuda']), outputOptions: expect.not.arrayContaining([expect.stringContaining('-multipass')]), twoPass: false, }), @@ -2766,10 +2822,13 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.arrayContaining([ - '-hwaccel cuda', - '-hwaccel_output_format cuda', + '-hwaccel', + 'cuda', + '-hwaccel_output_format', + 'cuda', '-noautorotate', - '-threads 1', + '-threads', + '1', ]), outputOptions: expect.arrayContaining([expect.stringContaining('scale_cuda=-2:720:format=nv12')]), twoPass: false, @@ -2787,7 +2846,7 @@ describe(MediaService.name, () => { '/original/path.ext', expect.any(String), expect.objectContaining({ - inputOptions: expect.arrayContaining(['-hwaccel cuda', '-hwaccel_output_format cuda']), + inputOptions: expect.arrayContaining(['-hwaccel', 'cuda', '-hwaccel_output_format', 'cuda']), outputOptions: expect.arrayContaining([ expect.stringContaining( 'tonemap_cuda=desat=0:matrix=bt709:primaries=bt709:range=pc:tonemap=hable:tonemap_mode=lum:transfer=bt709:peak=100:format=nv12', @@ -2808,7 +2867,7 @@ describe(MediaService.name, () => { '/original/path.ext', expect.any(String), expect.objectContaining({ - inputOptions: expect.arrayContaining(['-hwaccel cuda', '-hwaccel_output_format cuda']), + inputOptions: expect.arrayContaining(['-hwaccel', 'cuda', '-hwaccel_output_format', 'cuda']), outputOptions: expect.arrayContaining([expect.stringContaining('scale_cuda=-2:720:format=nv12')]), twoPass: false, }), @@ -2826,25 +2885,42 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.arrayContaining([ - '-init_hw_device qsv=hw,child_device=/dev/dri/renderD128', - '-filter_hw_device hw', + '-init_hw_device', + 'qsv=hw,child_device=/dev/dri/renderD128', + '-filter_hw_device', + 'hw', ]), outputOptions: expect.arrayContaining([ - `-c:v h264_qsv`, - '-c:a copy', - '-movflags faststart', - '-fps_mode passthrough', - '-map 0:0', - '-map 0:3', - '-bf 7', - '-refs 5', - '-g 256', - '-v verbose', - '-vf hwupload=extra_hw_frames=64,scale_qsv=-1:720:mode=hq:format=nv12', - '-preset 7', - '-global_quality:v 23', - '-maxrate 10000k', - '-bufsize 20000k', + '-c:v', + 'h264_qsv', + '-c:a', + 'copy', + '-movflags', + 'faststart', + '-fps_mode', + 'passthrough', + '-map', + '0:0', + '-map', + '0:3', + '-bf', + '7', + '-refs', + '5', + '-g', + '256', + '-v', + 'verbose', + '-vf', + 'hwupload=extra_hw_frames=64,scale_qsv=-1:720:mode=hq:format=nv12', + '-preset', + '7', + '-global_quality:v', + '23', + '-maxrate', + '10000k', + '-bufsize', + '20000k', ]), twoPass: false, }), @@ -2866,8 +2942,10 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.arrayContaining([ - '-init_hw_device qsv=hw,child_device=/dev/dri/renderD128', - '-filter_hw_device hw', + '-init_hw_device', + 'qsv=hw,child_device=/dev/dri/renderD128', + '-filter_hw_device', + 'hw', ]), outputOptions: expect.any(Array), twoPass: false, @@ -2886,8 +2964,10 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.arrayContaining([ - '-init_hw_device qsv=hw,child_device=/dev/dri/renderD128', - '-filter_hw_device hw', + '-init_hw_device', + 'qsv=hw,child_device=/dev/dri/renderD128', + '-filter_hw_device', + 'hw', ]), outputOptions: expect.not.arrayContaining([expect.stringContaining('-preset')]), twoPass: false, @@ -2906,10 +2986,12 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.arrayContaining([ - '-init_hw_device qsv=hw,child_device=/dev/dri/renderD128', - '-filter_hw_device hw', + '-init_hw_device', + 'qsv=hw,child_device=/dev/dri/renderD128', + '-filter_hw_device', + 'hw', ]), - outputOptions: expect.arrayContaining(['-low_power 1']), + outputOptions: expect.arrayContaining(['-low_power', '1']), twoPass: false, }), ); @@ -2935,10 +3017,12 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.arrayContaining([ - '-init_hw_device qsv=hw,child_device=/dev/dri/renderD129', - '-filter_hw_device hw', + '-init_hw_device', + 'qsv=hw,child_device=/dev/dri/renderD129', + '-filter_hw_device', + 'hw', ]), - outputOptions: expect.arrayContaining([`-c:v h264_qsv`]), + outputOptions: expect.arrayContaining(['-c:v', 'h264_qsv']), twoPass: false, }), ); @@ -2957,12 +3041,17 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.arrayContaining([ - '-hwaccel qsv', - '-hwaccel_output_format qsv', - '-async_depth 4', + '-hwaccel', + 'qsv', + '-hwaccel_output_format', + 'qsv', + '-async_depth', + '4', '-noautorotate', - '-threads 1', - '-qsv_device /dev/dri/renderD128', + '-threads', + '1', + '-qsv_device', + '/dev/dri/renderD128', ]), outputOptions: expect.arrayContaining([expect.stringContaining('scale_qsv=-1:720:async_depth=4:mode=hq')]), twoPass: false, @@ -2983,10 +3072,14 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.arrayContaining([ - '-hwaccel qsv', - '-hwaccel_output_format qsv', - '-async_depth 4', - '-threads 1', + '-hwaccel', + 'qsv', + '-hwaccel_output_format', + 'qsv', + '-async_depth', + '4', + '-threads', + '1', ]), outputOptions: expect.arrayContaining([ expect.stringContaining( @@ -3010,7 +3103,7 @@ describe(MediaService.name, () => { '/original/path.ext', expect.any(String), expect.objectContaining({ - inputOptions: expect.arrayContaining(['-hwaccel qsv', '-qsv_device /dev/dri/renderD129']), + inputOptions: expect.arrayContaining(['-hwaccel', 'qsv', '-qsv_device', '/dev/dri/renderD129']), outputOptions: expect.any(Array), twoPass: false, }), @@ -3030,10 +3123,14 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.arrayContaining([ - '-hwaccel qsv', - '-hwaccel_output_format qsv', - '-async_depth 4', - '-threads 1', + '-hwaccel', + 'qsv', + '-hwaccel_output_format', + 'qsv', + '-async_depth', + '4', + '-threads', + '1', ]), outputOptions: expect.arrayContaining([expect.stringContaining('format=nv12')]), twoPass: false, @@ -3050,21 +3147,34 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.arrayContaining([ - '-init_hw_device vaapi=accel:/dev/dri/renderD128', - '-filter_hw_device accel', + '-init_hw_device', + 'vaapi=accel:/dev/dri/renderD128', + '-filter_hw_device', + 'accel', ]), outputOptions: expect.arrayContaining([ - `-c:v h264_vaapi`, - '-c:a copy', - '-movflags faststart', - '-fps_mode passthrough', - '-map 0:0', - '-map 0:3', - '-g 256', - '-v verbose', - '-vf hwupload=extra_hw_frames=64,scale_vaapi=-2:720:mode=hq:out_range=pc:format=nv12', - '-compression_level 7', - '-rc_mode 1', + '-c:v', + 'h264_vaapi', + '-c:a', + 'copy', + '-movflags', + 'faststart', + '-fps_mode', + 'passthrough', + '-map', + '0:0', + '-map', + '0:3', + '-g', + '256', + '-v', + 'verbose', + '-vf', + 'hwupload=extra_hw_frames=64,scale_vaapi=-2:720:mode=hq:out_range=pc:format=nv12', + '-compression_level', + '7', + '-rc_mode', + '1', ]), twoPass: false, }), @@ -3082,15 +3192,22 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.arrayContaining([ - '-init_hw_device vaapi=accel:/dev/dri/renderD128', - '-filter_hw_device accel', + '-init_hw_device', + 'vaapi=accel:/dev/dri/renderD128', + '-filter_hw_device', + 'accel', ]), outputOptions: expect.arrayContaining([ - `-c:v h264_vaapi`, - '-b:v 6897k', - '-maxrate 10000k', - '-minrate 3448.5k', - '-rc_mode 3', + '-c:v', + 'h264_vaapi', + '-b:v', + '6897k', + '-maxrate', + '10000k', + '-minrate', + '3448.5k', + '-rc_mode', + '3', ]), twoPass: false, }), @@ -3106,15 +3223,22 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.arrayContaining([ - '-init_hw_device vaapi=accel:/dev/dri/renderD128', - '-filter_hw_device accel', + '-init_hw_device', + 'vaapi=accel:/dev/dri/renderD128', + '-filter_hw_device', + 'accel', ]), outputOptions: expect.arrayContaining([ - `-c:v h264_vaapi`, - '-c:a copy', - '-qp:v 23', - '-global_quality:v 23', - '-rc_mode 1', + '-c:v', + 'h264_vaapi', + '-c:a', + 'copy', + '-qp:v', + '23', + '-global_quality:v', + '23', + '-rc_mode', + '1', ]), twoPass: false, }), @@ -3132,8 +3256,10 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.arrayContaining([ - '-init_hw_device vaapi=accel:/dev/dri/renderD128', - '-filter_hw_device accel', + '-init_hw_device', + 'vaapi=accel:/dev/dri/renderD128', + '-filter_hw_device', + 'accel', ]), outputOptions: expect.not.arrayContaining([expect.stringContaining('-compression_level')]), twoPass: false, @@ -3151,10 +3277,12 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.arrayContaining([ - '-init_hw_device vaapi=accel:/dev/dri/renderD129', - '-filter_hw_device accel', + '-init_hw_device', + 'vaapi=accel:/dev/dri/renderD129', + '-filter_hw_device', + 'accel', ]), - outputOptions: expect.arrayContaining([`-c:v h264_vaapi`]), + outputOptions: expect.arrayContaining(['-c:v', 'h264_vaapi']), twoPass: false, }), ); @@ -3172,10 +3300,12 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.arrayContaining([ - '-init_hw_device vaapi=accel:/dev/dri/renderD128', - '-filter_hw_device accel', + '-init_hw_device', + 'vaapi=accel:/dev/dri/renderD128', + '-filter_hw_device', + 'accel', ]), - outputOptions: expect.arrayContaining([`-c:v h264_vaapi`]), + outputOptions: expect.arrayContaining(['-c:v', 'h264_vaapi']), twoPass: false, }), ); @@ -3194,11 +3324,15 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.arrayContaining([ - '-hwaccel vaapi', - '-hwaccel_output_format vaapi', + '-hwaccel', + 'vaapi', + '-hwaccel_output_format', + 'vaapi', '-noautorotate', - '-threads 1', - '-hwaccel_device /dev/dri/renderD128', + '-threads', + '1', + '-hwaccel_device', + '/dev/dri/renderD128', ]), outputOptions: expect.arrayContaining([expect.stringContaining('scale_vaapi=-2:720:mode=hq:out_range=pc')]), twoPass: false, @@ -3218,7 +3352,14 @@ describe(MediaService.name, () => { '/original/path.ext', expect.any(String), expect.objectContaining({ - inputOptions: expect.arrayContaining(['-hwaccel vaapi', '-hwaccel_output_format vaapi', '-threads 1']), + inputOptions: expect.arrayContaining([ + '-hwaccel', + 'vaapi', + '-hwaccel_output_format', + 'vaapi', + '-threads', + '1', + ]), outputOptions: expect.arrayContaining([ expect.stringContaining( 'hwmap=derive_device=opencl,tonemap_opencl=desat=0:format=nv12:matrix=bt709:primaries=bt709:transfer=bt709:range=pc:tonemap=hable:tonemap_mode=lum:peak=100,hwmap=derive_device=vaapi:reverse=1,format=vaapi', @@ -3241,7 +3382,14 @@ describe(MediaService.name, () => { '/original/path.ext', expect.any(String), expect.objectContaining({ - inputOptions: expect.arrayContaining(['-hwaccel vaapi', '-hwaccel_output_format vaapi', '-threads 1']), + inputOptions: expect.arrayContaining([ + '-hwaccel', + 'vaapi', + '-hwaccel_output_format', + 'vaapi', + '-threads', + '1', + ]), outputOptions: expect.arrayContaining([expect.stringContaining('format=nv12')]), twoPass: false, }), @@ -3260,7 +3408,7 @@ describe(MediaService.name, () => { '/original/path.ext', expect.any(String), expect.objectContaining({ - inputOptions: expect.arrayContaining(['-hwaccel vaapi', '-hwaccel_device /dev/dri/renderD129']), + inputOptions: expect.arrayContaining(['-hwaccel', 'vaapi', '-hwaccel_device', '/dev/dri/renderD129']), outputOptions: expect.any(Array), twoPass: false, }), @@ -3280,10 +3428,12 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.arrayContaining([ - '-init_hw_device vaapi=accel:/dev/dri/renderD128', - '-filter_hw_device accel', + '-init_hw_device', + 'vaapi=accel:/dev/dri/renderD128', + '-filter_hw_device', + 'accel', ]), - outputOptions: expect.arrayContaining([`-c:v h264_vaapi`]), + outputOptions: expect.arrayContaining(['-c:v', 'h264_vaapi']), twoPass: false, }), ); @@ -3303,7 +3453,7 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-c:v h264']), + outputOptions: expect.arrayContaining(['-c:v', 'h264']), twoPass: false, }), ); @@ -3320,7 +3470,7 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-c:v h264']), + outputOptions: expect.arrayContaining(['-c:v', 'h264']), twoPass: false, }), ); @@ -3345,24 +3495,39 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.arrayContaining([ - '-hwaccel rkmpp', - '-hwaccel_output_format drm_prime', - '-afbc rga', + '-hwaccel', + 'rkmpp', + '-hwaccel_output_format', + 'drm_prime', + '-afbc', + 'rga', '-noautorotate', ]), outputOptions: expect.arrayContaining([ - `-c:v h264_rkmpp`, - '-c:a copy', - '-movflags faststart', - '-fps_mode passthrough', - '-map 0:0', - '-map 0:3', - '-g 256', - '-v verbose', - '-vf scale_rkrga=-2:720:format=nv12:afbc=1:async_depth=4', - '-level 51', - '-rc_mode CQP', - '-qp_init 23', + '-c:v', + 'h264_rkmpp', + '-c:a', + 'copy', + '-movflags', + 'faststart', + '-fps_mode', + 'passthrough', + '-map', + '0:0', + '-map', + '0:3', + '-g', + '256', + '-v', + 'verbose', + '-vf', + 'scale_rkrga=-2:720:format=nv12:afbc=1:async_depth=4', + '-level', + '51', + '-rc_mode', + 'CQP', + '-qp_init', + '23', ]), twoPass: false, }), @@ -3384,8 +3549,24 @@ describe(MediaService.name, () => { '/original/path.ext', expect.any(String), expect.objectContaining({ - inputOptions: expect.arrayContaining(['-hwaccel rkmpp', '-hwaccel_output_format drm_prime', '-afbc rga']), - outputOptions: expect.arrayContaining([`-c:v hevc_rkmpp`, '-level 153', '-rc_mode AVBR', '-b:v 10000k']), + inputOptions: expect.arrayContaining([ + '-hwaccel', + 'rkmpp', + '-hwaccel_output_format', + 'drm_prime', + '-afbc', + 'rga', + ]), + outputOptions: expect.arrayContaining([ + '-c:v', + 'hevc_rkmpp', + '-level', + '153', + '-rc_mode', + 'AVBR', + '-b:v', + '10000k', + ]), twoPass: false, }), ); @@ -3401,8 +3582,24 @@ describe(MediaService.name, () => { '/original/path.ext', expect.any(String), expect.objectContaining({ - inputOptions: expect.arrayContaining(['-hwaccel rkmpp', '-hwaccel_output_format drm_prime', '-afbc rga']), - outputOptions: expect.arrayContaining([`-c:v h264_rkmpp`, '-level 51', '-rc_mode CQP', '-qp_init 30']), + inputOptions: expect.arrayContaining([ + '-hwaccel', + 'rkmpp', + '-hwaccel_output_format', + 'drm_prime', + '-afbc', + 'rga', + ]), + outputOptions: expect.arrayContaining([ + '-c:v', + 'h264_rkmpp', + '-level', + '51', + '-rc_mode', + 'CQP', + '-qp_init', + '30', + ]), twoPass: false, }), ); @@ -3418,7 +3615,14 @@ describe(MediaService.name, () => { '/original/path.ext', expect.any(String), expect.objectContaining({ - inputOptions: expect.arrayContaining(['-hwaccel rkmpp', '-hwaccel_output_format drm_prime', '-afbc rga']), + inputOptions: expect.arrayContaining([ + '-hwaccel', + 'rkmpp', + '-hwaccel_output_format', + 'drm_prime', + '-afbc', + 'rga', + ]), outputOptions: expect.arrayContaining([ expect.stringContaining( 'scale_rkrga=-2:720:format=p010:afbc=1:async_depth=4,hwmap=derive_device=opencl:mode=read,tonemap_opencl=format=nv12:r=pc:p=bt709:t=bt709:m=bt709:tonemap=hable:desat=0:tonemap_mode=lum:peak=100,hwmap=derive_device=rkmpp:mode=write:reverse=1,format=drm_prime', @@ -3440,7 +3644,14 @@ describe(MediaService.name, () => { '/original/path.ext', expect.any(String), expect.objectContaining({ - inputOptions: expect.arrayContaining(['-hwaccel rkmpp', '-hwaccel_output_format drm_prime', '-afbc rga']), + inputOptions: expect.arrayContaining([ + '-hwaccel', + 'rkmpp', + '-hwaccel_output_format', + 'drm_prime', + '-afbc', + 'rga', + ]), outputOptions: expect.arrayContaining([ expect.stringContaining('scale_rkrga=-2:720:format=nv12:afbc=1:async_depth=4'), ]), @@ -3502,9 +3713,12 @@ describe(MediaService.name, () => { expect.objectContaining({ inputOptions: expect.any(Array), outputOptions: expect.arrayContaining([ - '-c:v h264', - '-c:a copy', - '-vf tonemapx=tonemap=hable:desat=0:p=bt709:t=bt709:m=bt709:r=pc:peak=100:format=yuv420p', + '-c:v', + 'h264', + '-c:a', + 'copy', + '-vf', + 'tonemapx=tonemap=hable:desat=0:p=bt709:t=bt709:m=bt709:r=pc:peak=100:format=yuv420p', ]), twoPass: false, }), @@ -3521,9 +3735,12 @@ describe(MediaService.name, () => { expect.objectContaining({ inputOptions: expect.any(Array), outputOptions: expect.arrayContaining([ - '-c:v h264', - '-c:a copy', - '-vf tonemapx=tonemap=hable:desat=0:p=bt709:t=bt709:m=bt709:r=pc:peak=100:format=yuv420p', + '-c:v', + 'h264', + '-c:a', + 'copy', + '-vf', + 'tonemapx=tonemap=hable:desat=0:p=bt709:t=bt709:m=bt709:r=pc:peak=100:format=yuv420p', ]), twoPass: false, }), @@ -3539,7 +3756,7 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-c:v h264', '-c:a copy', '-vf format=yuv420p']), + outputOptions: expect.arrayContaining(['-c:v', 'h264', '-c:a', 'copy', '-vf', 'format=yuv420p']), twoPass: false, }), ); @@ -3554,7 +3771,7 @@ describe(MediaService.name, () => { expect.any(String), expect.objectContaining({ inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-c:v h264', '-c:a copy', '-vf scale=-2:720,format=yuv420p']), + outputOptions: expect.arrayContaining(['-c:v', 'h264', '-c:a', 'copy', '-vf', 'scale=-2:720,format=yuv420p']), twoPass: false, }), ); @@ -3600,7 +3817,7 @@ describe(MediaService.name, () => { expect.stringContaining('video-id.mp4'), expect.objectContaining({ inputOptions: expect.any(Array), - outputOptions: expect.arrayContaining(['-c:a copy']), + outputOptions: expect.arrayContaining(['-c:a', 'copy']), twoPass: false, }), ); diff --git a/server/src/utils/media.ts b/server/src/utils/media.ts index ce185305bd..fb27223d3a 100644 --- a/server/src/utils/media.ts +++ b/server/src/utils/media.ts @@ -91,14 +91,14 @@ export class BaseConfig implements VideoCodecSWConfig { ) { const options = { inputOptions: this.getBaseInputOptions(videoStream, format), - outputOptions: [...this.getBaseOutputOptions(target, videoStream, audioStream), '-v verbose'], + outputOptions: [...this.getBaseOutputOptions(target, videoStream, audioStream), '-v', 'verbose'], twoPass: this.eligibleForTwoPass(), progress: { frameCount: videoStream.frameCount, percentInterval: 5 }, } as TranscodeCommand; if ([TranscodeTarget.All, TranscodeTarget.Video].includes(target)) { const filters = this.getFilterOptions(videoStream); if (filters.length > 0) { - options.outputOptions.push(`-vf ${filters.join(',')}`); + options.outputOptions.push('-vf', filters.join(',')); } } @@ -121,36 +121,40 @@ export class BaseConfig implements VideoCodecSWConfig { const audioCodec = [TranscodeTarget.All, TranscodeTarget.Audio].includes(target) ? this.getAudioEncoder() : 'copy'; const options = [ - `-c:v ${videoCodec}`, - `-c:a ${audioCodec}`, + '-c:v', + videoCodec, + '-c:a', + audioCodec, // Makes a second pass moving the moov atom to the // beginning of the file for improved playback speed. - '-movflags faststart', - '-fps_mode passthrough', - // explicitly selects the video stream instead of leaving it up to FFmpeg - `-map 0:${videoStream.index}`, - // Strip metadata like capture date, camera, and GPS - '-map_metadata -1', + '-movflags', + 'faststart', + '-fps_mode', + 'passthrough', + '-map', + `0:${videoStream.index}`, + '-map_metadata', + '-1', ]; if (audioStream) { - options.push(`-map 0:${audioStream.index}`); + options.push('-map', `0:${audioStream.index}`); } if (this.getBFrames() > -1) { - options.push(`-bf ${this.getBFrames()}`); + options.push('-bf', `${this.getBFrames()}`); } if (this.getRefs() > 0) { - options.push(`-refs ${this.getRefs()}`); + options.push('-refs', `${this.getRefs()}`); } if (this.getGopSize() > 0) { - options.push(`-g ${this.getGopSize()}`); + options.push('-g', `${this.getGopSize()}`); } if ( this.config.targetVideoCodec === VideoCodec.Hevc && (videoCodec !== 'copy' || videoStream.codecName === 'hevc') ) { - options.push('-tag:v hvc1'); + options.push('-tag:v', 'hvc1'); } return options; @@ -173,26 +177,32 @@ export class BaseConfig implements VideoCodecSWConfig { } getPresetOptions() { - return [`-preset ${this.config.preset}`]; + return ['-preset', this.config.preset]; } getBitrateOptions() { const bitrates = this.getBitrateDistribution(); if (this.eligibleForTwoPass()) { return [ - `-b:v ${bitrates.target}${bitrates.unit}`, - `-minrate ${bitrates.min}${bitrates.unit}`, - `-maxrate ${bitrates.max}${bitrates.unit}`, + '-b:v', + `${bitrates.target}${bitrates.unit}`, + '-minrate', + `${bitrates.min}${bitrates.unit}`, + '-maxrate', + `${bitrates.max}${bitrates.unit}`, ]; } else if (bitrates.max > 0) { // -bufsize is the peak possible bitrate at any moment, while -maxrate is the max rolling average bitrate return [ - `-${this.useCQP() ? 'q:v' : 'crf'} ${this.config.crf}`, - `-maxrate ${bitrates.max}${bitrates.unit}`, - `-bufsize ${bitrates.max * 2}${bitrates.unit}`, + `-${this.useCQP() ? 'q:v' : 'crf'}`, + `${this.config.crf}`, + '-maxrate', + `${bitrates.max}${bitrates.unit}`, + '-bufsize', + `${bitrates.max * 2}${bitrates.unit}`, ]; } else { - return [`-${this.useCQP() ? 'q:v' : 'crf'} ${this.config.crf}`]; + return [`-${this.useCQP() ? 'q:v' : 'crf'}`, `${this.config.crf}`]; } } @@ -204,7 +214,7 @@ export class BaseConfig implements VideoCodecSWConfig { if (this.config.threads <= 0) { return []; } - return [`-threads ${this.config.threads}`]; + return ['-threads', `${this.config.threads}`]; } eligibleForTwoPass() { @@ -395,8 +405,8 @@ export class ThumbnailConfig extends BaseConfig { // skip_frame nointra skips all frames for some MPEG-TS files. Look at ffmpeg tickets 7950 and 7895 for more details. const options = format?.formatName === 'mpegts' - ? ['-sws_flags accurate_rnd+full_chroma_int'] - : ['-skip_frame nointra', '-sws_flags accurate_rnd+full_chroma_int']; + ? ['-sws_flags', 'accurate_rnd+full_chroma_int'] + : ['-skip_frame', 'nointra', '-sws_flags', 'accurate_rnd+full_chroma_int']; const metadataOverrides = []; if (videoStream.colorPrimaries === 'reserved') { @@ -413,14 +423,14 @@ export class ThumbnailConfig extends BaseConfig { if (metadataOverrides.length > 0) { // workaround for https://fftrac-bg.ffmpeg.org/ticket/11020 - options.push(`-bsf:v ${videoStream.codecName}_metadata=${metadataOverrides.join(':')}`); + options.push('-bsf:v', `${videoStream.codecName}_metadata=${metadataOverrides.join(':')}`); } return options; } getBaseOutputOptions() { - return ['-fps_mode vfr', '-frames:v 1', '-update 1']; + return ['-fps_mode', 'vfr', '-frames:v', '1', '-update', '1']; } getFilterOptions(videoStream: VideoStreamInfo): string[] { @@ -455,7 +465,7 @@ export class H264Config extends BaseConfig { getOutputThreadOptions() { const options = super.getOutputThreadOptions(); if (this.config.threads === 1) { - options.push('-x264-params frame-threads=1:pools=none'); + options.push('-x264-params', 'frame-threads=1:pools=none'); } return options; @@ -466,7 +476,7 @@ export class HEVCConfig extends BaseConfig { getOutputThreadOptions() { const options = super.getOutputThreadOptions(); if (this.config.threads === 1) { - options.push('-x265-params frame-threads=1:pools=none'); + options.push('-x265-params', 'frame-threads=1:pools=none'); } return options; @@ -477,7 +487,7 @@ export class VP9Config extends BaseConfig { getPresetOptions() { const speed = Math.min(this.getPresetIndex(), 5); // values over 5 require realtime mode, which is its own can of worms since it overrides -crf and -threads if (speed >= 0) { - return [`-cpu-used ${speed}`]; + return ['-cpu-used', `${speed}`]; } return []; } @@ -486,17 +496,20 @@ export class VP9Config extends BaseConfig { const bitrates = this.getBitrateDistribution(); if (bitrates.max > 0 && this.eligibleForTwoPass()) { return [ - `-b:v ${bitrates.target}${bitrates.unit}`, - `-minrate ${bitrates.min}${bitrates.unit}`, - `-maxrate ${bitrates.max}${bitrates.unit}`, + '-b:v', + `${bitrates.target}${bitrates.unit}`, + '-minrate', + `${bitrates.min}${bitrates.unit}`, + '-maxrate', + `${bitrates.max}${bitrates.unit}`, ]; } - return [`-${this.useCQP() ? 'q:v' : 'crf'} ${this.config.crf}`, `-b:v ${bitrates.max}${bitrates.unit}`]; + return [`-${this.useCQP() ? 'q:v' : 'crf'}`, `${this.config.crf}`, '-b:v', `${bitrates.max}${bitrates.unit}`]; } getOutputThreadOptions() { - return ['-row-mt 1', ...super.getOutputThreadOptions()]; + return ['-row-mt', '1', ...super.getOutputThreadOptions()]; } eligibleForTwoPass() { @@ -512,13 +525,13 @@ export class AV1Config extends BaseConfig { getPresetOptions() { const speed = this.getPresetIndex() + 4; // Use 4 as slowest, giving us an effective range of 4-12 which is far more useful than 0-8 if (speed >= 0) { - return [`-preset ${speed}`]; + return ['-preset', `${speed}`]; } return []; } getBitrateOptions() { - const options = [`-crf ${this.config.crf}`]; + const options = ['-crf', `${this.config.crf}`]; const bitrates = this.getBitrateDistribution(); const svtparams = []; if (this.config.threads > 0) { @@ -528,7 +541,7 @@ export class AV1Config extends BaseConfig { svtparams.push(`mbr=${bitrates.max}${bitrates.unit}`); } if (svtparams.length > 0) { - options.push(`-svtav1-params ${svtparams.join(':')}`); + options.push('-svtav1-params', svtparams.join(':')); } return options; } @@ -552,23 +565,27 @@ export class NvencSwDecodeConfig extends BaseHWConfig { } getBaseInputOptions() { - return [`-init_hw_device cuda=cuda:${this.device}`, '-filter_hw_device cuda']; + return ['-init_hw_device', `cuda=cuda:${this.device}`, '-filter_hw_device', 'cuda']; } getBaseOutputOptions(target: TranscodeTarget, videoStream: VideoStreamInfo, audioStream?: AudioStreamInfo) { const options = [ // below settings recommended from https://docs.nvidia.com/video-technologies/video-codec-sdk/12.0/ffmpeg-with-nvidia-gpu/index.html#command-line-for-latency-tolerant-high-quality-transcoding - '-tune hq', - '-qmin 0', - '-rc-lookahead 20', - '-i_qfactor 0.75', + '-tune', + 'hq', + '-qmin', + '0', + '-rc-lookahead', + '20', + '-i_qfactor', + '0.75', ...super.getBaseOutputOptions(target, videoStream, audioStream), ]; if (this.getBFrames() > 0) { - options.push('-b_ref_mode middle', '-b_qfactor 1.1'); + options.push('-b_ref_mode', 'middle', '-b_qfactor', '1.1'); } if (this.config.temporalAQ) { - options.push('-temporal-aq 1'); + options.push('-temporal-aq', '1'); } return options; } @@ -589,26 +606,33 @@ export class NvencSwDecodeConfig extends BaseHWConfig { return []; } presetIndex = 7 - Math.min(6, presetIndex); // map to p1-p7; p7 is the highest quality, so reverse index - return [`-preset p${presetIndex}`]; + return ['-preset', `p${presetIndex}`]; } getBitrateOptions() { const bitrates = this.getBitrateDistribution(); if (bitrates.max > 0 && this.config.twoPass) { return [ - `-b:v ${bitrates.target}${bitrates.unit}`, - `-maxrate ${bitrates.max}${bitrates.unit}`, - `-bufsize ${bitrates.target}${bitrates.unit}`, - '-multipass 2', + '-b:v', + `${bitrates.target}${bitrates.unit}`, + '-maxrate', + `${bitrates.max}${bitrates.unit}`, + '-bufsize', + `${bitrates.target}${bitrates.unit}`, + '-multipass', + '2', ]; } else if (bitrates.max > 0) { return [ - `-cq:v ${this.config.crf}`, - `-maxrate ${bitrates.max}${bitrates.unit}`, - `-bufsize ${bitrates.target}${bitrates.unit}`, + '-cq:v', + `${this.config.crf}`, + '-maxrate', + `${bitrates.max}${bitrates.unit}`, + '-bufsize', + `${bitrates.target}${bitrates.unit}`, ]; } else { - return [`-cq:v ${this.config.crf}`]; + return ['-cq:v', `${this.config.crf}`]; } } @@ -627,7 +651,7 @@ export class NvencSwDecodeConfig extends BaseHWConfig { export class NvencHwDecodeConfig extends NvencSwDecodeConfig { getBaseInputOptions() { - return ['-hwaccel cuda', '-hwaccel_output_format cuda', '-noautorotate', ...this.getInputThreadOptions()]; + return ['-hwaccel', 'cuda', '-hwaccel_output_format', 'cuda', '-noautorotate', ...this.getInputThreadOptions()]; } getFilterOptions(videoStream: VideoStreamInfo) { @@ -664,7 +688,7 @@ export class NvencHwDecodeConfig extends NvencSwDecodeConfig { } getInputThreadOptions() { - return [`-threads 1`]; + return ['-threads', '1']; } getOutputThreadOptions() { @@ -674,14 +698,14 @@ export class NvencHwDecodeConfig extends NvencSwDecodeConfig { export class QsvSwDecodeConfig extends BaseHWConfig { getBaseInputOptions() { - return [`-init_hw_device qsv=hw,child_device=${this.device}`, '-filter_hw_device hw']; + return ['-init_hw_device', `qsv=hw,child_device=${this.device}`, '-filter_hw_device', 'hw']; } getBaseOutputOptions(target: TranscodeTarget, videoStream: VideoStreamInfo, audioStream?: AudioStreamInfo) { const options = super.getBaseOutputOptions(target, videoStream, audioStream); // VP9 requires enabling low power mode https://git.ffmpeg.org/gitweb/ffmpeg.git/commit/33583803e107b6d532def0f9d949364b01b6ad5a if (this.config.targetVideoCodec === VideoCodec.Vp9) { - options.push('-low_power 1'); + options.push('-low_power', '1'); } return options; } @@ -701,14 +725,14 @@ export class QsvSwDecodeConfig extends BaseHWConfig { return []; } presetIndex = Math.min(6, presetIndex) + 1; // 1 to 7 - return [`-preset ${presetIndex}`]; + return ['-preset', `${presetIndex}`]; } getBitrateOptions() { - const options = [`-${this.useCQP() ? 'q:v' : 'global_quality:v'} ${this.config.crf}`]; + const options = [`-${this.useCQP() ? 'q:v' : 'global_quality:v'}`, `${this.config.crf}`]; const bitrates = this.getBitrateDistribution(); if (bitrates.max > 0) { - options.push(`-maxrate ${bitrates.max}${bitrates.unit}`, `-bufsize ${bitrates.max * 2}${bitrates.unit}`); + options.push('-maxrate', `${bitrates.max}${bitrates.unit}`, '-bufsize', `${bitrates.max * 2}${bitrates.unit}`); } return options; } @@ -744,11 +768,15 @@ export class QsvSwDecodeConfig extends BaseHWConfig { export class QsvHwDecodeConfig extends QsvSwDecodeConfig { getBaseInputOptions() { return [ - '-hwaccel qsv', - '-hwaccel_output_format qsv', - '-async_depth 4', + '-hwaccel', + 'qsv', + '-hwaccel_output_format', + 'qsv', + '-async_depth', + '4', '-noautorotate', - `-qsv_device ${this.device}`, + '-qsv_device', + this.device, ...this.getInputThreadOptions(), ]; } @@ -791,13 +819,13 @@ export class QsvHwDecodeConfig extends QsvSwDecodeConfig { } getInputThreadOptions() { - return [`-threads 1`]; + return ['-threads', '1']; } } export class VaapiSwDecodeConfig extends BaseHWConfig { getBaseInputOptions() { - return [`-init_hw_device vaapi=accel:${this.device}`, '-filter_hw_device accel']; + return ['-init_hw_device', `vaapi=accel:${this.device}`, '-filter_hw_device', 'accel']; } getFilterOptions(videoStream: VideoStreamInfo) { @@ -816,7 +844,7 @@ export class VaapiSwDecodeConfig extends BaseHWConfig { return []; } presetIndex = Math.min(6, presetIndex) + 1; // 1 to 7 - return [`-compression_level ${presetIndex}`]; + return ['-compression_level', `${presetIndex}`]; } getBitrateOptions() { @@ -824,21 +852,25 @@ export class VaapiSwDecodeConfig extends BaseHWConfig { const options = []; if (this.config.targetVideoCodec === VideoCodec.Vp9) { - options.push('-bsf:v vp9_raw_reorder,vp9_superframe'); + options.push('-bsf:v', 'vp9_raw_reorder,vp9_superframe'); } // VAAPI doesn't allow setting both quality and max bitrate if (bitrates.max > 0) { options.push( - `-b:v ${bitrates.target}${bitrates.unit}`, - `-maxrate ${bitrates.max}${bitrates.unit}`, - `-minrate ${bitrates.min}${bitrates.unit}`, - '-rc_mode 3', + '-b:v', + `${bitrates.target}${bitrates.unit}`, + '-maxrate', + `${bitrates.max}${bitrates.unit}`, + '-minrate', + `${bitrates.min}${bitrates.unit}`, + '-rc_mode', + '3', ); // variable bitrate } else if (this.useCQP()) { - options.push(`-qp:v ${this.config.crf}`, `-global_quality:v ${this.config.crf}`, '-rc_mode 1'); + options.push('-qp:v', `${this.config.crf}`, '-global_quality:v', `${this.config.crf}`, '-rc_mode', '1'); } else { - options.push(`-global_quality:v ${this.config.crf}`, '-rc_mode 4'); + options.push('-global_quality:v', `${this.config.crf}`, '-rc_mode', '4'); } return options; @@ -856,10 +888,13 @@ export class VaapiSwDecodeConfig extends BaseHWConfig { export class VaapiHwDecodeConfig extends VaapiSwDecodeConfig { getBaseInputOptions() { return [ - '-hwaccel vaapi', - '-hwaccel_output_format vaapi', + '-hwaccel', + 'vaapi', + '-hwaccel_output_format', + 'vaapi', '-noautorotate', - `-hwaccel_device ${this.device}`, + '-hwaccel_device', + this.device, ...this.getInputThreadOptions(), ]; } @@ -902,7 +937,7 @@ export class VaapiHwDecodeConfig extends VaapiSwDecodeConfig { } getInputThreadOptions() { - return [`-threads 1`]; + return ['-threads', '1']; } } @@ -919,11 +954,11 @@ export class RkmppSwDecodeConfig extends BaseHWConfig { switch (this.config.targetVideoCodec) { case VideoCodec.H264: { // from ffmpeg_mpp help, commonly referred to as H264 level 5.1 - return ['-level 51']; + return ['-level', '51']; } case VideoCodec.Hevc: { // from ffmpeg_mpp help, commonly referred to as HEVC level 5.1 - return ['-level 153']; + return ['-level', '153']; } default: { throw new Error(`Incompatible video codec for RKMPP: ${this.config.targetVideoCodec}`); @@ -935,10 +970,10 @@ export class RkmppSwDecodeConfig extends BaseHWConfig { const bitrate = this.getMaxBitrateValue(); if (bitrate > 0) { // -b:v specifies max bitrate, average bitrate is derived automatically... - return ['-rc_mode AVBR', `-b:v ${bitrate}${this.getBitrateUnit()}`]; + return ['-rc_mode', 'AVBR', '-b:v', `${bitrate}${this.getBitrateUnit()}`]; } // use CRF value as QP value - return ['-rc_mode CQP', `-qp_init ${this.config.crf}`]; + return ['-rc_mode', 'CQP', '-qp_init', `${this.config.crf}`]; } getSupportedCodecs() { @@ -952,7 +987,7 @@ export class RkmppSwDecodeConfig extends BaseHWConfig { export class RkmppHwDecodeConfig extends RkmppSwDecodeConfig { getBaseInputOptions() { - return ['-hwaccel rkmpp', '-hwaccel_output_format drm_prime', '-afbc rga', '-noautorotate']; + return ['-hwaccel', 'rkmpp', '-hwaccel_output_format', 'drm_prime', '-afbc', 'rga', '-noautorotate']; } getFilterOptions(videoStream: VideoStreamInfo) {