From 533cf9abb74ff1b4f05837f0ea7749724e7aea36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= <sybren@stuvel.eu> Date: Thu, 31 Jan 2019 11:35:13 +0100 Subject: [PATCH] Added 'EXR sequence to JPEG' command This is used for generating JPEG previews of EXR files. It is assumed that the EXR files are named '######.exr', with any number of leading zeroes. Gaps in the frame range are supported, and will be rendered as black images. --- CHANGELOG.md | 2 + flamenco_worker/commands.py | 37 +++++++++++++ .../resources/exr_sequence_to_jpeg.py | 52 +++++++++++++++++++ setup.py | 1 + 4 files changed, 92 insertions(+) create mode 100644 flamenco_worker/resources/exr_sequence_to_jpeg.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 37b09781..1f893073 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ changed functionality, fixed bugs). - Changed how progressive rendering works. Nonuniform tasks are now supported. This requires Flamenco Server 2.2 or newer. Progressive render jobs generated by older versions of Flamenco Server are no longer supported. +- Added the `exr_sequence_to_jpeg` command. This command uses Blender to convert a sequence of + EXR files to JPEG files. This is used in progressive rendering to get intermediary previews. ## Version 2.2.1 (2019-01-14) diff --git a/flamenco_worker/commands.py b/flamenco_worker/commands.py index f777ff5e..101779fd 100644 --- a/flamenco_worker/commands.py +++ b/flamenco_worker/commands.py @@ -1057,6 +1057,43 @@ class BlenderRenderAudioCommand(BlenderRenderCommand): ] +@command_executor('exr_sequence_to_jpeg') +class EXRSequenceToJPEGCommand(BlenderRenderCommand): + """Convert an EXR sequence to JPEG files. + + This assumes the EXR files are named '{frame number}.exr', where the + frame number may have any number of leading zeroes. + """ + pyscript = Path(__file__).parent / 'resources/exr_sequence_to_jpeg.py' + + def validate(self, settings: Settings) -> typing.Optional[str]: + if not self.pyscript.exists(): + raise FileNotFoundError(f'Resource script {self.pyscript} cannot be found') + + exr_directory, err = self._setting(settings, 'exr_directory', True) + if err: + return err + if not exr_directory: + return '"exr_directory" may not be empty' + + output_pattern, err = self._setting(settings, 'output_pattern', False, + default='preview-######.jpg') + if not output_pattern: + return '"output_pattern" may not be empty' + return super().validate(settings) + + async def _build_blender_cmd(self, settings) -> typing.List[str]: + cmd = await super()._build_blender_cmd(settings) + + return cmd + [ + '--python-exit-code', '32', + '--python', str(self.pyscript), + '--', + '--exr-dir', settings['exr_directory'], + '--output-pattern', settings['output_pattern'], + ] + + class AbstractFFmpegCommand(AbstractSubprocessCommand, abc.ABC): index_file: typing.Optional[pathlib.Path] = None diff --git a/flamenco_worker/resources/exr_sequence_to_jpeg.py b/flamenco_worker/resources/exr_sequence_to_jpeg.py new file mode 100644 index 00000000..36466f9c --- /dev/null +++ b/flamenco_worker/resources/exr_sequence_to_jpeg.py @@ -0,0 +1,52 @@ +# This file is supposed to run inside Blender: +# blender thefile.blend --python /path/to/exr_sequence_to_jpeg.py -- --exr-dir /path/to/exr/files + +import argparse +import pathlib +import sys + +import bpy + +# Find the EXR files to process. +dashdash_index = sys.argv.index('--') +parser = argparse.ArgumentParser() +parser.add_argument('--exr-dir') +parser.add_argument('--output-pattern') +cli_args, _ = parser.parse_known_args(sys.argv[dashdash_index + 1:]) + +imgdir = pathlib.Path(cli_args.exr_dir) +exr_files = list(imgdir.glob('*.exr')) + +if not exr_files: + raise ValueError(f'No *.exr files found in {cli_args.exr_dir}') + +# Create a copy of the scene without data, so we can fill the sequence editor +# with an image sequence. +bpy.ops.scene.new(type='EMPTY') + +scene = bpy.context.scene +se = scene.sequence_editor_create() + +# Place files at the correct frame, based on their filename. +# This makes the rendering consistent w.r.t. gaps in the frames. +# This assumes the files are named '000020.exr' etc. +min_frame = float('inf') +max_frame = float('-inf') +for file in exr_files: + frame_num = int(file.stem, 10) + min_frame = min(min_frame, frame_num) + max_frame = max(max_frame, frame_num) + se.sequences.new_image(file.name, str(file), 1, frame_num) + +scene.frame_start = min_frame +scene.frame_end = max_frame + +render = scene.render +render.use_sequencer = True +render.filepath = str(imgdir / cli_args.output_pattern) +render.image_settings.file_format = 'JPEG' +render.image_settings.quality = 90 +render.use_overwrite = True # overwrite lesser quality previews + +bpy.ops.render.render(animation=True, use_viewport=False) +bpy.ops.wm.quit_blender() diff --git a/setup.py b/setup.py index 7b01c689..9255a8e5 100755 --- a/setup.py +++ b/setup.py @@ -57,6 +57,7 @@ class ZipCommand(Command): add_to_root(Path('README.md')) add_to_root(Path('CHANGELOG.md')) add_to_root(Path('flamenco_worker/resources/merge-exr.blend')) + add_to_root(Path('flamenco_worker/resources/exr_sequence_to_jpeg.py')) paths = collections.deque([Path('system-integration')]) while paths: -- GitLab