diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index adb9bb2c3ad..ee9a0f08de1 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -533,7 +533,7 @@ def convert_input_paths_for_ffmpeg( output_dir, logger=None ): - """Contert source file to format supported in ffmpeg. + """Convert source file to format supported in ffmpeg. Currently can convert only exrs. The input filepaths should be files with same type. Information about input is loaded only from first found diff --git a/openpype/plugins/publish/extract_jpeg_exr.py b/openpype/plugins/publish/extract_jpeg_exr.py index ae29f8b95b6..30176566211 100644 --- a/openpype/plugins/publish/extract_jpeg_exr.py +++ b/openpype/plugins/publish/extract_jpeg_exr.py @@ -3,22 +3,20 @@ import pyblish.api from openpype.lib import ( get_ffmpeg_tool_path, + get_oiio_tools_path, + is_oiio_supported, run_subprocess, path_to_subprocess_arg, - get_transcode_temp_directory, - convert_input_paths_for_ffmpeg, - should_convert_for_ffmpeg + execute, ) -import shutil - -class ExtractJpegEXR(pyblish.api.InstancePlugin): +class ExtractThumbnail(pyblish.api.InstancePlugin): """Create jpg thumbnail from sequence using ffmpeg""" - label = "Extract Jpeg EXR" + label = "Extract Thumbnail" order = pyblish.api.ExtractorOrder families = [ "imagesequence", "render", "render2d", @@ -49,7 +47,6 @@ def process(self, instance): return filtered_repres = self._get_filtered_repres(instance) - for repre in filtered_repres: repre_files = repre["files"] if not isinstance(repre_files, (list, tuple)): @@ -62,100 +59,50 @@ def process(self, instance): full_input_path = os.path.join(stagingdir, input_file) self.log.info("input {}".format(full_input_path)) - - do_convert = should_convert_for_ffmpeg(full_input_path) - # If result is None the requirement of conversion can't be - # determined - if do_convert is None: - self.log.info(( - "Can't determine if representation requires conversion." - " Skipped." - )) - continue - - # Do conversion if needed - # - change staging dir of source representation - # - must be set back after output definitions processing - convert_dir = None - if do_convert: - convert_dir = get_transcode_temp_directory() - filename = os.path.basename(full_input_path) - convert_input_paths_for_ffmpeg( - [full_input_path], - convert_dir, - self.log - ) - full_input_path = os.path.join(convert_dir, filename) - filename = os.path.splitext(input_file)[0] if not filename.endswith('.'): filename += "." jpeg_file = filename + "jpg" full_output_path = os.path.join(stagingdir, jpeg_file) - self.log.info("output {}".format(full_output_path)) - - ffmpeg_path = get_ffmpeg_tool_path("ffmpeg") - ffmpeg_args = self.ffmpeg_args or {} - - jpeg_items = [] - jpeg_items.append(path_to_subprocess_arg(ffmpeg_path)) - # override file if already exists - jpeg_items.append("-y") - # use same input args like with mov - jpeg_items.extend(ffmpeg_args.get("input") or []) - # input file - jpeg_items.append("-i {}".format( - path_to_subprocess_arg(full_input_path) - )) - # output arguments from presets - jpeg_items.extend(ffmpeg_args.get("output") or []) - - # If its a movie file, we just want one frame. - if repre["ext"] == "mov": - jpeg_items.append("-vframes 1") - - # output file - jpeg_items.append(path_to_subprocess_arg(full_output_path)) - - subprocess_command = " ".join(jpeg_items) - - # run subprocess - self.log.debug("{}".format(subprocess_command)) - try: # temporary until oiiotool is supported cross platform - run_subprocess( - subprocess_command, shell=True, logger=self.log - ) - except RuntimeError as exp: - if "Compression" in str(exp): - self.log.debug( - "Unsupported compression on input files. Skipping!!!" - ) - return - self.log.warning("Conversion crashed", exc_info=True) - raise + thumbnail_created = False + # Try to use FFMPEG if OIIO is not supported (for cases when + # oiiotool isn't available) + if not is_oiio_supported(): + thumbnail_created = self.create_thumbnail_ffmpeg(full_input_path, full_output_path) # noqa + else: + # Check if the file can be read by OIIO + oiio_tool_path = get_oiio_tools_path() + args = [ + oiio_tool_path, "--info", "-i", full_output_path + ] + returncode = execute(args, silent=True) + # If the input can read by OIIO then use OIIO method for + # conversion otherwise use ffmpeg + if returncode == 0: + self.log.info("Input can be read by OIIO, converting with oiiotool now.") # noqa + thumbnail_created = self.create_thumbnail_oiio(full_input_path, full_output_path) # noqa + else: + self.log.info("Converting with FFMPEG because input can't be read by OIIO.") # noqa + thumbnail_created = self.create_thumbnail_ffmpeg(full_input_path, full_output_path) # noqa + + # Skip the rest of the process if the thumbnail wasn't created + if not thumbnail_created: + self.log.warning("Thumbanil has not been created.") + return new_repre = { - "name": "thumbnail", - "ext": "jpg", - "files": jpeg_file, - "stagingDir": stagingdir, - "thumbnail": True, - "tags": ["thumbnail"] - } - - # adding representation - self.log.debug("Adding: {}".format(new_repre)) - instance.data["representations"].append(new_repre) - - # Cleanup temp folder - if convert_dir is not None and os.path.exists(convert_dir): - shutil.rmtree(convert_dir) - - # Create only one representation with name 'thumbnail' - # TODO maybe handle way how to decide from which representation - # will be thumbnail created - break + "name": "thumbnail", + "ext": "jpg", + "files": jpeg_file, + "stagingDir": stagingdir, + "thumbnail": True, + "tags": ["thumbnail"] + } + + # adding representation + self.log.debug("Adding: {}".format(new_repre)) + instance.data["representations"].append(new_repre) def _get_filtered_repres(self, instance): filtered_repres = [] @@ -175,3 +122,61 @@ def _get_filtered_repres(self, instance): filtered_repres.append(repre) return filtered_repres + + def create_thumbnail_oiio(self, src_path, dst_path): + self.log.info("outputting {}".format(dst_path)) + oiio_tool_path = get_oiio_tools_path() + oiio_cmd = [oiio_tool_path, "-a", + src_path, "-o", + dst_path + ] + subprocess_exr = " ".join(oiio_cmd) + self.log.info(f"running: {subprocess_exr}") + try: + run_subprocess(oiio_cmd, logger=self.log) + return True + except Exception: + self.log.warning( + "Failed to create thubmnail using oiiotool", + exc_info=True + ) + return False + + def create_thumbnail_ffmpeg(self, src_path, dst_path): + self.log.info("outputting {}".format(dst_path)) + + ffmpeg_path = get_ffmpeg_tool_path("ffmpeg") + ffmpeg_args = self.ffmpeg_args or {} + + jpeg_items = [] + jpeg_items.append(path_to_subprocess_arg(ffmpeg_path)) + # override file if already exists + jpeg_items.append("-y") + # flag for large file sizes + max_int = 2147483647 + jpeg_items.append("-analyzeduration {}".format(max_int)) + jpeg_items.append("-probesize {}".format(max_int)) + # use same input args like with mov + jpeg_items.extend(ffmpeg_args.get("input") or []) + # input file + jpeg_items.append("-i {}".format( + path_to_subprocess_arg(src_path) + )) + # output arguments from presets + jpeg_items.extend(ffmpeg_args.get("output") or []) + # we just want one frame from movie files + jpeg_items.append("-vframes 1") + # output file + jpeg_items.append(path_to_subprocess_arg(dst_path)) + subprocess_command = " ".join(jpeg_items) + try: + run_subprocess( + subprocess_command, shell=True, logger=self.log + ) + return True + except Exception: + self.log.warning( + "Failed to create thubmnail using ffmpeg", + exc_info=True + ) + return False diff --git a/openpype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json index 5c5a14bf210..f0b2a7e5557 100644 --- a/openpype/settings/defaults/project_settings/deadline.json +++ b/openpype/settings/defaults/project_settings/deadline.json @@ -98,4 +98,4 @@ } } } -} \ No newline at end of file +} diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 7b223798f11..cedd0eed99e 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -33,7 +33,7 @@ "enabled": false, "profiles": [] }, - "ExtractJpegEXR": { + "ExtractThumbnail": { "enabled": true, "ffmpeg_args": { "input": [ diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index 061874e31c5..a3cbf0cfcdc 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -126,8 +126,8 @@ "type": "dict", "collapsible": true, "checkbox_key": "enabled", - "key": "ExtractJpegEXR", - "label": "ExtractJpegEXR", + "key": "ExtractThumbnail", + "label": "ExtractThumbnail", "is_group": true, "children": [ {