Skip to content
Snippets Groups Projects
Commit 2a079061 authored by Sybren A. Stüvel's avatar Sybren A. Stüvel
Browse files

Pass FFmpeg binary as `ffmpeg_cmd` and split the command

This is for consistency with the `blender_cmd` setting of Blender render
commands. The splitting is done so that extra CLI commands can be given
in the Manager config.
parent 9d793b41
No related branches found
No related tags found
No related merge requests found
...@@ -905,13 +905,16 @@ class CreateVideoCommand(AbstractSubprocessCommand): ...@@ -905,13 +905,16 @@ class CreateVideoCommand(AbstractSubprocessCommand):
max_b_frames: typing.Optional[int] = 0 max_b_frames: typing.Optional[int] = 0
def validate(self, settings: Settings) -> typing.Optional[str]: def validate(self, settings: Settings) -> typing.Optional[str]:
# Check that FFmpeg can be found. # Check that FFmpeg can be found and shlex-split the string.
ffmpeg, err = self._setting(settings, 'ffmpeg', is_required=False, default='ffmpeg') ffmpeg_cmd, err = self._setting(settings, 'ffmpeg_cmd', is_required=False, default='ffmpeg')
if err: if err:
return err return err
executable_path: typing.Optional[str] = shutil.which(ffmpeg)
cmd = shlex.split(ffmpeg_cmd)
executable_path: typing.Optional[str] = shutil.which(cmd[0])
if not executable_path: if not executable_path:
return f'FFmpeg command {ffmpeg!r} not found on $PATH' return f'FFmpeg command {ffmpeg_cmd!r} not found on $PATH'
settings['ffmpeg_cmd'] = cmd
self._log.debug('Found FFmpeg command at %r', executable_path) self._log.debug('Found FFmpeg command at %r', executable_path)
# Check that we know our input and output image files. # Check that we know our input and output image files.
...@@ -935,14 +938,17 @@ class CreateVideoCommand(AbstractSubprocessCommand): ...@@ -935,14 +938,17 @@ class CreateVideoCommand(AbstractSubprocessCommand):
await self.subprocess(cmd) await self.subprocess(cmd)
def _build_ffmpeg_command(self, settings) -> typing.List[str]: def _build_ffmpeg_command(self, settings) -> typing.List[str]:
assert isinstance(settings['ffmpeg_cmd'], list), \
'run validate() before _build_ffmpeg_command'
cmd = [ cmd = [
settings['ffmpeg'], *settings['ffmpeg_cmd'],
'-pattern_type', 'glob', '-pattern_type', 'glob',
'-i', settings['input_files'], '-i', settings['input_files'],
'-c:v', self.codec_video, '-c:v', self.codec_video,
'-crf', str(self.constant_rate_factor), '-crf', str(self.constant_rate_factor),
'-g', str(self.keyframe_interval), '-g', str(self.keyframe_interval),
'-r', str(settings['fps']), '-r', str(settings['fps']),
'-y',
] ]
if self.max_b_frames is not None: if self.max_b_frames is not None:
cmd.extend(['-bf', str(self.max_b_frames)]) cmd.extend(['-bf', str(self.max_b_frames)])
......
...@@ -14,7 +14,7 @@ log = logging.getLogger(__name__) ...@@ -14,7 +14,7 @@ log = logging.getLogger(__name__)
class BlenderRenderTest(AbstractCommandTest): class BlenderRenderTest(AbstractCommandTest):
settings: typing.Dict[str, typing.Any] = { settings: typing.Dict[str, typing.Any] = {
'ffmpeg': sys.executable, 'ffmpeg_cmd': f'"{sys.executable}" -hide_banner',
'input_files': '/tmp/*.png', 'input_files': '/tmp/*.png',
'output_file': '/tmp/merged.mkv', 'output_file': '/tmp/merged.mkv',
'fps': 24, 'fps': 24,
...@@ -30,31 +30,36 @@ class BlenderRenderTest(AbstractCommandTest): ...@@ -30,31 +30,36 @@ class BlenderRenderTest(AbstractCommandTest):
task_id='12345', task_id='12345',
command_idx=0, command_idx=0,
) )
self.settings = self.settings.copy()
def test_validate(self): def test_validate(self):
self.assertIn('not found on $PATH', self.cmd.validate({'ffmpeg': '/does/not/exist'})) self.assertIn('not found on $PATH', self.cmd.validate({'ffmpeg_cmd': '/does/not/exist'}))
self.assertIsNone(self.cmd.validate(self.settings)) self.assertIsNone(self.cmd.validate(self.settings))
def test_validate_without_ffmpeg(self): def test_validate_without_ffmpeg(self):
settings = self.settings.copy() settings = self.settings.copy()
del settings['ffmpeg'] del settings['ffmpeg_cmd']
self.assertIsNone(self.cmd.validate(settings)) self.assertIsNone(self.cmd.validate(settings))
self.assertEqual('ffmpeg', settings['ffmpeg'], self.assertEqual(['ffmpeg'], settings['ffmpeg_cmd'],
'The default setting should be stored in the dict after validation') 'The default setting should be stored in the dict after validation')
def test_build_ffmpeg_cmd(self): def test_build_ffmpeg_cmd(self):
self.cmd.validate(self.settings)
cliargs = self.cmd._build_ffmpeg_command(self.settings)
self.assertEqual([ self.assertEqual([
sys.executable, sys.executable, '-hide_banner',
'-pattern_type', 'glob', '-pattern_type', 'glob',
'-i', '/tmp/*.png', '-i', '/tmp/*.png',
'-c:v', 'h264', '-c:v', 'h264',
'-crf', '17', '-crf', '17',
'-g', '1', '-g', '1',
'-r', '24', '-r', '24',
'-y',
'-bf', '0', '-bf', '0',
'/tmp/merged.mkv', '/tmp/merged.mkv',
], self.cmd._build_ffmpeg_command(self.settings)) ], cliargs)
def test_run_ffmpeg(self): def test_run_ffmpeg(self):
with tempfile.TemporaryDirectory() as tempdir: with tempfile.TemporaryDirectory() as tempdir:
...@@ -62,7 +67,7 @@ class BlenderRenderTest(AbstractCommandTest): ...@@ -62,7 +67,7 @@ class BlenderRenderTest(AbstractCommandTest):
frame_dir = Path(__file__).with_name('test_frames') frame_dir = Path(__file__).with_name('test_frames')
settings: typing.Dict[str, typing.Any] = { settings: typing.Dict[str, typing.Any] = {
**self.settings, **self.settings,
'ffmpeg': 'ffmpeg', # use the real FFmpeg for this test. 'ffmpeg_cmd': 'ffmpeg', # use the real FFmpeg for this test.
'input_files': f'{frame_dir}/*.png', 'input_files': f'{frame_dir}/*.png',
'output_file': str(outfile), 'output_file': str(outfile),
} }
...@@ -77,5 +82,6 @@ class BlenderRenderTest(AbstractCommandTest): ...@@ -77,5 +82,6 @@ class BlenderRenderTest(AbstractCommandTest):
log.debug('Running %s', ' '.join(shlex.quote(arg) for arg in ffprobe_cmd)) log.debug('Running %s', ' '.join(shlex.quote(arg) for arg in ffprobe_cmd))
probe_out = subprocess.check_output(ffprobe_cmd) probe_out = subprocess.check_output(ffprobe_cmd)
probed_duration = float(probe_out) probed_duration = float(probe_out)
expect_duration = len(list(frame_dir.glob('*.png'))) / settings['fps'] fps: int = settings['fps']
expect_duration = len(list(frame_dir.glob('*.png'))) / fps
self.assertAlmostEqual(expect_duration, probed_duration, places=3) self.assertAlmostEqual(expect_duration, probed_duration, places=3)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment