From edc10cd85f232d132266b00dae6392ab33945b3f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 11:57:51 +0100 Subject: [PATCH 001/227] OP-4643 - added Settings for ExtractColorTranscode --- .../defaults/project_settings/global.json | 8 +- .../schemas/schema_global_publish.json | 73 +++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 0b4e4c74e6..167f7611ce 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -70,6 +70,10 @@ "output": [] } }, + "ExtractColorTranscode": { + "enabled": true, + "profiles": [] + }, "ExtractReview": { "enabled": true, "profiles": [ @@ -442,7 +446,9 @@ "template": "{family}{Task}" }, { - "families": ["render"], + "families": [ + "render" + ], "hosts": [ "aftereffects" ], 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 5388d04bc9..46ae6ba554 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 @@ -197,6 +197,79 @@ } ] }, + { + "type": "dict", + "collapsible": true, + "key": "ExtractColorTranscode", + "label": "ExtractColorTranscode", + "checkbox_key": "enabled", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "list", + "key": "profiles", + "label": "Profiles", + "object_type": { + "type": "dict", + "children": [ + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + }, + { + "key": "hosts", + "label": "Host names", + "type": "hosts-enum", + "multiselection": true + }, + { + "key": "task_types", + "label": "Task types", + "type": "task-types-enum" + }, + { + "key": "task_names", + "label": "Task names", + "type": "list", + "object_type": "text" + }, + { + "key": "subsets", + "label": "Subset names", + "type": "list", + "object_type": "text" + }, + { + "type": "splitter" + }, + { + "key": "ext", + "label": "Output extension", + "type": "text" + }, + { + "key": "output_colorspace", + "label": "Output colorspace", + "type": "text" + }, + { + "key": "custom_tags", + "label": "Custom Tags", + "type": "list", + "object_type": "text" + } + ] + } + } + ] + }, { "type": "dict", "collapsible": true, From c9996bb0c00b2fdb3d4db964efe1933bc99438e1 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 11:58:51 +0100 Subject: [PATCH 002/227] OP-4643 - added ExtractColorTranscode Added method to convert from one colorspace to another to transcoding lib --- openpype/lib/transcoding.py | 53 ++++++++ .../publish/extract_color_transcode.py | 124 ++++++++++++++++++ 2 files changed, 177 insertions(+) create mode 100644 openpype/plugins/publish/extract_color_transcode.py diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 57279d0380..6899811ed5 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1037,3 +1037,56 @@ def convert_ffprobe_fps_to_float(value): if divisor == 0.0: return 0.0 return dividend / divisor + + +def convert_colorspace_for_input_paths( + input_paths, + output_dir, + source_color_space, + target_color_space, + logger=None +): + """Convert source files from one color space to another. + + Filenames of input files are kept so make sure that output directory + is not the same directory as input files have. + - This way it can handle gaps and can keep input filenames without handling + frame template + + Args: + input_paths (str): Paths that should be converted. It is expected that + contains single file or image sequence of samy type. + output_dir (str): Path to directory where output will be rendered. + Must not be same as input's directory. + source_color_space (str): ocio valid color space of source files + target_color_space (str): ocio valid target color space + logger (logging.Logger): Logger used for logging. + + """ + if logger is None: + logger = logging.getLogger(__name__) + + input_arg = "-i" + oiio_cmd = [ + get_oiio_tools_path(), + + # Don't add any additional attributes + "--nosoftwareattrib", + "--colorconvert", source_color_space, target_color_space + ] + for input_path in input_paths: + # Prepare subprocess arguments + + oiio_cmd.extend([ + input_arg, input_path, + ]) + + # Add last argument - path to output + base_filename = os.path.basename(input_path) + output_path = os.path.join(output_dir, base_filename) + oiio_cmd.extend([ + "-o", output_path + ]) + + logger.debug("Conversion command: {}".format(" ".join(oiio_cmd))) + run_subprocess(oiio_cmd, logger=logger) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py new file mode 100644 index 0000000000..58508ab18f --- /dev/null +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -0,0 +1,124 @@ +import pyblish.api + +from openpype.pipeline import publish +from openpype.lib import ( + + is_oiio_supported, +) + +from openpype.lib.transcoding import ( + convert_colorspace_for_input_paths, + get_transcode_temp_directory, +) + +from openpype.lib.profiles_filtering import filter_profiles + + +class ExtractColorTranscode(publish.Extractor): + """ + Extractor to convert colors from one colorspace to different. + """ + + label = "Transcode color spaces" + order = pyblish.api.ExtractorOrder + 0.01 + + optional = True + + # Configurable by Settings + profiles = None + options = None + + def process(self, instance): + if not self.profiles: + self.log.warning("No profiles present for create burnin") + return + + if "representations" not in instance.data: + self.log.warning("No representations, skipping.") + return + + if not is_oiio_supported(): + self.log.warning("OIIO not supported, no transcoding possible.") + return + + colorspace_data = instance.data.get("colorspaceData") + if not colorspace_data: + # TODO get_colorspace ?? + self.log.warning("Instance has not colorspace data, skipping") + return + source_color_space = colorspace_data["colorspace"] + + host_name = instance.context.data["hostName"] + family = instance.data["family"] + task_data = instance.data["anatomyData"].get("task", {}) + task_name = task_data.get("name") + task_type = task_data.get("type") + subset = instance.data["subset"] + + filtering_criteria = { + "hosts": host_name, + "families": family, + "task_names": task_name, + "task_types": task_type, + "subset": subset + } + profile = filter_profiles(self.profiles, filtering_criteria, + logger=self.log) + + if not profile: + self.log.info(( + "Skipped instance. None of profiles in presets are for" + " Host: \"{}\" | Families: \"{}\" | Task \"{}\"" + " | Task type \"{}\" | Subset \"{}\" " + ).format(host_name, family, task_name, task_type, subset)) + return + + self.log.debug("profile: {}".format(profile)) + + target_colorspace = profile["output_colorspace"] + if not target_colorspace: + raise RuntimeError("Target colorspace must be set") + + repres = instance.data.get("representations") or [] + for idx, repre in enumerate(repres): + self.log.debug("repre ({}): `{}`".format(idx + 1, repre["name"])) + if not self.repre_is_valid(repre): + continue + + new_staging_dir = get_transcode_temp_directory() + repre["stagingDir"] = new_staging_dir + files_to_remove = repre["files"] + if not isinstance(files_to_remove, list): + files_to_remove = [files_to_remove] + instance.context.data["cleanupFullPaths"].extend(files_to_remove) + + convert_colorspace_for_input_paths( + repre["files"], + new_staging_dir, + source_color_space, + target_colorspace, + self.log + ) + + def repre_is_valid(self, repre): + """Validation if representation should be processed. + + Args: + repre (dict): Representation which should be checked. + + Returns: + bool: False if can't be processed else True. + """ + + if "review" not in (repre.get("tags") or []): + self.log.info(( + "Representation \"{}\" don't have \"review\" tag. Skipped." + ).format(repre["name"])) + return False + + if not repre.get("files"): + self.log.warning(( + "Representation \"{}\" have empty files. Skipped." + ).format(repre["name"])) + return False + return True From 23a5f21f6b54f4f01d40c73ef5bdb032a2c7440f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 12:05:57 +0100 Subject: [PATCH 003/227] OP-4643 - extractor must run just before ExtractReview Nuke render local is set to 0.01 --- openpype/plugins/publish/extract_color_transcode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 58508ab18f..5163cd4045 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -20,7 +20,7 @@ class ExtractColorTranscode(publish.Extractor): """ label = "Transcode color spaces" - order = pyblish.api.ExtractorOrder + 0.01 + order = pyblish.api.ExtractorOrder + 0.019 optional = True From 9f8107df36dd8d281482d93fb6a0e3530d91fe91 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 14:03:22 +0100 Subject: [PATCH 004/227] OP-4643 - fix for full file paths --- .../publish/extract_color_transcode.py | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 5163cd4045..6ad7599f2c 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -1,3 +1,4 @@ +import os import pyblish.api from openpype.pipeline import publish @@ -41,13 +42,6 @@ class ExtractColorTranscode(publish.Extractor): self.log.warning("OIIO not supported, no transcoding possible.") return - colorspace_data = instance.data.get("colorspaceData") - if not colorspace_data: - # TODO get_colorspace ?? - self.log.warning("Instance has not colorspace data, skipping") - return - source_color_space = colorspace_data["colorspace"] - host_name = instance.context.data["hostName"] family = instance.data["family"] task_data = instance.data["anatomyData"].get("task", {}) @@ -82,18 +76,32 @@ class ExtractColorTranscode(publish.Extractor): repres = instance.data.get("representations") or [] for idx, repre in enumerate(repres): self.log.debug("repre ({}): `{}`".format(idx + 1, repre["name"])) - if not self.repre_is_valid(repre): + # if not self.repre_is_valid(repre): + # continue + + colorspace_data = repre.get("colorspaceData") + if not colorspace_data: + # TODO get_colorspace ?? + self.log.warning("Repre has not colorspace data, skipping") + continue + source_color_space = colorspace_data["colorspace"] + config_path = colorspace_data.get("configData", {}).get("path") + if not os.path.exists(config_path): + self.log.warning("Config file doesn't exist, skipping") continue new_staging_dir = get_transcode_temp_directory() + original_staging_dir = repre["stagingDir"] repre["stagingDir"] = new_staging_dir - files_to_remove = repre["files"] - if not isinstance(files_to_remove, list): - files_to_remove = [files_to_remove] - instance.context.data["cleanupFullPaths"].extend(files_to_remove) + files_to_convert = repre["files"] + if not isinstance(files_to_convert, list): + files_to_convert = [files_to_convert] + files_to_convert = [os.path.join(original_staging_dir, path) + for path in files_to_convert] + instance.context.data["cleanupFullPaths"].extend(files_to_convert) convert_colorspace_for_input_paths( - repre["files"], + files_to_convert, new_staging_dir, source_color_space, target_colorspace, From d588ee7ed967e34adfd4017824d9f323e2ab7ad4 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 14:04:06 +0100 Subject: [PATCH 005/227] OP-4643 - pass path for ocio config --- openpype/lib/transcoding.py | 3 +++ openpype/plugins/publish/extract_color_transcode.py | 1 + 2 files changed, 4 insertions(+) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 6899811ed5..792e8ddd1e 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1042,6 +1042,7 @@ def convert_ffprobe_fps_to_float(value): def convert_colorspace_for_input_paths( input_paths, output_dir, + config_path, source_color_space, target_color_space, logger=None @@ -1058,6 +1059,7 @@ def convert_colorspace_for_input_paths( contains single file or image sequence of samy type. output_dir (str): Path to directory where output will be rendered. Must not be same as input's directory. + config_path (str): path to OCIO config file source_color_space (str): ocio valid color space of source files target_color_space (str): ocio valid target color space logger (logging.Logger): Logger used for logging. @@ -1072,6 +1074,7 @@ def convert_colorspace_for_input_paths( # Don't add any additional attributes "--nosoftwareattrib", + "--colorconfig", config_path, "--colorconvert", source_color_space, target_color_space ] for input_path in input_paths: diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 6ad7599f2c..fdb13a47e8 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -103,6 +103,7 @@ class ExtractColorTranscode(publish.Extractor): convert_colorspace_for_input_paths( files_to_convert, new_staging_dir, + config_path, source_color_space, target_colorspace, self.log From 5ca4f825006d28bed4d0c01df71af3b3ffe21deb Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 14:15:33 +0100 Subject: [PATCH 006/227] OP-4643 - add custom_tags --- openpype/plugins/publish/extract_color_transcode.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index fdb13a47e8..ab932b2476 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -72,6 +72,7 @@ class ExtractColorTranscode(publish.Extractor): target_colorspace = profile["output_colorspace"] if not target_colorspace: raise RuntimeError("Target colorspace must be set") + custom_tags = profile["custom_tags"] repres = instance.data.get("representations") or [] for idx, repre in enumerate(repres): @@ -109,6 +110,11 @@ class ExtractColorTranscode(publish.Extractor): self.log ) + if custom_tags: + if not repre.get("custom_tags"): + repre["custom_tags"] = [] + repre["custom_tags"].extend(custom_tags) + def repre_is_valid(self, repre): """Validation if representation should be processed. From 4854b521d408d326f741cc73cda0ddc3e67795ce Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 14:18:38 +0100 Subject: [PATCH 007/227] OP-4643 - added docstring --- openpype/plugins/publish/extract_color_transcode.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index ab932b2476..88e2eed90f 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -18,6 +18,17 @@ from openpype.lib.profiles_filtering import filter_profiles class ExtractColorTranscode(publish.Extractor): """ Extractor to convert colors from one colorspace to different. + + Expects "colorspaceData" on representation. This dictionary is collected + previously and denotes that representation files should be converted. + This dict contains source colorspace information, collected by hosts. + + Target colorspace is selected by profiles in the Settings, based on: + - families + - host + - task types + - task names + - subset names """ label = "Transcode color spaces" From 39b8c111cfd418b74e0cf915590bcae2547c30ae Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 18:15:44 +0100 Subject: [PATCH 008/227] OP-4643 - updated Settings schema --- .../schemas/schema_global_publish.json | 48 +++++++++++++------ 1 file changed, 34 insertions(+), 14 deletions(-) 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 46ae6ba554..c2c911d7d6 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 @@ -246,24 +246,44 @@ "type": "list", "object_type": "text" }, + { + "type": "boolean", + "key": "delete_original", + "label": "Delete Original Representation" + }, { "type": "splitter" }, { - "key": "ext", - "label": "Output extension", - "type": "text" - }, - { - "key": "output_colorspace", - "label": "Output colorspace", - "type": "text" - }, - { - "key": "custom_tags", - "label": "Custom Tags", - "type": "list", - "object_type": "text" + "key": "outputs", + "label": "Output Definitions", + "type": "dict-modifiable", + "highlight_content": true, + "object_type": { + "type": "dict", + "children": [ + { + "key": "output_extension", + "label": "Output extension", + "type": "text" + }, + { + "key": "output_colorspace", + "label": "Output colorspace", + "type": "text" + }, + { + "type": "schema", + "name": "schema_representation_tags" + }, + { + "key": "custom_tags", + "label": "Custom Tags", + "type": "list", + "object_type": "text" + } + ] + } } ] } From a63ddebc19c7078c33b49029e5de3059b0fcdd9d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 18:17:25 +0100 Subject: [PATCH 009/227] OP-4643 - skip video files Only frames currently supported. --- .../plugins/publish/extract_color_transcode.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 88e2eed90f..a0714c9a33 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -36,6 +36,9 @@ class ExtractColorTranscode(publish.Extractor): optional = True + # Supported extensions + supported_exts = ["exr", "jpg", "jpeg", "png", "dpx"] + # Configurable by Settings profiles = None options = None @@ -88,13 +91,7 @@ class ExtractColorTranscode(publish.Extractor): repres = instance.data.get("representations") or [] for idx, repre in enumerate(repres): self.log.debug("repre ({}): `{}`".format(idx + 1, repre["name"])) - # if not self.repre_is_valid(repre): - # continue - - colorspace_data = repre.get("colorspaceData") - if not colorspace_data: - # TODO get_colorspace ?? - self.log.warning("Repre has not colorspace data, skipping") + if not self._repre_is_valid(repre): continue source_color_space = colorspace_data["colorspace"] config_path = colorspace_data.get("configData", {}).get("path") @@ -136,9 +133,9 @@ class ExtractColorTranscode(publish.Extractor): bool: False if can't be processed else True. """ - if "review" not in (repre.get("tags") or []): - self.log.info(( - "Representation \"{}\" don't have \"review\" tag. Skipped." + if repre.get("ext") not in self.supported_exts: + self.log.warning(( + "Representation \"{}\" of unsupported extension. Skipped." ).format(repre["name"])) return False From eced917d1a06fb6c973396c22c37db70bf71e4d4 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 18:19:08 +0100 Subject: [PATCH 010/227] OP-4643 - refactored profile, delete of original Implemented multiple outputs from single input representation --- .../publish/extract_color_transcode.py | 156 ++++++++++++------ 1 file changed, 109 insertions(+), 47 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index a0714c9a33..b0c851d5f4 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -1,4 +1,6 @@ import os +import copy + import pyblish.api from openpype.pipeline import publish @@ -56,13 +58,94 @@ class ExtractColorTranscode(publish.Extractor): self.log.warning("OIIO not supported, no transcoding possible.") return + profile = self._get_profile(instance) + if not profile: + return + + repres = instance.data.get("representations") or [] + for idx, repre in enumerate(list(repres)): + self.log.debug("repre ({}): `{}`".format(idx + 1, repre["name"])) + if not self._repre_is_valid(repre): + continue + + colorspace_data = repre["colorspaceData"] + source_color_space = colorspace_data["colorspace"] + config_path = colorspace_data.get("configData", {}).get("path") + if not os.path.exists(config_path): + self.log.warning("Config file doesn't exist, skipping") + continue + + repre = self._handle_original_repre(repre, profile) + + for _, output_def in profile.get("outputs", {}).items(): + new_repre = copy.deepcopy(repre) + + new_staging_dir = get_transcode_temp_directory() + original_staging_dir = new_repre["stagingDir"] + new_repre["stagingDir"] = new_staging_dir + files_to_convert = new_repre["files"] + if not isinstance(files_to_convert, list): + files_to_convert = [files_to_convert] + + files_to_delete = copy.deepcopy(files_to_convert) + + output_extension = output_def["output_extension"] + files_to_convert = self._rename_output_files(files_to_convert, + output_extension) + + files_to_convert = [os.path.join(original_staging_dir, path) + for path in files_to_convert] + + target_colorspace = output_def["output_colorspace"] + if not target_colorspace: + raise RuntimeError("Target colorspace must be set") + + convert_colorspace_for_input_paths( + files_to_convert, + new_staging_dir, + config_path, + source_color_space, + target_colorspace, + self.log + ) + + instance.context.data["cleanupFullPaths"].extend( + files_to_delete) + + custom_tags = output_def.get("custom_tags") + if custom_tags: + if not new_repre.get("custom_tags"): + new_repre["custom_tags"] = [] + new_repre["custom_tags"].extend(custom_tags) + + # Add additional tags from output definition to representation + for tag in output_def["tags"]: + if tag not in new_repre["tags"]: + new_repre["tags"].append(tag) + + instance.data["representations"].append(new_repre) + + def _rename_output_files(self, files_to_convert, output_extension): + """Change extension of converted files.""" + if output_extension: + output_extension = output_extension.replace('.', '') + renamed_files = [] + for file_name in files_to_convert: + file_name, _ = os.path.splitext(file_name) + new_file_name = '{}.{}'.format(file_name, + output_extension) + renamed_files.append(new_file_name) + files_to_convert = renamed_files + return files_to_convert + + def _get_profile(self, instance): + """Returns profile if and how repre should be color transcoded.""" host_name = instance.context.data["hostName"] family = instance.data["family"] task_data = instance.data["anatomyData"].get("task", {}) task_name = task_data.get("name") task_type = task_data.get("type") subset = instance.data["subset"] - filtering_criteria = { "hosts": host_name, "families": family, @@ -75,55 +158,15 @@ class ExtractColorTranscode(publish.Extractor): if not profile: self.log.info(( - "Skipped instance. None of profiles in presets are for" - " Host: \"{}\" | Families: \"{}\" | Task \"{}\"" - " | Task type \"{}\" | Subset \"{}\" " - ).format(host_name, family, task_name, task_type, subset)) - return + "Skipped instance. None of profiles in presets are for" + " Host: \"{}\" | Families: \"{}\" | Task \"{}\"" + " | Task type \"{}\" | Subset \"{}\" " + ).format(host_name, family, task_name, task_type, subset)) self.log.debug("profile: {}".format(profile)) + return profile - target_colorspace = profile["output_colorspace"] - if not target_colorspace: - raise RuntimeError("Target colorspace must be set") - custom_tags = profile["custom_tags"] - - repres = instance.data.get("representations") or [] - for idx, repre in enumerate(repres): - self.log.debug("repre ({}): `{}`".format(idx + 1, repre["name"])) - if not self._repre_is_valid(repre): - continue - source_color_space = colorspace_data["colorspace"] - config_path = colorspace_data.get("configData", {}).get("path") - if not os.path.exists(config_path): - self.log.warning("Config file doesn't exist, skipping") - continue - - new_staging_dir = get_transcode_temp_directory() - original_staging_dir = repre["stagingDir"] - repre["stagingDir"] = new_staging_dir - files_to_convert = repre["files"] - if not isinstance(files_to_convert, list): - files_to_convert = [files_to_convert] - files_to_convert = [os.path.join(original_staging_dir, path) - for path in files_to_convert] - instance.context.data["cleanupFullPaths"].extend(files_to_convert) - - convert_colorspace_for_input_paths( - files_to_convert, - new_staging_dir, - config_path, - source_color_space, - target_colorspace, - self.log - ) - - if custom_tags: - if not repre.get("custom_tags"): - repre["custom_tags"] = [] - repre["custom_tags"].extend(custom_tags) - - def repre_is_valid(self, repre): + def _repre_is_valid(self, repre): """Validation if representation should be processed. Args: @@ -144,4 +187,23 @@ class ExtractColorTranscode(publish.Extractor): "Representation \"{}\" have empty files. Skipped." ).format(repre["name"])) return False + + if not repre.get("colorspaceData"): + self.log.warning("Repre has not colorspace data, skipping") + return False + return True + + def _handle_original_repre(self, repre, profile): + delete_original = profile["delete_original"] + + if delete_original: + if not repre.get("tags"): + repre["tags"] = [] + + if "review" in repre["tags"]: + repre["tags"].remove("review") + if "delete" not in repre["tags"]: + repre["tags"].append("delete") + + return repre From 82a8fda4d6fd319be4f570d0731881d0effb3dac Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 18:23:01 +0100 Subject: [PATCH 011/227] OP-4643 - switched logging levels Do not use warning unnecessary. --- openpype/plugins/publish/extract_color_transcode.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index b0c851d5f4..4d38514b8b 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -47,11 +47,11 @@ class ExtractColorTranscode(publish.Extractor): def process(self, instance): if not self.profiles: - self.log.warning("No profiles present for create burnin") + self.log.debug("No profiles present for color transcode") return if "representations" not in instance.data: - self.log.warning("No representations, skipping.") + self.log.debug("No representations, skipping.") return if not is_oiio_supported(): @@ -177,19 +177,19 @@ class ExtractColorTranscode(publish.Extractor): """ if repre.get("ext") not in self.supported_exts: - self.log.warning(( + self.log.debug(( "Representation \"{}\" of unsupported extension. Skipped." ).format(repre["name"])) return False if not repre.get("files"): - self.log.warning(( + self.log.debug(( "Representation \"{}\" have empty files. Skipped." ).format(repre["name"])) return False if not repre.get("colorspaceData"): - self.log.warning("Repre has not colorspace data, skipping") + self.log.debug("Repre has no colorspace data. Skipped.") return False return True From 6f7e1c3cb49fe0162220919d398e86cddf68d0fa Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 18:46:14 +0100 Subject: [PATCH 012/227] OP-4643 - propagate new extension to representation --- .../publish/extract_color_transcode.py | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 4d38514b8b..62cf8f0dee 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -90,8 +90,13 @@ class ExtractColorTranscode(publish.Extractor): files_to_delete = copy.deepcopy(files_to_convert) output_extension = output_def["output_extension"] - files_to_convert = self._rename_output_files(files_to_convert, - output_extension) + output_extension = output_extension.replace('.', '') + if output_extension: + new_repre["name"] = output_extension + new_repre["ext"] = output_extension + + files_to_convert = self._rename_output_files( + files_to_convert, output_extension) files_to_convert = [os.path.join(original_staging_dir, path) for path in files_to_convert] @@ -127,15 +132,13 @@ class ExtractColorTranscode(publish.Extractor): def _rename_output_files(self, files_to_convert, output_extension): """Change extension of converted files.""" - if output_extension: - output_extension = output_extension.replace('.', '') - renamed_files = [] - for file_name in files_to_convert: - file_name, _ = os.path.splitext(file_name) - new_file_name = '{}.{}'.format(file_name, - output_extension) - renamed_files.append(new_file_name) - files_to_convert = renamed_files + renamed_files = [] + for file_name in files_to_convert: + file_name, _ = os.path.splitext(file_name) + new_file_name = '{}.{}'.format(file_name, + output_extension) + renamed_files.append(new_file_name) + files_to_convert = renamed_files return files_to_convert def _get_profile(self, instance): From d27dab7c970903e483aa6335ebee62ffffbab191 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 18:46:35 +0100 Subject: [PATCH 013/227] OP-4643 - added label to Settings --- .../projects_schema/schemas/schema_global_publish.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 c2c911d7d6..7155510fef 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 @@ -201,10 +201,14 @@ "type": "dict", "collapsible": true, "key": "ExtractColorTranscode", - "label": "ExtractColorTranscode", + "label": "ExtractColorTranscode (ImageIO)", "checkbox_key": "enabled", "is_group": true, "children": [ + { + "type": "label", + "label": "Configure output format(s) and color spaces for matching representations. Empty 'Output extension' denotes keeping source extension." + }, { "type": "boolean", "key": "enabled", From 24abe69437801ea4f022d3a0816664e93b2072f4 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 16 Jan 2023 18:22:08 +0100 Subject: [PATCH 014/227] OP-4643 - refactored according to review Function turned into single filepath input. --- openpype/lib/transcoding.py | 43 ++++++----- .../publish/extract_color_transcode.py | 72 ++++++++++--------- 2 files changed, 57 insertions(+), 58 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 792e8ddd1e..8e3432e0e9 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1039,12 +1039,12 @@ def convert_ffprobe_fps_to_float(value): return dividend / divisor -def convert_colorspace_for_input_paths( - input_paths, - output_dir, +def convert_colorspace( + input_path, + out_filepath, config_path, - source_color_space, - target_color_space, + source_colorspace, + target_colorspace, logger=None ): """Convert source files from one color space to another. @@ -1055,13 +1055,13 @@ def convert_colorspace_for_input_paths( frame template Args: - input_paths (str): Paths that should be converted. It is expected that + input_path (str): Paths that should be converted. It is expected that contains single file or image sequence of samy type. - output_dir (str): Path to directory where output will be rendered. + out_filepath (str): Path to directory where output will be rendered. Must not be same as input's directory. config_path (str): path to OCIO config file - source_color_space (str): ocio valid color space of source files - target_color_space (str): ocio valid target color space + source_colorspace (str): ocio valid color space of source files + target_colorspace (str): ocio valid target color space logger (logging.Logger): Logger used for logging. """ @@ -1075,21 +1075,18 @@ def convert_colorspace_for_input_paths( # Don't add any additional attributes "--nosoftwareattrib", "--colorconfig", config_path, - "--colorconvert", source_color_space, target_color_space + "--colorconvert", source_colorspace, target_colorspace ] - for input_path in input_paths: - # Prepare subprocess arguments + # Prepare subprocess arguments - oiio_cmd.extend([ - input_arg, input_path, - ]) + oiio_cmd.extend([ + input_arg, input_path, + ]) - # Add last argument - path to output - base_filename = os.path.basename(input_path) - output_path = os.path.join(output_dir, base_filename) - oiio_cmd.extend([ - "-o", output_path - ]) + # Add last argument - path to output + oiio_cmd.extend([ + "-o", out_filepath + ]) - logger.debug("Conversion command: {}".format(" ".join(oiio_cmd))) - run_subprocess(oiio_cmd, logger=logger) + logger.debug("Conversion command: {}".format(" ".join(oiio_cmd))) + run_subprocess(oiio_cmd, logger=logger) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 62cf8f0dee..3a05426432 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -10,7 +10,7 @@ from openpype.lib import ( ) from openpype.lib.transcoding import ( - convert_colorspace_for_input_paths, + convert_colorspace, get_transcode_temp_directory, ) @@ -69,7 +69,7 @@ class ExtractColorTranscode(publish.Extractor): continue colorspace_data = repre["colorspaceData"] - source_color_space = colorspace_data["colorspace"] + source_colorspace = colorspace_data["colorspace"] config_path = colorspace_data.get("configData", {}).get("path") if not os.path.exists(config_path): self.log.warning("Config file doesn't exist, skipping") @@ -80,8 +80,8 @@ class ExtractColorTranscode(publish.Extractor): for _, output_def in profile.get("outputs", {}).items(): new_repre = copy.deepcopy(repre) - new_staging_dir = get_transcode_temp_directory() original_staging_dir = new_repre["stagingDir"] + new_staging_dir = get_transcode_temp_directory() new_repre["stagingDir"] = new_staging_dir files_to_convert = new_repre["files"] if not isinstance(files_to_convert, list): @@ -92,27 +92,28 @@ class ExtractColorTranscode(publish.Extractor): output_extension = output_def["output_extension"] output_extension = output_extension.replace('.', '') if output_extension: - new_repre["name"] = output_extension + if new_repre["name"] == new_repre["ext"]: + new_repre["name"] = output_extension new_repre["ext"] = output_extension - files_to_convert = self._rename_output_files( - files_to_convert, output_extension) - - files_to_convert = [os.path.join(original_staging_dir, path) - for path in files_to_convert] - target_colorspace = output_def["output_colorspace"] if not target_colorspace: raise RuntimeError("Target colorspace must be set") - convert_colorspace_for_input_paths( - files_to_convert, - new_staging_dir, - config_path, - source_color_space, - target_colorspace, - self.log - ) + for file_name in files_to_convert: + input_filepath = os.path.join(original_staging_dir, + file_name) + output_path = self._get_output_file_path(input_filepath, + new_staging_dir, + output_extension) + convert_colorspace( + input_filepath, + output_path, + config_path, + source_colorspace, + target_colorspace, + self.log + ) instance.context.data["cleanupFullPaths"].extend( files_to_delete) @@ -130,16 +131,16 @@ class ExtractColorTranscode(publish.Extractor): instance.data["representations"].append(new_repre) - def _rename_output_files(self, files_to_convert, output_extension): - """Change extension of converted files.""" - renamed_files = [] - for file_name in files_to_convert: - file_name, _ = os.path.splitext(file_name) - new_file_name = '{}.{}'.format(file_name, - output_extension) - renamed_files.append(new_file_name) - files_to_convert = renamed_files - return files_to_convert + def _get_output_file_path(self, input_filepath, output_dir, + output_extension): + """Create output file name path.""" + file_name = os.path.basename(input_filepath) + file_name, input_extension = os.path.splitext(file_name) + if not output_extension: + output_extension = input_extension + new_file_name = '{}.{}'.format(file_name, + output_extension) + return os.path.join(output_dir, new_file_name) def _get_profile(self, instance): """Returns profile if and how repre should be color transcoded.""" @@ -161,10 +162,10 @@ class ExtractColorTranscode(publish.Extractor): if not profile: self.log.info(( - "Skipped instance. None of profiles in presets are for" - " Host: \"{}\" | Families: \"{}\" | Task \"{}\"" - " | Task type \"{}\" | Subset \"{}\" " - ).format(host_name, family, task_name, task_type, subset)) + "Skipped instance. None of profiles in presets are for" + " Host: \"{}\" | Families: \"{}\" | Task \"{}\"" + " | Task type \"{}\" | Subset \"{}\" " + ).format(host_name, family, task_name, task_type, subset)) self.log.debug("profile: {}".format(profile)) return profile @@ -181,18 +182,19 @@ class ExtractColorTranscode(publish.Extractor): if repre.get("ext") not in self.supported_exts: self.log.debug(( - "Representation \"{}\" of unsupported extension. Skipped." + "Representation '{}' of unsupported extension. Skipped." ).format(repre["name"])) return False if not repre.get("files"): self.log.debug(( - "Representation \"{}\" have empty files. Skipped." + "Representation '{}' have empty files. Skipped." ).format(repre["name"])) return False if not repre.get("colorspaceData"): - self.log.debug("Repre has no colorspace data. Skipped.") + self.log.debug("Representation '{}' has no colorspace data. " + "Skipped.") return False return True From a9a86d112093d6b4dd47f6edc5f9ac75ace3b13d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 26 Jan 2023 12:53:02 +0100 Subject: [PATCH 015/227] OP-4643 - updated schema Co-authored-by: Toke Jepsen --- .../projects_schema/schemas/schema_global_publish.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 7155510fef..80c18ce118 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 @@ -267,8 +267,8 @@ "type": "dict", "children": [ { - "key": "output_extension", - "label": "Output extension", + "key": "extension", + "label": "Extension", "type": "text" }, { From 0858c16ce0882949c570beeefe050f71219d28dc Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 26 Jan 2023 12:54:46 +0100 Subject: [PATCH 016/227] OP-4643 - updated plugin name in schema MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jakub Ježek --- .../projects_schema/schemas/schema_global_publish.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 80c18ce118..357cbfb287 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 @@ -200,8 +200,8 @@ { "type": "dict", "collapsible": true, - "key": "ExtractColorTranscode", - "label": "ExtractColorTranscode (ImageIO)", + "key": "ExtractOIIOTranscode", + "label": "Extract OIIO Transcode", "checkbox_key": "enabled", "is_group": true, "children": [ From 875cac007dd0a3630b87cf545f73d038c4de79c0 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 26 Jan 2023 12:55:57 +0100 Subject: [PATCH 017/227] OP-4643 - updated key in schema Co-authored-by: Toke Jepsen --- .../projects_schema/schemas/schema_global_publish.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 357cbfb287..0281b0ded6 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 @@ -272,8 +272,8 @@ "type": "text" }, { - "key": "output_colorspace", - "label": "Output colorspace", + "key": "colorspace", + "label": "Colorspace", "type": "text" }, { From 18b728aaf53a90422614a97ddf7e528ff9a50955 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 26 Jan 2023 12:57:03 +0100 Subject: [PATCH 018/227] OP-4643 - changed oiio_cmd creation Co-authored-by: Toke Jepsen --- openpype/lib/transcoding.py | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 8e3432e0e9..828861e21e 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1068,25 +1068,15 @@ def convert_colorspace( if logger is None: logger = logging.getLogger(__name__) - input_arg = "-i" oiio_cmd = [ get_oiio_tools_path(), - + input_path, # Don't add any additional attributes "--nosoftwareattrib", "--colorconfig", config_path, - "--colorconvert", source_colorspace, target_colorspace - ] - # Prepare subprocess arguments - - oiio_cmd.extend([ - input_arg, input_path, - ]) - - # Add last argument - path to output - oiio_cmd.extend([ + "--colorconvert", source_colorspace, target_colorspace, "-o", out_filepath - ]) + ] logger.debug("Conversion command: {}".format(" ".join(oiio_cmd))) run_subprocess(oiio_cmd, logger=logger) From 669e38d2c37481d089816d9dc663573f4a6895c3 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 26 Jan 2023 13:44:45 +0100 Subject: [PATCH 019/227] OP-4643 - updated new keys into settings --- .../settings/defaults/project_settings/global.json | 2 +- .../projects_schema/schemas/schema_global_publish.json | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 167f7611ce..f448f1a79a 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -70,7 +70,7 @@ "output": [] } }, - "ExtractColorTranscode": { + "ExtractOIIOTranscode": { "enabled": true, "profiles": [] }, 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 0281b0ded6..74b81b13af 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 @@ -276,6 +276,16 @@ "label": "Colorspace", "type": "text" }, + { + "key": "display", + "label": "Display", + "type": "text" + }, + { + "key": "view", + "label": "View", + "type": "text" + }, { "type": "schema", "name": "schema_representation_tags" From 104dd91bba17cb59f5254426cfc98b7b48dbe91f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 26 Jan 2023 13:45:42 +0100 Subject: [PATCH 020/227] OP-4643 - renanmed plugin, added new keys into outputs --- openpype/plugins/publish/extract_color_transcode.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 3a05426432..cc63b35988 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -17,7 +17,7 @@ from openpype.lib.transcoding import ( from openpype.lib.profiles_filtering import filter_profiles -class ExtractColorTranscode(publish.Extractor): +class ExtractOIIOTranscode(publish.Extractor): """ Extractor to convert colors from one colorspace to different. @@ -89,14 +89,14 @@ class ExtractColorTranscode(publish.Extractor): files_to_delete = copy.deepcopy(files_to_convert) - output_extension = output_def["output_extension"] + output_extension = output_def["extension"] output_extension = output_extension.replace('.', '') if output_extension: if new_repre["name"] == new_repre["ext"]: new_repre["name"] = output_extension new_repre["ext"] = output_extension - target_colorspace = output_def["output_colorspace"] + target_colorspace = output_def["colorspace"] if not target_colorspace: raise RuntimeError("Target colorspace must be set") From 4179a8e48c5603ac8b17fbd48783e372135c3c33 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 26 Jan 2023 18:03:13 +0100 Subject: [PATCH 021/227] OP-4643 - fixed config path key --- openpype/plugins/publish/extract_color_transcode.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index cc63b35988..245faeb306 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -70,8 +70,8 @@ class ExtractOIIOTranscode(publish.Extractor): colorspace_data = repre["colorspaceData"] source_colorspace = colorspace_data["colorspace"] - config_path = colorspace_data.get("configData", {}).get("path") - if not os.path.exists(config_path): + config_path = colorspace_data.get("config", {}).get("path") + if not config_path or not os.path.exists(config_path): self.log.warning("Config file doesn't exist, skipping") continue From 381f65bc8c973725f8d57a345d70a12ebcf95786 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 26 Jan 2023 18:03:42 +0100 Subject: [PATCH 022/227] OP-4643 - fixed renaming files --- openpype/plugins/publish/extract_color_transcode.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 245faeb306..c079dcf70e 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -96,6 +96,14 @@ class ExtractOIIOTranscode(publish.Extractor): new_repre["name"] = output_extension new_repre["ext"] = output_extension + renamed_files = [] + _, orig_ext = os.path.splitext(files_to_convert[0]) + for file_name in files_to_convert: + file_name = file_name.replace(orig_ext, + "."+output_extension) + renamed_files.append(file_name) + new_repre["files"] = renamed_files + target_colorspace = output_def["colorspace"] if not target_colorspace: raise RuntimeError("Target colorspace must be set") From c1bb93d0fbf75f978b1a04195edc102e3b113f70 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 26 Jan 2023 18:04:44 +0100 Subject: [PATCH 023/227] OP-4643 - updated to calculate sequence format --- .../publish/extract_color_transcode.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index c079dcf70e..09c86909cb 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -1,5 +1,6 @@ import os import copy +import clique import pyblish.api @@ -108,6 +109,8 @@ class ExtractOIIOTranscode(publish.Extractor): if not target_colorspace: raise RuntimeError("Target colorspace must be set") + files_to_convert = self._translate_to_sequence( + files_to_convert) for file_name in files_to_convert: input_filepath = os.path.join(original_staging_dir, file_name) @@ -139,6 +142,40 @@ class ExtractOIIOTranscode(publish.Extractor): instance.data["representations"].append(new_repre) + def _translate_to_sequence(self, files_to_convert): + """Returns original list of files or single sequence format filename. + + Uses clique to find frame sequence, in this case it merges all frames + into sequence format (%0X) and returns it. + If sequence not found, it returns original list + + Args: + files_to_convert (list): list of file names + Returns: + (list) of [file.%04.exr] or [fileA.exr, fileB.exr] + """ + pattern = [clique.PATTERNS["frames"]] + collections, remainder = clique.assemble( + files_to_convert, patterns=pattern, + assume_padded_when_ambiguous=True) + + if collections: + if len(collections) > 1: + raise ValueError( + "Too many collections {}".format(collections)) + + collection = collections[0] + padding = collection.padding + padding_str = "%0{}".format(padding) + frames = list(collection.indexes) + frame_str = "{}-{}#".format(frames[0], frames[-1]) + file_name = "{}{}{}".format(collection.head, frame_str, + collection.tail) + + files_to_convert = [file_name] + + return files_to_convert + def _get_output_file_path(self, input_filepath, output_dir, output_extension): """Create output file name path.""" From 5a386b58d6bc9125adf6148bbb854e0688a0adf0 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 26 Jan 2023 18:54:02 +0100 Subject: [PATCH 024/227] OP-4643 - implemented display and viewer color space --- openpype/lib/transcoding.py | 23 +++++++++++++++++-- .../publish/extract_color_transcode.py | 13 +++++++++-- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 828861e21e..fab9eeaaad 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1045,6 +1045,8 @@ def convert_colorspace( config_path, source_colorspace, target_colorspace, + view, + display, logger=None ): """Convert source files from one color space to another. @@ -1062,8 +1064,11 @@ def convert_colorspace( config_path (str): path to OCIO config file source_colorspace (str): ocio valid color space of source files target_colorspace (str): ocio valid target color space + view (str): name for viewer space (ocio valid) + display (str): name for display-referred reference space (ocio valid) logger (logging.Logger): Logger used for logging. - + Raises: + ValueError: if misconfigured """ if logger is None: logger = logging.getLogger(__name__) @@ -1074,9 +1079,23 @@ def convert_colorspace( # Don't add any additional attributes "--nosoftwareattrib", "--colorconfig", config_path, - "--colorconvert", source_colorspace, target_colorspace, "-o", out_filepath ] + if all([target_colorspace, view, display]): + raise ValueError("Colorspace and both screen and display" + " cannot be set together." + "Choose colorspace or screen and display") + if not target_colorspace and not all([view, display]): + raise ValueError("Both screen and display must be set.") + + if target_colorspace: + oiio_cmd.extend(["--colorconvert", + source_colorspace, + target_colorspace]) + if view and display: + oiio_cmd.extend(["--iscolorspace", source_colorspace]) + oiio_cmd.extend(["--ociodisplay", display, view]) + logger.debug("Conversion command: {}".format(" ".join(oiio_cmd))) run_subprocess(oiio_cmd, logger=logger) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 09c86909cb..cd8421c0cd 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -106,8 +106,15 @@ class ExtractOIIOTranscode(publish.Extractor): new_repre["files"] = renamed_files target_colorspace = output_def["colorspace"] - if not target_colorspace: - raise RuntimeError("Target colorspace must be set") + view = output_def["view"] or colorspace_data.get("view") + display = (output_def["display"] or + colorspace_data.get("display")) + # both could be already collected by DCC, + # but could be overwritten + if view: + new_repre["colorspaceData"]["view"] = view + if display: + new_repre["colorspaceData"]["view"] = display files_to_convert = self._translate_to_sequence( files_to_convert) @@ -123,6 +130,8 @@ class ExtractOIIOTranscode(publish.Extractor): config_path, source_colorspace, target_colorspace, + view, + display, self.log ) From 2245869ffd0723177c2e76111723d9aeb43446c4 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 26 Jan 2023 19:03:10 +0100 Subject: [PATCH 025/227] OP-4643 - fix wrong order of deletion of representation --- openpype/plugins/publish/extract_color_transcode.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index cd8421c0cd..9cca5cc969 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -69,6 +69,8 @@ class ExtractOIIOTranscode(publish.Extractor): if not self._repre_is_valid(repre): continue + added_representations = False + colorspace_data = repre["colorspaceData"] source_colorspace = colorspace_data["colorspace"] config_path = colorspace_data.get("config", {}).get("path") @@ -76,8 +78,6 @@ class ExtractOIIOTranscode(publish.Extractor): self.log.warning("Config file doesn't exist, skipping") continue - repre = self._handle_original_repre(repre, profile) - for _, output_def in profile.get("outputs", {}).items(): new_repre = copy.deepcopy(repre) @@ -150,6 +150,10 @@ class ExtractOIIOTranscode(publish.Extractor): new_repre["tags"].append(tag) instance.data["representations"].append(new_repre) + added_representations = True + + if added_representations: + self._mark_original_repre_for_deletion(repre, profile) def _translate_to_sequence(self, files_to_convert): """Returns original list of files or single sequence format filename. @@ -253,7 +257,8 @@ class ExtractOIIOTranscode(publish.Extractor): return True - def _handle_original_repre(self, repre, profile): + def _mark_original_repre_for_deletion(self, repre, profile): + """If new transcoded representation created, delete old.""" delete_original = profile["delete_original"] if delete_original: @@ -264,5 +269,3 @@ class ExtractOIIOTranscode(publish.Extractor): repre["tags"].remove("review") if "delete" not in repre["tags"]: repre["tags"].append("delete") - - return repre From ce784ac78207e1a16f2a14f7cdaaad5268fd6c26 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 27 Jan 2023 11:26:27 +0100 Subject: [PATCH 026/227] OP-4643 - updated docstring, standardized arguments --- openpype/lib/transcoding.py | 19 +++++++---------- .../publish/extract_color_transcode.py | 21 +++++++++---------- 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index fab9eeaaad..752712166f 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1041,7 +1041,7 @@ def convert_ffprobe_fps_to_float(value): def convert_colorspace( input_path, - out_filepath, + output_path, config_path, source_colorspace, target_colorspace, @@ -1049,18 +1049,13 @@ def convert_colorspace( display, logger=None ): - """Convert source files from one color space to another. - - Filenames of input files are kept so make sure that output directory - is not the same directory as input files have. - - This way it can handle gaps and can keep input filenames without handling - frame template + """Convert source file from one color space to another. Args: - input_path (str): Paths that should be converted. It is expected that - contains single file or image sequence of samy type. - out_filepath (str): Path to directory where output will be rendered. - Must not be same as input's directory. + input_path (str): Path that should be converted. It is expected that + contains single file or image sequence of same type + (sequence in format 'file.FRAMESTART-FRAMEEND#.exr', see oiio docs) + output_path (str): Path to output filename. config_path (str): path to OCIO config file source_colorspace (str): ocio valid color space of source files target_colorspace (str): ocio valid target color space @@ -1079,7 +1074,7 @@ def convert_colorspace( # Don't add any additional attributes "--nosoftwareattrib", "--colorconfig", config_path, - "-o", out_filepath + "-o", output_path ] if all([target_colorspace, view, display]): diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 9cca5cc969..c4cef15ea6 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -119,13 +119,13 @@ class ExtractOIIOTranscode(publish.Extractor): files_to_convert = self._translate_to_sequence( files_to_convert) for file_name in files_to_convert: - input_filepath = os.path.join(original_staging_dir, - file_name) - output_path = self._get_output_file_path(input_filepath, + input_path = os.path.join(original_staging_dir, + file_name) + output_path = self._get_output_file_path(input_path, new_staging_dir, output_extension) convert_colorspace( - input_filepath, + input_path, output_path, config_path, source_colorspace, @@ -156,16 +156,17 @@ class ExtractOIIOTranscode(publish.Extractor): self._mark_original_repre_for_deletion(repre, profile) def _translate_to_sequence(self, files_to_convert): - """Returns original list of files or single sequence format filename. + """Returns original list or list with filename formatted in single + sequence format. Uses clique to find frame sequence, in this case it merges all frames - into sequence format (%0X) and returns it. + into sequence format (FRAMESTART-FRAMEEND#) and returns it. If sequence not found, it returns original list Args: files_to_convert (list): list of file names Returns: - (list) of [file.%04.exr] or [fileA.exr, fileB.exr] + (list) of [file.1001-1010#.exr] or [fileA.exr, fileB.exr] """ pattern = [clique.PATTERNS["frames"]] collections, remainder = clique.assemble( @@ -178,8 +179,6 @@ class ExtractOIIOTranscode(publish.Extractor): "Too many collections {}".format(collections)) collection = collections[0] - padding = collection.padding - padding_str = "%0{}".format(padding) frames = list(collection.indexes) frame_str = "{}-{}#".format(frames[0], frames[-1]) file_name = "{}{}{}".format(collection.head, frame_str, @@ -189,10 +188,10 @@ class ExtractOIIOTranscode(publish.Extractor): return files_to_convert - def _get_output_file_path(self, input_filepath, output_dir, + def _get_output_file_path(self, input_path, output_dir, output_extension): """Create output file name path.""" - file_name = os.path.basename(input_filepath) + file_name = os.path.basename(input_path) file_name, input_extension = os.path.splitext(file_name) if not output_extension: output_extension = input_extension From 02ad1d1998235e5416f41b3a973577659cb0d971 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 27 Jan 2023 11:27:06 +0100 Subject: [PATCH 027/227] OP-4643 - fix wrong assignment --- openpype/plugins/publish/extract_color_transcode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index c4cef15ea6..4e899a519c 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -114,7 +114,7 @@ class ExtractOIIOTranscode(publish.Extractor): if view: new_repre["colorspaceData"]["view"] = view if display: - new_repre["colorspaceData"]["view"] = display + new_repre["colorspaceData"]["display"] = display files_to_convert = self._translate_to_sequence( files_to_convert) From 7d4a17169377dc8f8f68b54ae5338c296fd362a7 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 27 Jan 2023 11:59:13 +0100 Subject: [PATCH 028/227] OP-4643 - fix files to delete --- .../publish/extract_color_transcode.py | 45 ++++++++++++------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 4e899a519c..99e684ba21 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -84,26 +84,18 @@ class ExtractOIIOTranscode(publish.Extractor): original_staging_dir = new_repre["stagingDir"] new_staging_dir = get_transcode_temp_directory() new_repre["stagingDir"] = new_staging_dir - files_to_convert = new_repre["files"] - if not isinstance(files_to_convert, list): - files_to_convert = [files_to_convert] - files_to_delete = copy.deepcopy(files_to_convert) + if isinstance(new_repre["files"], list): + files_to_convert = copy.deepcopy(new_repre["files"]) + else: + files_to_convert = [new_repre["files"]] output_extension = output_def["extension"] output_extension = output_extension.replace('.', '') if output_extension: - if new_repre["name"] == new_repre["ext"]: - new_repre["name"] = output_extension - new_repre["ext"] = output_extension - - renamed_files = [] - _, orig_ext = os.path.splitext(files_to_convert[0]) - for file_name in files_to_convert: - file_name = file_name.replace(orig_ext, - "."+output_extension) - renamed_files.append(file_name) - new_repre["files"] = renamed_files + self._rename_in_representation(new_repre, + files_to_convert, + output_extension) target_colorspace = output_def["colorspace"] view = output_def["view"] or colorspace_data.get("view") @@ -135,8 +127,12 @@ class ExtractOIIOTranscode(publish.Extractor): self.log ) - instance.context.data["cleanupFullPaths"].extend( - files_to_delete) + # cleanup temporary transcoded files + for file_name in new_repre["files"]: + transcoded_file_path = os.path.join(new_staging_dir, + file_name) + instance.context.data["cleanupFullPaths"].append( + transcoded_file_path) custom_tags = output_def.get("custom_tags") if custom_tags: @@ -155,6 +151,21 @@ class ExtractOIIOTranscode(publish.Extractor): if added_representations: self._mark_original_repre_for_deletion(repre, profile) + def _rename_in_representation(self, new_repre, files_to_convert, + output_extension): + """Replace old extension with new one everywhere in representation.""" + if new_repre["name"] == new_repre["ext"]: + new_repre["name"] = output_extension + new_repre["ext"] = output_extension + + renamed_files = [] + for file_name in files_to_convert: + file_name, _ = os.path.splitext(file_name) + file_name = '{}.{}'.format(file_name, + output_extension) + renamed_files.append(file_name) + new_repre["files"] = renamed_files + def _translate_to_sequence(self, files_to_convert): """Returns original list or list with filename formatted in single sequence format. From 302a79095ec50031a2c411dd2704e75e6e51d6cf Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 27 Jan 2023 13:17:59 +0100 Subject: [PATCH 029/227] OP-4643 - moved output argument to the end --- openpype/lib/transcoding.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 752712166f..1629058beb 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1073,8 +1073,7 @@ def convert_colorspace( input_path, # Don't add any additional attributes "--nosoftwareattrib", - "--colorconfig", config_path, - "-o", output_path + "--colorconfig", config_path ] if all([target_colorspace, view, display]): @@ -1092,5 +1091,7 @@ def convert_colorspace( oiio_cmd.extend(["--iscolorspace", source_colorspace]) oiio_cmd.extend(["--ociodisplay", display, view]) + oiio_cmd.extend(["-o", output_path]) + logger.debug("Conversion command: {}".format(" ".join(oiio_cmd))) run_subprocess(oiio_cmd, logger=logger) From 1de178e98fd0a59533826ae132b86ec85b742ce8 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 27 Jan 2023 13:18:33 +0100 Subject: [PATCH 030/227] OP-4643 - fix no tags in repre --- openpype/plugins/publish/extract_color_transcode.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 99e684ba21..3d897c6d9f 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -142,6 +142,8 @@ class ExtractOIIOTranscode(publish.Extractor): # Add additional tags from output definition to representation for tag in output_def["tags"]: + if not new_repre.get("tags"): + new_repre["tags"] = [] if tag not in new_repre["tags"]: new_repre["tags"].append(tag) From 8ce2c151ce920e22089712e81b0d782a66e8fa38 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 27 Jan 2023 13:26:07 +0100 Subject: [PATCH 031/227] OP-4643 - changed docstring Elaborated more that 'target_colorspace' and ('view', 'display') are disjunctive. --- openpype/lib/transcoding.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 1629058beb..6d91f514ec 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1045,8 +1045,8 @@ def convert_colorspace( config_path, source_colorspace, target_colorspace, - view, - display, + view=None, + display=None, logger=None ): """Convert source file from one color space to another. @@ -1059,7 +1059,9 @@ def convert_colorspace( config_path (str): path to OCIO config file source_colorspace (str): ocio valid color space of source files target_colorspace (str): ocio valid target color space + if filled, 'view' and 'display' must be empty view (str): name for viewer space (ocio valid) + both 'view' and 'display' must be filled (if 'target_colorspace') display (str): name for display-referred reference space (ocio valid) logger (logging.Logger): Logger used for logging. Raises: From 9f17a4803f4f6fdfb8858a841e20dbe283b119f2 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 1 Feb 2023 11:14:07 +0100 Subject: [PATCH 032/227] OP-4663 - fix double dots in extension Co-authored-by: Toke Jepsen --- openpype/plugins/publish/extract_color_transcode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 3d897c6d9f..bfed69c300 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -207,7 +207,7 @@ class ExtractOIIOTranscode(publish.Extractor): file_name = os.path.basename(input_path) file_name, input_extension = os.path.splitext(file_name) if not output_extension: - output_extension = input_extension + output_extension = input_extension.replace(".", "") new_file_name = '{}.{}'.format(file_name, output_extension) return os.path.join(output_dir, new_file_name) From e8d4a752a94e0f760bc2430536fdd8d87eda7636 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 1 Feb 2023 11:22:20 +0100 Subject: [PATCH 033/227] Fix pyproject.toml version because of Poetry Automatization injects wrong format --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 634aeda5ac..2fc4f6fe39 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.15.1-nightly.2" # OpenPype +version = "3.15.1" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From e7fbe105fdd312ecdf3560e1db5859dbbda39076 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 1 Feb 2023 16:11:45 +0100 Subject: [PATCH 034/227] OP-4643 - update documentation in Settings schema --- .../schemas/projects_schema/schemas/schema_global_publish.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 74b81b13af..3956f403f4 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 @@ -207,7 +207,7 @@ "children": [ { "type": "label", - "label": "Configure output format(s) and color spaces for matching representations. Empty 'Output extension' denotes keeping source extension." + "label": "Configure Output Definition(s) for new representation(s). \nEmpty 'Extension' denotes keeping source extension. \nName(key) of output definition will be used as new representation name \nunless 'passthrough' value is used to keep existing name. \nFill either 'Colorspace' (for target colorspace) or \nboth 'Display' and 'View' (for display and viewer colorspaces)." }, { "type": "boolean", From ed95995fc7b688d7ea5a66e6a818364a1aadc551 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 1 Feb 2023 16:13:59 +0100 Subject: [PATCH 035/227] OP-4643 - name of new representation from output definition key --- .../publish/extract_color_transcode.py | 49 +++++++++++++++---- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index bfed69c300..e39ea3add9 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -32,6 +32,25 @@ class ExtractOIIOTranscode(publish.Extractor): - task types - task names - subset names + + Can produce one or more representations (with different extensions) based + on output definition in format: + "output_name: { + "extension": "png", + "colorspace": "ACES - ACEScg", + "display": "", + "view": "", + "tags": [], + "custom_tags": [] + } + + If 'extension' is empty original representation extension is used. + 'output_name' will be used as name of new representation. In case of value + 'passthrough' name of original representation will be used. + + 'colorspace' denotes target colorspace to be transcoded into. Could be + empty if transcoding should be only into display and viewer colorspace. + (In that case both 'display' and 'view' must be filled.) """ label = "Transcode color spaces" @@ -78,7 +97,7 @@ class ExtractOIIOTranscode(publish.Extractor): self.log.warning("Config file doesn't exist, skipping") continue - for _, output_def in profile.get("outputs", {}).items(): + for output_name, output_def in profile.get("outputs", {}).items(): new_repre = copy.deepcopy(repre) original_staging_dir = new_repre["stagingDir"] @@ -92,10 +111,10 @@ class ExtractOIIOTranscode(publish.Extractor): output_extension = output_def["extension"] output_extension = output_extension.replace('.', '') - if output_extension: - self._rename_in_representation(new_repre, - files_to_convert, - output_extension) + self._rename_in_representation(new_repre, + files_to_convert, + output_name, + output_extension) target_colorspace = output_def["colorspace"] view = output_def["view"] or colorspace_data.get("view") @@ -154,10 +173,22 @@ class ExtractOIIOTranscode(publish.Extractor): self._mark_original_repre_for_deletion(repre, profile) def _rename_in_representation(self, new_repre, files_to_convert, - output_extension): - """Replace old extension with new one everywhere in representation.""" - if new_repre["name"] == new_repre["ext"]: - new_repre["name"] = output_extension + output_name, output_extension): + """Replace old extension with new one everywhere in representation. + + Args: + new_repre (dict) + files_to_convert (list): of filenames from repre["files"], + standardized to always list + output_name (str): key of output definition from Settings, + if "" token used, keep original repre name + output_extension (str): extension from output definition + """ + if output_name != "passthrough": + new_repre["name"] = output_name + if not output_extension: + return + new_repre["ext"] = output_extension renamed_files = [] From a20646e82f5c39108c1ac5b0b9988226c49c1a56 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 1 Feb 2023 16:42:48 +0100 Subject: [PATCH 036/227] OP-4643 - updated docstring for convert_colorspace --- openpype/lib/transcoding.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 6d91f514ec..18273dd432 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1054,8 +1054,11 @@ def convert_colorspace( Args: input_path (str): Path that should be converted. It is expected that contains single file or image sequence of same type - (sequence in format 'file.FRAMESTART-FRAMEEND#.exr', see oiio docs) + (sequence in format 'file.FRAMESTART-FRAMEEND#.ext', see oiio docs, + eg `big.1-3#.tif`) output_path (str): Path to output filename. + (must follow format of 'input_path', eg. single file or + sequence in 'file.FRAMESTART-FRAMEEND#.ext', `output.1-3#.tif`) config_path (str): path to OCIO config file source_colorspace (str): ocio valid color space of source files target_colorspace (str): ocio valid target color space From e24665cf536aa4d48538f229718de57a731e75f1 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 7 Feb 2023 18:22:10 +0100 Subject: [PATCH 037/227] OP-4643 - remove review from old representation If new representation gets created and adds 'review' tag it becomes new reviewable representation. --- .../publish/extract_color_transcode.py | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index e39ea3add9..d10b887a0b 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -89,6 +89,7 @@ class ExtractOIIOTranscode(publish.Extractor): continue added_representations = False + added_review = False colorspace_data = repre["colorspaceData"] source_colorspace = colorspace_data["colorspace"] @@ -166,11 +167,15 @@ class ExtractOIIOTranscode(publish.Extractor): if tag not in new_repre["tags"]: new_repre["tags"].append(tag) + if tag == "review": + added_review = True + instance.data["representations"].append(new_repre) added_representations = True if added_representations: - self._mark_original_repre_for_deletion(repre, profile) + self._mark_original_repre_for_deletion(repre, profile, + added_review) def _rename_in_representation(self, new_repre, files_to_convert, output_name, output_extension): @@ -300,15 +305,16 @@ class ExtractOIIOTranscode(publish.Extractor): return True - def _mark_original_repre_for_deletion(self, repre, profile): + def _mark_original_repre_for_deletion(self, repre, profile, added_review): """If new transcoded representation created, delete old.""" + if not repre.get("tags"): + repre["tags"] = [] + delete_original = profile["delete_original"] if delete_original: - if not repre.get("tags"): - repre["tags"] = [] - - if "review" in repre["tags"]: - repre["tags"].remove("review") if "delete" not in repre["tags"]: repre["tags"].append("delete") + + if added_review and "review" in repre["tags"]: + repre["tags"].remove("review") From 3aa74b231c2e7116ea792901ac53cfcd848513fc Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 7 Feb 2023 18:23:42 +0100 Subject: [PATCH 038/227] OP-4643 - remove representation that should be deleted Or old revieable representation would be reviewed too. --- openpype/plugins/publish/extract_color_transcode.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index d10b887a0b..93ee1ec44d 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -177,6 +177,11 @@ class ExtractOIIOTranscode(publish.Extractor): self._mark_original_repre_for_deletion(repre, profile, added_review) + for repre in tuple(instance.data["representations"]): + tags = repre.get("tags") or [] + if "delete" in tags and "thumbnail" not in tags: + instance.data["representations"].remove(repre) + def _rename_in_representation(self, new_repre, files_to_convert, output_name, output_extension): """Replace old extension with new one everywhere in representation. From 5e0c4a3ab1432e120b8f0c324f899070f1a5f831 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 8 Feb 2023 12:07:00 +0100 Subject: [PATCH 039/227] Fix - added missed scopes for Slack bot --- openpype/modules/slack/manifest.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/modules/slack/manifest.yml b/openpype/modules/slack/manifest.yml index 7a65cc5915..233c39fbaf 100644 --- a/openpype/modules/slack/manifest.yml +++ b/openpype/modules/slack/manifest.yml @@ -19,6 +19,8 @@ oauth_config: - chat:write.public - files:write - channels:read + - users:read + - usergroups:read settings: org_deploy_enabled: false socket_mode_enabled: false From 714454d7300cf2e067785c888dadddb415f88baf Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 14 Feb 2023 14:54:25 +0100 Subject: [PATCH 040/227] OP-4643 - fix logging Wrong variable used --- openpype/plugins/publish/extract_review.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index dcb43d7fa2..0f6dacba18 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -169,7 +169,7 @@ class ExtractReview(pyblish.api.InstancePlugin): "Skipped representation. All output definitions from" " selected profile does not match to representation's" " custom tags. \"{}\"" - ).format(str(tags))) + ).format(str(custom_tags))) continue outputs_per_representations.append((repre, outputs)) From 2b5e4bc14e7a4241ee97a3bbb0716b148ec13513 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 14 Feb 2023 15:14:14 +0100 Subject: [PATCH 041/227] OP-4643 - allow new repre to stay One might want to delete outputs with 'delete' tag, but repre must stay there at least until extract_review. More universal new tag might be created for this. --- openpype/plugins/publish/extract_color_transcode.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 93ee1ec44d..4a03e623fd 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -161,15 +161,17 @@ class ExtractOIIOTranscode(publish.Extractor): new_repre["custom_tags"].extend(custom_tags) # Add additional tags from output definition to representation + if not new_repre.get("tags"): + new_repre["tags"] = [] for tag in output_def["tags"]: - if not new_repre.get("tags"): - new_repre["tags"] = [] if tag not in new_repre["tags"]: new_repre["tags"].append(tag) if tag == "review": added_review = True + new_repre["tags"].append("newly_added") + instance.data["representations"].append(new_repre) added_representations = True @@ -179,6 +181,12 @@ class ExtractOIIOTranscode(publish.Extractor): for repre in tuple(instance.data["representations"]): tags = repre.get("tags") or [] + # TODO implement better way, for now do not delete new repre + # new repre might have 'delete' tag to removed, but it first must + # be there for review to be created + if "newly_added" in tags: + tags.remove("newly_added") + continue if "delete" in tags and "thumbnail" not in tags: instance.data["representations"].remove(repre) From 0abcbc152390c00aa596b80ccc40c6a64c4861c4 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 15 Feb 2023 16:04:59 +0100 Subject: [PATCH 042/227] OP-4642 - added additional command arguments to Settings --- .../schemas/schema_global_publish.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) 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 3956f403f4..5333d514b5 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 @@ -286,6 +286,20 @@ "label": "View", "type": "text" }, + { + "key": "oiiotool_args", + "label": "OIIOtool arguments", + "type": "dict", + "highlight_content": true, + "children": [ + { + "key": "additional_command_args", + "label": "Additional command line arguments", + "type": "list", + "object_type": "text" + } + ] + }, { "type": "schema", "name": "schema_representation_tags" From 5ec5cda2bcd1e68de459496c20a1cc9699ba7144 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 15 Feb 2023 16:08:06 +0100 Subject: [PATCH 043/227] OP-4642 - added additional command arguments for oiiotool Some extension requires special command line arguments (.dpx and binary depth). --- openpype/lib/transcoding.py | 6 ++++++ openpype/plugins/publish/extract_color_transcode.py | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 18273dd432..95042fb74c 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1047,6 +1047,7 @@ def convert_colorspace( target_colorspace, view=None, display=None, + additional_command_args=None, logger=None ): """Convert source file from one color space to another. @@ -1066,6 +1067,8 @@ def convert_colorspace( view (str): name for viewer space (ocio valid) both 'view' and 'display' must be filled (if 'target_colorspace') display (str): name for display-referred reference space (ocio valid) + additional_command_args (list): arguments for oiiotool (like binary + depth for .dpx) logger (logging.Logger): Logger used for logging. Raises: ValueError: if misconfigured @@ -1088,6 +1091,9 @@ def convert_colorspace( if not target_colorspace and not all([view, display]): raise ValueError("Both screen and display must be set.") + if additional_command_args: + oiio_cmd.extend(additional_command_args) + if target_colorspace: oiio_cmd.extend(["--colorconvert", source_colorspace, diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 4a03e623fd..3de404125d 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -128,6 +128,9 @@ class ExtractOIIOTranscode(publish.Extractor): if display: new_repre["colorspaceData"]["display"] = display + additional_command_args = (output_def["oiiotool_args"] + ["additional_command_args"]) + files_to_convert = self._translate_to_sequence( files_to_convert) for file_name in files_to_convert: @@ -144,6 +147,7 @@ class ExtractOIIOTranscode(publish.Extractor): target_colorspace, view, display, + additional_command_args, self.log ) From b30979b8c1a4d4da0d998862516f95a89e522d9d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 15 Feb 2023 16:21:25 +0100 Subject: [PATCH 044/227] OP-4642 - refactored newly added representations --- openpype/plugins/publish/extract_color_transcode.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 3de404125d..8c4ef59de9 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -82,6 +82,7 @@ class ExtractOIIOTranscode(publish.Extractor): if not profile: return + new_representations = [] repres = instance.data.get("representations") or [] for idx, repre in enumerate(list(repres)): self.log.debug("repre ({}): `{}`".format(idx + 1, repre["name"])) @@ -174,9 +175,7 @@ class ExtractOIIOTranscode(publish.Extractor): if tag == "review": added_review = True - new_repre["tags"].append("newly_added") - - instance.data["representations"].append(new_repre) + new_representations.append(new_repre) added_representations = True if added_representations: @@ -185,15 +184,11 @@ class ExtractOIIOTranscode(publish.Extractor): for repre in tuple(instance.data["representations"]): tags = repre.get("tags") or [] - # TODO implement better way, for now do not delete new repre - # new repre might have 'delete' tag to removed, but it first must - # be there for review to be created - if "newly_added" in tags: - tags.remove("newly_added") - continue if "delete" in tags and "thumbnail" not in tags: instance.data["representations"].remove(repre) + instance.data["representations"].extend(new_representations) + def _rename_in_representation(self, new_repre, files_to_convert, output_name, output_extension): """Replace old extension with new one everywhere in representation. From 73cca8299506e95b2ec515e6ac085b85a9b2049d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 15 Feb 2023 16:24:53 +0100 Subject: [PATCH 045/227] OP-4642 - refactored query of representations line 73 returns if no representations. --- openpype/plugins/publish/extract_color_transcode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 8c4ef59de9..de36ea7d5f 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -83,7 +83,7 @@ class ExtractOIIOTranscode(publish.Extractor): return new_representations = [] - repres = instance.data.get("representations") or [] + repres = instance.data["representations"] for idx, repre in enumerate(list(repres)): self.log.debug("repre ({}): `{}`".format(idx + 1, repre["name"])) if not self._repre_is_valid(repre): From 78933eb56259045a27c1c56f06a31dfb39f38e37 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 10:44:10 +0100 Subject: [PATCH 046/227] OP-4643 - fixed subset filtering Co-authored-by: Toke Jepsen --- openpype/plugins/publish/extract_color_transcode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index de36ea7d5f..71124b527a 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -273,7 +273,7 @@ class ExtractOIIOTranscode(publish.Extractor): "families": family, "task_names": task_name, "task_types": task_type, - "subset": subset + "subsets": subset } profile = filter_profiles(self.profiles, filtering_criteria, logger=self.log) From deaad39437501f18fc3ba4be8b1fc5f0ee3be65d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 12:12:35 +0100 Subject: [PATCH 047/227] OP-4643 - split command line arguments to separate items Reuse existing method from ExtractReview, put it into transcoding.py --- openpype/lib/transcoding.py | 29 +++++++++++++++++++++- openpype/plugins/publish/extract_review.py | 27 +++----------------- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 95042fb74c..a87300c280 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1092,7 +1092,7 @@ def convert_colorspace( raise ValueError("Both screen and display must be set.") if additional_command_args: - oiio_cmd.extend(additional_command_args) + oiio_cmd.extend(split_cmd_args(additional_command_args)) if target_colorspace: oiio_cmd.extend(["--colorconvert", @@ -1106,3 +1106,30 @@ def convert_colorspace( logger.debug("Conversion command: {}".format(" ".join(oiio_cmd))) run_subprocess(oiio_cmd, logger=logger) + + +def split_cmd_args(in_args): + """Makes sure all entered arguments are separated in individual items. + + Split each argument string with " -" to identify if string contains + one or more arguments. + Args: + in_args (list): of arguments ['-n', '-d uint10'] + Returns + (list): ['-n', '-d', 'unint10'] + """ + splitted_args = [] + for arg in in_args: + sub_args = arg.split(" -") + if len(sub_args) == 1: + if arg and arg not in splitted_args: + splitted_args.append(arg) + continue + + for idx, arg in enumerate(sub_args): + if idx != 0: + arg = "-" + arg + + if arg and arg not in splitted_args: + splitted_args.append(arg) + return splitted_args diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index 0f6dacba18..e80141fc4a 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -22,6 +22,7 @@ from openpype.lib.transcoding import ( should_convert_for_ffmpeg, convert_input_paths_for_ffmpeg, get_transcode_temp_directory, + split_cmd_args ) @@ -670,7 +671,7 @@ class ExtractReview(pyblish.api.InstancePlugin): res_filters = self.rescaling_filters(temp_data, output_def, new_repre) ffmpeg_video_filters.extend(res_filters) - ffmpeg_input_args = self.split_ffmpeg_args(ffmpeg_input_args) + ffmpeg_input_args = split_cmd_args(ffmpeg_input_args) lut_filters = self.lut_filters(new_repre, instance, ffmpeg_input_args) ffmpeg_video_filters.extend(lut_filters) @@ -723,28 +724,6 @@ class ExtractReview(pyblish.api.InstancePlugin): ffmpeg_output_args ) - def split_ffmpeg_args(self, in_args): - """Makes sure all entered arguments are separated in individual items. - - Split each argument string with " -" to identify if string contains - one or more arguments. - """ - splitted_args = [] - for arg in in_args: - sub_args = arg.split(" -") - if len(sub_args) == 1: - if arg and arg not in splitted_args: - splitted_args.append(arg) - continue - - for idx, arg in enumerate(sub_args): - if idx != 0: - arg = "-" + arg - - if arg and arg not in splitted_args: - splitted_args.append(arg) - return splitted_args - def ffmpeg_full_args( self, input_args, video_filters, audio_filters, output_args ): @@ -764,7 +743,7 @@ class ExtractReview(pyblish.api.InstancePlugin): Returns: list: Containing all arguments ready to run in subprocess. """ - output_args = self.split_ffmpeg_args(output_args) + output_args = split_cmd_args(output_args) video_args_dentifiers = ["-vf", "-filter:v"] audio_args_dentifiers = ["-af", "-filter:a"] From 83cb0b0b04a59a5f4acffb05659a893f2318c91c Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 13:02:41 +0100 Subject: [PATCH 048/227] OP-4643 - refactor - changed existence check --- openpype/plugins/publish/extract_color_transcode.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 71124b527a..456e40008d 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -161,12 +161,12 @@ class ExtractOIIOTranscode(publish.Extractor): custom_tags = output_def.get("custom_tags") if custom_tags: - if not new_repre.get("custom_tags"): + if new_repre.get("custom_tags") is None: new_repre["custom_tags"] = [] new_repre["custom_tags"].extend(custom_tags) # Add additional tags from output definition to representation - if not new_repre.get("tags"): + if new_repre.get("tags") is None: new_repre["tags"] = [] for tag in output_def["tags"]: if tag not in new_repre["tags"]: From 909f51b702825be0fe23fd946023279787d28e1c Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 13:10:11 +0100 Subject: [PATCH 049/227] OP-4643 - changed label MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jakub Ježek --- .../schemas/projects_schema/schemas/schema_global_publish.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 5333d514b5..3e9467af61 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 @@ -294,7 +294,7 @@ "children": [ { "key": "additional_command_args", - "label": "Additional command line arguments", + "label": "Arguments", "type": "list", "object_type": "text" } From 71013e45052bb0775cc80e799cf4556a935372ef Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 13:11:11 +0100 Subject: [PATCH 050/227] Revert "Fix - added missed scopes for Slack bot" This reverts commit 5e0c4a3ab1432e120b8f0c324f899070f1a5f831. --- openpype/modules/slack/manifest.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/modules/slack/manifest.yml b/openpype/modules/slack/manifest.yml index 233c39fbaf..7a65cc5915 100644 --- a/openpype/modules/slack/manifest.yml +++ b/openpype/modules/slack/manifest.yml @@ -19,8 +19,6 @@ oauth_config: - chat:write.public - files:write - channels:read - - users:read - - usergroups:read settings: org_deploy_enabled: false socket_mode_enabled: false From 12ba88e9573a3c33184264e2b6c86468a26b3a3b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 15:06:16 +0100 Subject: [PATCH 051/227] OP-4643 - added documentation --- .../assets/global_oiio_transcode.png | Bin 0 -> 29010 bytes .../project_settings/settings_project_global.md | 15 +++++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 website/docs/project_settings/assets/global_oiio_transcode.png diff --git a/website/docs/project_settings/assets/global_oiio_transcode.png b/website/docs/project_settings/assets/global_oiio_transcode.png new file mode 100644 index 0000000000000000000000000000000000000000..99396d5bb3f16d434a92c6079515128488b82489 GIT binary patch literal 29010 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 literal 0 HcmV?d00001 diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index 37fed93e69..52671d2db6 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -45,6 +45,21 @@ The _input pattern_ matching uses REGEX expression syntax (try [regexr.com](http The **colorspace name** value is a raw string input and no validation is run after saving project settings. We recommend to open the specified `config.ocio` file and copy pasting the exact colorspace names. ::: +### Extract OIIO Transcode +There is profile configurable (see lower) plugin which allows to transcode any incoming representation to one or multiple new representations (configured in `Output Definitions`) with different target colorspaces. +Plugin expects instances with filled dictionary `colorspaceData` on a representation. This data contains information about source colorspace and must be collected for transcoding. +`oiiotool` is used for transcoding, eg. `oiiotool` must be present in `vendor/bin/oiio` or environment variable `OPENPYPE_OIIO_PATHS` must be provided for custom oiio installation. + +Notable parameters: +- **`Delete Original Representation`** - keep or remove original representation. If old representation is kept, but there is new transcoded representation with 'Create review' tag, original representation looses its 'review' tag if present. +- **`Extension`** - target extension, could be empty - original extension is used +- **`Colorspace`** - target colorspace - must be available in used color config +- **`Display & View`** - transcoding into colorspace OR into display and viewer space could be used. (It is disjunctive: Colorspace & nothing in Display and View or opposite) +- **`Arguments`** - special additional command line arguments for `oiiotool` + + +Example here describes use case for creation of new color coded review of png image sequence. Original representation's files are kept intact, review is created from transcoded files, but these files are removed in cleanup process. +![global_oiio_transcode](assets/global_oiio_transcode.png) ## Profile filters From f1718284f9245dd24160d8975061d90cc6f8c11e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 16:41:42 +0100 Subject: [PATCH 052/227] OP-4643 - updates to documentation Co-authored-by: Toke Jepsen --- website/docs/project_settings/settings_project_global.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index 52671d2db6..cc661a21fa 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -46,7 +46,7 @@ The **colorspace name** value is a raw string input and no validation is run aft ::: ### Extract OIIO Transcode -There is profile configurable (see lower) plugin which allows to transcode any incoming representation to one or multiple new representations (configured in `Output Definitions`) with different target colorspaces. +There is profile configurable plugin which allows to transcode any incoming representation to one or multiple new representations (configured in `Output Definitions`) with different target colorspaces. Plugin expects instances with filled dictionary `colorspaceData` on a representation. This data contains information about source colorspace and must be collected for transcoding. `oiiotool` is used for transcoding, eg. `oiiotool` must be present in `vendor/bin/oiio` or environment variable `OPENPYPE_OIIO_PATHS` must be provided for custom oiio installation. From 337e695c17d1d5314ffac99a83330f875053703a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 16:42:06 +0100 Subject: [PATCH 053/227] OP-4643 - updates to documentation Co-authored-by: Toke Jepsen --- website/docs/project_settings/settings_project_global.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index cc661a21fa..8e557a381c 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -52,7 +52,7 @@ Plugin expects instances with filled dictionary `colorspaceData` on a representa Notable parameters: - **`Delete Original Representation`** - keep or remove original representation. If old representation is kept, but there is new transcoded representation with 'Create review' tag, original representation looses its 'review' tag if present. -- **`Extension`** - target extension, could be empty - original extension is used +- **`Extension`** - target extension. If left empty, original extension is used. - **`Colorspace`** - target colorspace - must be available in used color config - **`Display & View`** - transcoding into colorspace OR into display and viewer space could be used. (It is disjunctive: Colorspace & nothing in Display and View or opposite) - **`Arguments`** - special additional command line arguments for `oiiotool` From 931d0002ec80b0cabd1bbc0b1d74cbdb06ab2d88 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 16:42:29 +0100 Subject: [PATCH 054/227] OP-4643 - updates to documentation Co-authored-by: Toke Jepsen --- website/docs/project_settings/settings_project_global.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index 8e557a381c..166400cb7f 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -53,7 +53,7 @@ Plugin expects instances with filled dictionary `colorspaceData` on a representa Notable parameters: - **`Delete Original Representation`** - keep or remove original representation. If old representation is kept, but there is new transcoded representation with 'Create review' tag, original representation looses its 'review' tag if present. - **`Extension`** - target extension. If left empty, original extension is used. -- **`Colorspace`** - target colorspace - must be available in used color config +- **`Colorspace`** - target colorspace, which must be available in used color config. - **`Display & View`** - transcoding into colorspace OR into display and viewer space could be used. (It is disjunctive: Colorspace & nothing in Display and View or opposite) - **`Arguments`** - special additional command line arguments for `oiiotool` From 14a8a1449a6e46d82192dece2fb08c8aa807a032 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 16:42:48 +0100 Subject: [PATCH 055/227] OP-4643 - updates to documentation Co-authored-by: Toke Jepsen --- website/docs/project_settings/settings_project_global.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index 166400cb7f..908191f122 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -54,7 +54,7 @@ Notable parameters: - **`Delete Original Representation`** - keep or remove original representation. If old representation is kept, but there is new transcoded representation with 'Create review' tag, original representation looses its 'review' tag if present. - **`Extension`** - target extension. If left empty, original extension is used. - **`Colorspace`** - target colorspace, which must be available in used color config. -- **`Display & View`** - transcoding into colorspace OR into display and viewer space could be used. (It is disjunctive: Colorspace & nothing in Display and View or opposite) +- **`Display & View`** - transcoding into colorspace or into display and viewer space could be used. Cannot use both `Colorspace` and `Display & View` at the same time. - **`Arguments`** - special additional command line arguments for `oiiotool` From cb7b8d423e1591a9d0995dfba9d3cb697c551a57 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 16:43:06 +0100 Subject: [PATCH 056/227] OP-4643 - updates to documentation Co-authored-by: Toke Jepsen --- website/docs/project_settings/settings_project_global.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index 908191f122..0a73868d2d 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -55,7 +55,7 @@ Notable parameters: - **`Extension`** - target extension. If left empty, original extension is used. - **`Colorspace`** - target colorspace, which must be available in used color config. - **`Display & View`** - transcoding into colorspace or into display and viewer space could be used. Cannot use both `Colorspace` and `Display & View` at the same time. -- **`Arguments`** - special additional command line arguments for `oiiotool` +- **`Arguments`** - special additional command line arguments for `oiiotool`. Example here describes use case for creation of new color coded review of png image sequence. Original representation's files are kept intact, review is created from transcoded files, but these files are removed in cleanup process. From 748d8989496d6cbd4a5348fb9a78e75617a69cab Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 17:21:20 +0100 Subject: [PATCH 057/227] OP-4643 - updates to documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jakub Ježek --- website/docs/project_settings/settings_project_global.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index 0a73868d2d..9e2ee187cc 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -46,8 +46,8 @@ The **colorspace name** value is a raw string input and no validation is run aft ::: ### Extract OIIO Transcode -There is profile configurable plugin which allows to transcode any incoming representation to one or multiple new representations (configured in `Output Definitions`) with different target colorspaces. -Plugin expects instances with filled dictionary `colorspaceData` on a representation. This data contains information about source colorspace and must be collected for transcoding. +OIIOTools transcoder plugin with configurable output presets. Any incoming representation with `colorspaceData` is convertable to single or multiple representations with different target colorspaces or display and viewer names found in linked **config.ocio** file. + `oiiotool` is used for transcoding, eg. `oiiotool` must be present in `vendor/bin/oiio` or environment variable `OPENPYPE_OIIO_PATHS` must be provided for custom oiio installation. Notable parameters: From 421185f5216fd50fc11c6b1e04f80a5e2454ab48 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 17:56:21 +0100 Subject: [PATCH 058/227] Revert "OP-4643 - split command line arguments to separate items" This reverts commit deaad39437501f18fc3ba4be8b1fc5f0ee3be65d. --- openpype/lib/transcoding.py | 29 +--------------------- openpype/plugins/publish/extract_review.py | 27 +++++++++++++++++--- 2 files changed, 25 insertions(+), 31 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index a87300c280..95042fb74c 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1092,7 +1092,7 @@ def convert_colorspace( raise ValueError("Both screen and display must be set.") if additional_command_args: - oiio_cmd.extend(split_cmd_args(additional_command_args)) + oiio_cmd.extend(additional_command_args) if target_colorspace: oiio_cmd.extend(["--colorconvert", @@ -1106,30 +1106,3 @@ def convert_colorspace( logger.debug("Conversion command: {}".format(" ".join(oiio_cmd))) run_subprocess(oiio_cmd, logger=logger) - - -def split_cmd_args(in_args): - """Makes sure all entered arguments are separated in individual items. - - Split each argument string with " -" to identify if string contains - one or more arguments. - Args: - in_args (list): of arguments ['-n', '-d uint10'] - Returns - (list): ['-n', '-d', 'unint10'] - """ - splitted_args = [] - for arg in in_args: - sub_args = arg.split(" -") - if len(sub_args) == 1: - if arg and arg not in splitted_args: - splitted_args.append(arg) - continue - - for idx, arg in enumerate(sub_args): - if idx != 0: - arg = "-" + arg - - if arg and arg not in splitted_args: - splitted_args.append(arg) - return splitted_args diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index e80141fc4a..0f6dacba18 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -22,7 +22,6 @@ from openpype.lib.transcoding import ( should_convert_for_ffmpeg, convert_input_paths_for_ffmpeg, get_transcode_temp_directory, - split_cmd_args ) @@ -671,7 +670,7 @@ class ExtractReview(pyblish.api.InstancePlugin): res_filters = self.rescaling_filters(temp_data, output_def, new_repre) ffmpeg_video_filters.extend(res_filters) - ffmpeg_input_args = split_cmd_args(ffmpeg_input_args) + ffmpeg_input_args = self.split_ffmpeg_args(ffmpeg_input_args) lut_filters = self.lut_filters(new_repre, instance, ffmpeg_input_args) ffmpeg_video_filters.extend(lut_filters) @@ -724,6 +723,28 @@ class ExtractReview(pyblish.api.InstancePlugin): ffmpeg_output_args ) + def split_ffmpeg_args(self, in_args): + """Makes sure all entered arguments are separated in individual items. + + Split each argument string with " -" to identify if string contains + one or more arguments. + """ + splitted_args = [] + for arg in in_args: + sub_args = arg.split(" -") + if len(sub_args) == 1: + if arg and arg not in splitted_args: + splitted_args.append(arg) + continue + + for idx, arg in enumerate(sub_args): + if idx != 0: + arg = "-" + arg + + if arg and arg not in splitted_args: + splitted_args.append(arg) + return splitted_args + def ffmpeg_full_args( self, input_args, video_filters, audio_filters, output_args ): @@ -743,7 +764,7 @@ class ExtractReview(pyblish.api.InstancePlugin): Returns: list: Containing all arguments ready to run in subprocess. """ - output_args = split_cmd_args(output_args) + output_args = self.split_ffmpeg_args(output_args) video_args_dentifiers = ["-vf", "-filter:v"] audio_args_dentifiers = ["-af", "-filter:a"] From 7e15a91217d4d74d2cddfe6d219742d5146ba52d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 18:02:17 +0100 Subject: [PATCH 059/227] OP-4643 - different splitting for oiio It seems that logic in ExtractReview does different thing. --- openpype/lib/transcoding.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 95042fb74c..8a80e88d3a 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1092,7 +1092,7 @@ def convert_colorspace( raise ValueError("Both screen and display must be set.") if additional_command_args: - oiio_cmd.extend(additional_command_args) + oiio_cmd.extend(split_cmd_args(additional_command_args)) if target_colorspace: oiio_cmd.extend(["--colorconvert", @@ -1106,3 +1106,21 @@ def convert_colorspace( logger.debug("Conversion command: {}".format(" ".join(oiio_cmd))) run_subprocess(oiio_cmd, logger=logger) + + +def split_cmd_args(in_args): + """Makes sure all entered arguments are separated in individual items. + + Split each argument string with " -" to identify if string contains + one or more arguments. + Args: + in_args (list): of arguments ['-n', '-d uint10'] + Returns + (list): ['-n', '-d', 'unint10'] + """ + splitted_args = [] + for arg in in_args: + if not arg.strip(): + continue + splitted_args.extend(arg.split(" ")) + return splitted_args From 0871625951f2054392ba74cbe6ba5ea47b36d44b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 18:14:57 +0100 Subject: [PATCH 060/227] OP-4643 - allow colorspace to be empty and collected from DCC --- openpype/plugins/publish/extract_color_transcode.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 456e40008d..82b92ec93e 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -118,7 +118,8 @@ class ExtractOIIOTranscode(publish.Extractor): output_name, output_extension) - target_colorspace = output_def["colorspace"] + target_colorspace = (output_def["colorspace"] or + colorspace_data.get("colorspace")) view = output_def["view"] or colorspace_data.get("view") display = (output_def["display"] or colorspace_data.get("display")) From 2563d302fa0f9b1140b226e7e992b70bae403430 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 22 Feb 2023 12:22:53 +0100 Subject: [PATCH 061/227] OP-4643 - fix colorspace from DCC representation["colorspaceData"]["colorspace"] is only input colorspace --- openpype/plugins/publish/extract_color_transcode.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 82b92ec93e..456e40008d 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -118,8 +118,7 @@ class ExtractOIIOTranscode(publish.Extractor): output_name, output_extension) - target_colorspace = (output_def["colorspace"] or - colorspace_data.get("colorspace")) + target_colorspace = output_def["colorspace"] view = output_def["view"] or colorspace_data.get("view") display = (output_def["display"] or colorspace_data.get("display")) From 36b7fa32df20c9640ccda6a81ed61766017d607c Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 22 Feb 2023 17:33:20 +0100 Subject: [PATCH 062/227] OP-4643 - added explicit enum for transcoding type As transcoding info (colorspace, display) might be collected from DCC, it must be explicit which should be used. --- openpype/lib/transcoding.py | 2 +- .../plugins/publish/extract_color_transcode.py | 15 +++++++++++---- .../schemas/schema_global_publish.json | 9 +++++++++ 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 8a80e88d3a..42db374402 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1044,7 +1044,7 @@ def convert_colorspace( output_path, config_path, source_colorspace, - target_colorspace, + target_colorspace=None, view=None, display=None, additional_command_args=None, diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 456e40008d..b0921688e9 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -118,10 +118,17 @@ class ExtractOIIOTranscode(publish.Extractor): output_name, output_extension) - target_colorspace = output_def["colorspace"] - view = output_def["view"] or colorspace_data.get("view") - display = (output_def["display"] or - colorspace_data.get("display")) + transcoding_type = output_def["transcoding_type"] + + target_colorspace = view = display = None + if transcoding_type == "colorspace": + target_colorspace = (output_def["colorspace"] or + colorspace_data.get("colorspace")) + else: + view = output_def["view"] or colorspace_data.get("view") + display = (output_def["display"] or + colorspace_data.get("display")) + # both could be already collected by DCC, # but could be overwritten if view: 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 3e9467af61..76574e8b9b 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 @@ -271,6 +271,15 @@ "label": "Extension", "type": "text" }, + { + "type": "enum", + "key": "transcoding_type", + "label": "Transcoding type", + "enum_items": [ + { "colorspace": "Use Colorspace" }, + { "display": "Use Display&View" } + ] + }, { "key": "colorspace", "label": "Colorspace", From 90afe4185110f2571242007f88566be7dce78dff Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 22 Feb 2023 17:34:03 +0100 Subject: [PATCH 063/227] OP-4643 - added explicit enum for transcoding type As transcoding info (colorspace, display) might be collected from DCC, it must be explicit which should be used. --- .../assets/global_oiio_transcode.png | Bin 29010 -> 17936 bytes .../settings_project_global.md | 5 +++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/website/docs/project_settings/assets/global_oiio_transcode.png b/website/docs/project_settings/assets/global_oiio_transcode.png index 99396d5bb3f16d434a92c6079515128488b82489..d818ecfe19f93366e5f4664734d68c7b448d7b4f 100644 GIT binary patch literal 17936 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 DbV~re literal 29010 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 diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index 9e2ee187cc..6e78ee5d45 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -53,8 +53,9 @@ OIIOTools transcoder plugin with configurable output presets. Any incoming repre Notable parameters: - **`Delete Original Representation`** - keep or remove original representation. If old representation is kept, but there is new transcoded representation with 'Create review' tag, original representation looses its 'review' tag if present. - **`Extension`** - target extension. If left empty, original extension is used. -- **`Colorspace`** - target colorspace, which must be available in used color config. -- **`Display & View`** - transcoding into colorspace or into display and viewer space could be used. Cannot use both `Colorspace` and `Display & View` at the same time. +- **`Transcoding type`** - transcoding into colorspace or into display and viewer space could be used. Cannot use both at the same time. +- **`Colorspace`** - target colorspace, which must be available in used color config. (If `Transcoding type` is `Use Colorspace` value in configuration is used OR if empty value collected on instance from DCC). +- **`Display & View`** - display and viewer colorspace. (If `Transcoding type` is `Use Display&View` values in configuration is used OR if empty values collected on instance from DCC). - **`Arguments`** - special additional command line arguments for `oiiotool`. From 1594d7753728820b4d84568c7c5fe80aaa0ebbab Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 22 Feb 2023 17:42:07 +0100 Subject: [PATCH 064/227] OP-4643 - added use case for Maya to documentation --- .../assets/global_oiio_transcode2.png | Bin 0 -> 17960 bytes .../project_settings/settings_project_global.md | 3 +++ 2 files changed, 3 insertions(+) create mode 100644 website/docs/project_settings/assets/global_oiio_transcode2.png diff --git a/website/docs/project_settings/assets/global_oiio_transcode2.png b/website/docs/project_settings/assets/global_oiio_transcode2.png new file mode 100644 index 0000000000000000000000000000000000000000..906f780830a96b4bc6f26d98484dd3f9cc3885f2 GIT binary patch literal 17960 zcmch{OO{7Z? zy-ShaJE7#e0X=8V%ri6Rogd#1O7^|`z1LdTy4Kq9S5lBAxdOfd0)a^6o=B;HKzQ`P z5C3I6pycRM4mI%Sf}@J81Sq%T_5$$XqPaLs90bY_AwDv^1bim4d!pqC0$r;+{khO& zn{EOWUVkaA`BK%^?4^sLgDFVX!PL~o(ZcqnzS?D=iFBfzl=w4O{gpB67c>L$Ne3^C z&8Z8kxXIhvR+T;Rl=f_@*>q* zo|i+XfVt(vyGUc%F&jm$3d1S5IUb7e5rGXM@m~Z2!wNU*mp(tTc%Gz)zYMAfIPN+W z-Emm#&B#3b?hzoQ<{3EQ=g}#ww)d&Jls4YTAKNy^=S2kaIZ^_H#HyJ|Kz9rl&_nr- z&+XQ#^Mr)1f_z-<_QuEKNo|H5YWf1S2hl^7XqP=nNu83Nv7LsMeo2rIh1FL7c#iP# z_4gBN3#GJ~KOVII1S)u7vw;n(7uNA0+!Zk)QKw*Q501w#Kt7TMfgZM4N*NiFsNZC3 z|2ggz2f+tDv)iLAT^p|G1ZT6WBY~k`j9&uX$r#r>otW3z#KH+cA334;pk_+6c>W5N zh}~k_K@?{VKlkC+kJJtt1Uc{s8lj_JVoLp*?Z&+al_w>?i$OR2Fi8)Z1m|W42>5QQ z@5R!~ThHYtM?KIJ6xhhI79*0|H0@%RmhPt z?&lL+u55TMS-Hn%x#Q-?r(bw4C~-nny{(vRi`%FeLv+X&C!z3bLBjkSZN#x9+uGAe zAqMhmL2BAjLiU^6iPhV~_#U`Bm!G}{iW45YH4gtJrpF9Bkb@7)?CJQD^q{o}KE=>0 zidd7Reu8V|hqlGJ?#4w#KH~}+1O+8Qenn{`cTmLc5k36Nq^kzPgq6(sylvz1>l?2jHCWyR%)C%$l)8j9K(qkK@An+uUI`ph*nbKt14qX6m+#)KO{ z;VIl^640(dOmiHR=|`ZW6^ZM?W^DJRc6$maeQf1p!L&7f_h}EHpqTo$o71Xw6*;IF z1#GK6^2?R+sZ=VooV6Uzdn|!f^g$-9;66QJ%_`sQq3B7*x@~I`Py6Wl_a$2$3jU+| zo|B0oNJ|nAjNs4U!*})7?g}+IdG0(_g-%agbEejXJx@@vYD8Z!d*~TXU`K}*54GF9 z%6t#%dvEdsDCs>j$%p!iA8y0o?LM~%M_3Kbw|cdXZz;;)8&?sI)l2>cS|B(=v}wYdFTjDU?bh>PFKbBLsN7^uW# z-y|dz-8m}H%Imm7)KYsJc*s$B(8$MDxH+uMEaw%S4JT*~#DG>&5ayCz%!|cCs6sXqo)R(C z*QgL`{Pr%>EH_^I2Tal+Gs#a8sH*mw7 z_+&D};5=E7mgC33B7t7fYB`W5!JUanWOh4fP7`6;T5Zx`)H%@N)zAT?zqCbK9tf8dHC z{PjqtocoBoJSyS7rA|9qnfOjs#C4I3V|Vt=kN<3-g?3vhW|Nz}c%T~#?D%vp6z$J( z=ZzLP(bBiZ%?s_{5$!K90Etitm(FYYq>NnPvpM-$?npOQc95~(QZ6cTM7j75F_Ks) z==rSj(Or7P(Wv4vb%H9^)7)uWxR_?;H2;H7%&4Edfx5^oi4gAu!tC5e74|nqQ zZF6b+4BwkXF5Ol5RP2Nyiq==V-xisJE@(tK+2Lr^-he2>B6}d7%xqY zEHYhEQd=A<%x=33RlP+aPv0A=cjCh8{>U)lJI0rlVEjoo9~JtM^9ZjyAe1X;vRZ$x zkkUq|m$3cmx@FXq&Vfqx zEtL#h*Q%ZuH_pGO7m5zd45%q4{$#YZ81FNP0WHts(rcUZ$Btj3IT#^q#=aRsi1rcGp3NsNV z^U(%$gN`K}1RUq2L4gpj(&;Rw>{1p`TCW;%S2k6k`e5!{_;SW@FP9Dsva6?w`(r75 z1T8s(Oj3W1AZpAkz8^)ag4qiNOShe#o;>Lv20_Ktxvl(G3}_YqXB|+>HPTghSkgkL zO$o(gP7k)NEH=aSxY#UkpUG|Zaeau5#}v&HC*pz~bOqs92ux196KCBt6VevKkFMnk z6TPUl_6(FextEZ9F2TM=D2UJDKbD%_deX;DoOPH}`%4NA+EYeGSnLFPnHsjXHSGAq z;h7O;uGQ8rYlNej+&20>ZunR+w?myP9dpMoK_f8P@s)rQ(*%kD=z;CNR9v?4@*1J9 zO7MP?GV&zA+A%*jI!uSr;8R4{LHclwHou$4E%gz1?sl7&B_iHV?+d(CrQ;}&t|VN& zAdshh-LrM{NE`RgOPzx~C@N+qS+pe8-}(XdqPjHGQe~xE=dU2bk<57WDiAgL!l*3J zD7(;{Yn5hR)nO`1hC@U}2*%nra2QJqsgLApJ*Znx$b|u{fxR}e*kZvK9eec?ut!XF~hG*w?f5hi|i`-c*Z@tJ=j6gISP(ty`LfG&=vh_vmYXR^t`*An{|3*hn`+Y>D zB;1DrfOda-Oqh938~%3>OFE1vR`wT(maMOQV{wW|Jr2zXwmm67c$5awG_%?h9dkdS z%v#%jy<>LiXYjB*eGL3ycOs7p`S9!%F9Y_ACQS5GF|W_{k9}>!hKEu0s{gJ<{DZU4 z4X3ZsDN^c<+2S^zW z)$#q6?>NvUAC*fJzu#X%gP%B2$#H9<61GYkA06pu@;V{Tbmr%NgVkD`=jze8>`6Mp z?lWS5{>RVvqrwNH{BBPeHDf{PlxX|0MXEy$bV-}8Zeny~Y8#p#vO81H&G}*&V-;it zfZfLsb-3PUeM=7li?JeIKK8xykO(MtotmMtk_0&=L!|9bo(+BwN`PP>z_F&Ufj`il=|^}N{z?ze+IP7 zT>X%`t#SK_3d2M-V)KCl zA|b2Wrvca{GPWSM*KE4Z`;NU&TqB>-tX2_a`2S#s4R=^E2Wf*Aa>Cb5HS;p>rgg+= zXdv%5zC}b7^2~?96od{7st>=v9q5ZN^N)PS7%PNW%^WB48CFK7JcN?R_ii@rSSMz_ zcF~NY9%sxy$cM9_P;ua+?kF8cCt6`fC;nSruPQ7>AW zYx53+4o*T4tVbhU7BFtU%606By7V>CjUt>2FAOLO6FA`skM8wIR zy3(R4A-2~8VC=N&50~Scn9RdvnggyH_~I;8_0=TXp6{RGHUKIwQz=P>6G(rjxFALb zU}Jxmh^-=?`K&G0Ii$CskAGI&D`@}&-a46OR05R3r8-;Gg)4wMa0T2jwJqC0p@?H@ z-p<}5YF>j$z883a7sbr5;K#1c8YdIxXChII;Q1N&=M!Gc1+Wxc>6zd$f@o#G=qtD# z8`Tv&aKM=?oVW}8Lg>>@eWvPq3}DPtd95$xta&UrXYh+15LY*}7RNPO??u2LF^DHV z=|~;@SfL9ifDohq4iNvV_@8;R|8BLLwsm?sg2C10i{INL%XPk88E~FODTm8l3a>D3*twDTkC8t;JZk z)tGn*&?HQZQs4)GEgPBl_V_EPd~2062YLM2%Wf3=vZ zJNFjDPN;@FDDMB6IvsE3ox-nzeA|a|C%Mrc`ey#Ea`e3+xfk_uJzQ?16J66Q&YB4g z2*~gXV4|z!%5Djy#D3$ggZIScrmSHbRpcZwpP7%nmXxfNS_X*#yFWei2$11P1g zutIisrW{J|2089KY6Gd`{aZB}Oiia%!J( zaj@}{tiHj3D3riW=}x|M(O`Mw@d((n+}9t|^$vEf>`(7RHVS!|<-)gTwlO9`7036e zOB@G2W^-rPUNLdlys9}Z7@F6NwdhN6!TM4U=y0@;l! z`bXYLM#~H@-~jKhfWRvxgqFNNX>EQ3 zz9ms`MKI&ruv{OeIr*q+;eu8DW6wD)q~t(OnnE`MTeWNo+ibuG%+0m;i54H|R_kh& zw=Rp+@D4NRc0~*N-z2Zi$Q*oHHKJ+D1)UB$^st~7&P@<_#E*j9+gM)Ubl6ac=g)g^ zT|ST)hi7Wl#S{xrET&$i-xXNDINvh}-RZv4^1VUluiIZA+*J?V2JNQ$&RHrIeIX?}s4pIO$c_l5Jn9;D+)!KXZs4nUlBF57=%7J}Rj?vuRtY8@ zKB<{08 zRbWkbkJvIEFlyR(6E6KUBC*Fph0M`R>4sx!DIW@=F7eD`JS6whdxAQqOyG>2?n-PH z!ckTIs=q{i!@ysdcS|ACb3fYftA^D)M@juR$F~iOlSO+DV?Ky_I|&GQ+zeVu+mZ?F zAae9qJw7`!%q4g@Ua(UcsqvS3qq&Lip)+uSBspMSYh8n?JUE9o)u9^0!sK}NR0w>o zI8tgeFiyL^KI*X;w=y{V)orY4yz=DLXn?yb;F`8FVG3Yt|3s`D5Ny*23Ws~6?J+&459YWj-zj-*m)tzpR$~ytLVH27<5)9HKEh>o;$%nB+j+Wi;%GB0@%X55 z1hEqpLQ5BwlYI=b`wLyWS>bUQT-$>%K}(O|`pKhki}5w(2Z(WiQIM0-Kd#k@^*GF~ zx?o3g>Jd7%tS+}*ZVBwW)pI~s+3EtNy1asC?4WxTx zOmJ00$Xksi=yvK7GhjfMl~#p4ihFr9+~Fyl;#J>gZ^y^xM%p@g;RG!sorrwa%=v6I z_2jTaowk~X7k`Ee$fCcH?BB{wM@FH0bQXL3CM8V3!eG+yQXOH?3DByU`3soW&>U`n+#$g3i7 zz&Pl$sPv{z{L$idM@&uRI>m=J+xP}(z%N_x1OL7Vh8W`&avxQP2SF}ZH)VPyYaeOQ zK%78F_)3eYenSB-1+gH85L0*Dc=AJm%c;V7E+db_Wy!bSE$&mVFx*&*IR>|J)(#5o zSjh=#6zMEoP8d|z_dWh@>G5IR`AEn5;Ys#iiD6jbqMF9uxq>gL7oePLm^bwKyz}tM zZr0=O{bUYfg<%v@K*mqeq(jk5f6J9dFi?itOe8=wz4SwK)L%!26WP+(0V6MEcUuSg zC7w62>Eezf!wKJ`!?cj|S*^^lW&gm9<+*}&M{v@1`|B`c~|ZkVaB# zDo4CUv01vGPqFD~w)IV@B3Bb8o~TA0$9c+WQti%^dCt4Le%S522JGrW#m1PKH=PO6 zzBDrJVfF1S2;4wV2A;iUNHQTnJdkh!9pwn`-RKakmLjxQb$td!K)fwlqsgH{#a*2? zG}-NQJfw(j@xtLruHiya{vE9w)AA3UBTkAn4PKAyc$V^b()DupZ{BE(qB~SiLpYtX zQ`f0Gy&tp!0+h8rze?*z8WaY02>T z8@m0`!olvvE&uF_ZV^G*9RV@LW&vFp1Ng}NMj!V{(hfT`8*-u^V-O(p^^5znUqiRH z&9y6FFP5Z*|B|^k&B7IYl5gv@sA1mz$UqG;;kvs#2c=*7v{{aFnaBx2tW zafcUlr53~4N<^0hAth9EEcT0Q&m~ z%%YGz^=s$2WsK@@Q^#1cO-)5@0(piK_+V}AWK|T`)9GEkr~rD-663`D6wWJxwefI$ zX~TbLzlS2sCR!lKE$a00Y!Wl7d=MkZ|C45-IAkO(_eJ_-Id8DnvKPggvtm_+p_ucg z{oxQBHF*!D5Q1B&Ii5HPLR6l#*6eYPn2JwpsX8%%zJ#|4+(1EUleN@Ai}Sa~X|rgl zZR=#@!>YoPAS%6^+@%;v=t-h z@%L=WRdl)iRu6-t1WD@Gsv;>rv*PBDWH^&hoQ3ODGotyhr%Ic^jT&3YCQ+WfuZVP3UMi_uw+7Npc zILE!l!;=m3weSkWo(t}PZfsXNhTKc9dnu%&V_~j+xO}hc)jIV(OMOl}G2X&!^7*N7 zcJBOX@5i^;87p^9WF=_kt0dGyi(tJyEdnjEjMg79t}zE1I3r8-)Z;?juT|W3;)#oY`e)Zb@@lH1HW#aLEFp^RS^synkP9u5RVpjZ6rYrpSN8;T+$CxSIwojPeJ zDA5&1>b1i$M7b7pE5Km%hz^n0FfMoGMn1vpF}G;7%@-QgZb={(7VV^&f_X1(CQ~1B zn1JkuIJmUPhdrKZI*>iEM0X2946K(aPQr>08MrOAwnU4y1s_#2^KftQq^|1fe?q3l zdGW2VUGl*K-F(GBzKKdLN;+!39_;3Sd{KuuX&dtmOY#n?;8i5eljU3cX1Oy|u;WC+gkFx}xr;btqMrz$!LH z%bU5O7!yyu6Vz1zw)c%1%7=+>#M=jlNY_zp(R0SS)rKE2)%C9Cbn*_I9FJn zBgoz>{^g_n%nL~u%xJEtA{qVZLN9<09s;5c0=1j}(Lr@kHWEN&PxT1MXZBR{{Ku#N z&`^N-69H5L0jSFpP$eXwq=PoVkpqcs#r&s+$ua05yFD)u=yKt;2>C*Q>&oDsGpIhA zZChTFusLnwWJXf}E&Mwk5(rFyGP##NY9l3$6%~ZGzNd0j^_QJaY_UxMP+l?on=?KA zNix6``(m8F7y)GF$857UGGcVC%KRAr!!nn|%39Ek27F(SwKc?gu1D!%qEX?s&2+at zwETdIA`+fB#O*0IkS&Xk3GdRa5+rQCs`PS`WpxG7bz0}l3GBMP$CQ>p=`hXBU6{9|4E3D_k5+_) znTd59jLC*#Wfmys67jT|e}wz*&J3QbpUc9r#W z(>Vnj=nM#&RQ0@3^8;M(#oicO1DpY$^#X;1Z=rcJ8M>ttNmG~gz#EglWH&H2QZX=2 z1j)@ZH)#4fnAuuWejFOsf0!cy|76~|x;Ei;GNC(Bh)oNWu335gn(Noem4I7*z2}n^7fJ^W3kx=$i0BJ+@&O^y0C}@TZHu%mzk=S# zbx7!UQQhF<3#A7t^%K_sgxF?JaYpIR5odd;?pw_JMTBEd<>^j^Gcs{FmS#)hcj<1@ za!)u!$OGJ5twRdLo}9*U%WAo{Aqmo~qlYE~g4$(6Y z>b-E;lW>B!ch??macC4nB#i26z&@rw9pJ2e4PVSQ)wiHMkZ=Mw9VxifI%hIX;;ey0 z9fm~--XHX!NQ(HHmS_66CuTESH>2aaNDt^IX-#a2$0Q4_YxgBw5=C=aDPB-NjkMlD zb$e@@nGLvYu`U0&7oy#CDXDx$*sS`OC40~*MM2(KeKfT^8n~Ay*h&KRy-kr0k!Waa z^)Cmt;*l1Bx}0JJLC1>1K2 z?2)J91OaBiBO9Wu_qs(tcROhRwo~1r77$wT4&2DryUzgSez?-t<=sPA{Xa!3nL&^_YNef}&>crVH<<;ah9=I&$Zo2z%(KU9sv zp>UBZvi>tR2%v!?1!zarr`KSl45gPm&)!8i!78gZ{Mju5#`{cw2czHfIj$oUNyt+> zBBy!7L%|X1xbv}^ZHWSLaGh=~sv$_@j#r(_ya4(Y5KVmeK@IHx;e;Qyzl9sLMp4x= zx<9;6>P7FbYL}lff1I9n8LvMZGhac4&d1!F4kxe&-r%GIe8%^4OYPX>Q9tsdZ|QYa zd>r#Z+Iw%P)pg%TrwzXrUcy||X`mw^h*bvnLoC88o7zfmqhD3%a=<3hkun3{v2IVH z@XC$#If#phb5LT>HyOCnE#Qd3(Fe~hv0q&{V%&nX)ZuTccb9vzdg6qAgMgVb!xU5o zAdN$X0`|LCiGX>C!yPcam~?Af-D!gg@vCTYLnUM!L?66U7_+`iMGR&D7-j-=i`+;| z2tbu;`4J>(33$-j2p%UNM4}*el)$5bed_p%5KURaB&}f=x)lK~^XgyBB%{)Bhk{%e zi{DPVuWmN-cxP@E(d^l(3@!`6%*^eEhUMgq&X04S`sq&5pqpuhbCbcsviG_5P*K@? z)WBkyVdGzetNO5u?Kh8GFZ&P_#h-lHYK|*LZk+W2`XvVc`2dO^j(=L0LgnB=9?a{y_Ze&3r$T%-B!4mlh!}~(fo_z* zy#bh{B4DGLNfv?FwYD@XY~sa8+p>U@M~bnHchJE{kKa$!;a|;{msoUO`&Qs1b&$z` z&rRm>#cf8%oQ5*8{x@H#&KT?Yu+Nrc){rdE+B6x$%#j7T%OB49a!<>*m4g*Vo*h$J z{02=G?T+io^4r!!pAnwdV(EEQJu2+Ejh5XOpj(5*>z7W21+0;`0#Jy;u=KoV?a2@| zC2+X9HF~4}FNY-3sK-k(l)Vm3J&$HOsTLBn=4m%)nY(8lo+>_#q#>Z6oD>23=cF7l zsa3|yT7eO`7)8tFuF;3jt3OA!P-(Tq^eG#}BMn+Rb~H@F`~rO?w&clIa&X%od@H^l z!Z+0b!m{q$pn>)vh43c~?PnknCkG%{?ZMU-)%PEV-f#M4q2T&umvN9fGiGB)>IK6v zFR#t&1P}!ZGqbtnp944IRB7MCh7Oi*L=cQ*{*{{8l$8bP#@^9RdIHKO^+TQRDUFKOCV`V)3anSYfX`q0Ugi^L`0~q)zlQTT4*FWM`V>Fwm)odqOZ_sB zm@OX2Wr!gMNH5eb#^h*HkM=`wP4{iD%ey!4=Ua!#ntL#KI$H@Xif2Qp6p>#I8N7Gf z_VG~G(}Ic!wf?v#G-x^gXt3Y@{1X}vh`rsO?01w~qsUGYWrPVxLO?J7xISTEsg^LK zm4%yHMrnr}?djBCw8!vaT%OBC&Ao*O_7+Ij@co`%4w==%k?z+n&~GVXSqQzPX_`#U z>`WAC_f|sJ7qab#%!AM^qYsUa<7B*~!^ls{5cJBS>$%H9*x{f&HigC@Ux@;e#{53P zV+s$5delq_g$ytghs0fVY+0f@m?vHEh7E;>nx!rWRE6@kz_Zy|tiMW)fH3ojz99Rn zjgn8YVR<=4DxC?YrZUYQZD(R?^$4x(r(JkYO@3OHyjPt6;G^($k$4{39h5eli!V?U zy}=vmg*3=e15c~n-;I1G3lIPKjppb;k0!%6Oul+QaVgA$m~^U}1Xog?QyfuEZ9T>D zayyLRQCFTShf#{D>=!nkThjM;do3lxZyQTsH7JF^kj5ogrQENm^T0Q-Gnyvw?~*w? zJpo0&C+|g={RsCS@=uoWq^MKWo)k7_MKC(ny$4_If6)D8B=AHoL`l%!3k}a>m9K=b zn7^7rZ)Biqkc_f}y~PrDl#%m3Hngi=lOiiQ+fTVcW)$B?7VS961BIK+uwGxic@~u( z&vU>6^8`jE8#U%&BG#3eOCbkUa&7EvxtGP@9I23#yN@5+L%M}k^C7e4sq=Rp2=;oc zlMZ!@7^^8A5;Y3SSc!PtY?HIG=s94qWz3_IOs~y+nO|SxAI7)X%8tlGDp; zA-(q=dmfe&ZX5CmL6(HxY#C3rD|z(0>~srxCFUpgTr91FnTXD!WaN8k$N6%ZRXN8$ zM6v@@dk{xXcQuiDDNB{}r|&}vE?Jud`7STf z{P<$*G}eOlb~SkzMIO~c2zaQAJkWTrw}^H?rvdM$D0X~%GGh;gVZa!9ag0lzJlV!p zRY|+N^}dHI85g>}oghuwoE4u?a}Q7!{a2WSO0_>EGRvb<8#?`YDj=;-X|R;#Qc4t$ zzWaKfsuL}Fl=oGXqMe#nmn+LBxN{J5kfL?87b__YNZZpkK`puP(8%?VfmU@|cF%M~;R!iF?ppDT;3utR!Pnu#OsEe_dJMIRP6vX^Knj0@;< z@vIdP18My;HHUdOO!{1pXV>ht0$B(zx;5^D+Uc{X*tlPxYHk8%!qeyAq}yk<$;Tn- z)Oua`@#=3LIuv_PNA7O$Y3P-~_0U(?6_#Q`1e)@4mS_sb;b*-uids2e&HK79*OXhQ z=VDuWGE4L2v7Ydu06~+w-bwpPNLA3WJ zLF79goz5FOxcSjZ7lf1_H$JzBtp?zTH@!U2Y_ZG>;_@%ytWO_)rzcKjmJlgUrrloTs$t}< zH84ByZTSmKzu3T3vIF4!Qy?&T;rX6@iWcv*HoSml^V)ZLoh0LIUL6ymOyd#X0 zlhMN9;B$n@PjYdWI&F8x9vTOD*L`hkeqvn`5C=|sS zbEffrgKJ>(wNEh7OsJ<3k?i9^ndB*co>>2E%$qF~x7kUOi=Y$CI5AjW{Pze+kXqKe z_Saa|@9d}^|J=`&RB?KpW-Fy3K0A+@kAQCD(;6Qb9j!C&vM0r;!oYp5V%`C_dNJTahGsrlj?3iuRyo3ajxxl8C|qRedWt`22!nmU}@kK z{({0I!&#w5M13XzW(Q*SSD|6#=UoGki|G7?8cRo-vHae&Gf=Vl8|PV@i!3eTkZ}dR^aab zk*>rMS%s2n2b4#r>)tF)zagGx+&>~GLHY%M4)l`1uKH3IpH9jI3y58RWPwyLq)jV} zitc~KVE+lA%~<~N^uDQeSOot^Kkwoh_VeyvzNPQ95f-fHSFt*7b9^lL-2R{O`Vl&Q zc-6$2F#mTB+;7N6(^SotMYROF0Fj^jsX50t4*3Jy;8p8v3dp;nda>?0ojA-NX!`jr zf{27aY%lt3+Xk*Mw@WpR9)Uf+a1EK93R^bm`MW`}qXDGkuXlC$(PugU(J;A%#GJv& zhz!7+!J3WC+7t%`-y&ZGWDW#?ZRHNkdjK~-XCXH=ZvR|7ADVOXtKYN8N^!j?i!dh? zu|Ld+M3vmkY)20^tMeaEirO$HU3;J2koU9jsk9PFSfdH6(g*E7%)e~d4f7laqKT)% zkE%p>1C=UOEvXzm1s=lD%D!JEC)F(z7ep_nK$@umbRS*{$rw@!{n%;liftWcB}>~M zDzs~uCQ(pmCUFwzs0d})`WG2uwT5H7706Y>ss=T6?oZuVsi(VYn}rU1`ec>=w{$X_ z!=K&V9{WP*MUVA^6x{4a`ya10TI?e(((@h3o8Nmgq`KWYb0WY=K)B`Kj%LTNE$hf zH3nZ3D=baPxYW7uzQ)`Xy%rI3t(VWK|YxC}cz!+z2T?f5mzh$E=9FsFqhW0vY zSEJnj>%rT9%E7Tg(y23pyz#HLCep82QlqmvxHcbFJeF~=bNcr=Gl3pC= zsT0-_F>7Uv*zE1$9Ccbd?k{5`ZF!RyBn&@I50N9TNjKJN*)zb^oUW}*4a`zM7^bM> zn_;4@{sKHogx1mzH*2wbbsx#LbWdec363RrXA%$GiMbONX8sL4;qO_KZA=&W$rHDO zBZh|2J@_KkW2z^`dYiSq4Q)ly;c~ylWT+ne!jmc7%uN}I{&p#{8MX=Z1cej$(b6SG zmjKBXL8r+Tww_Z|f08TChb01rbvW%zm!-XT!Ey0ketp?GrxMVjpk=1ed}r(|5#zts z9crEM&y^0&QUVXnye00Iq;OV|^JMbJTzy)FtL;thSsOdrez#fs4Drj}em|2ooMw{bOYI`Imw1W0ylBWWKIsUlbX75_P6wujn~ z!#z#3%zg`NoH1Q%&2uuoE#{!p`x;{sJs%xwU`+bp`l%9VH~R0Gij3APoUpyN9KgwR zG#Q(hXa!tSsIP>55yU1@m|GsTW)|KE4G2_4x@WIr-spAH)3@wGf-!e&gCiR!+3+-b z*fS{f!E5Y85-tX=rM4v;nm?ka9R&9)9B3phP1;SK(U;Q{TUzMb98Ra!$KJN!+VW-B zWeIY5waML|l-H3J*W;ohUYr%Dru>p4JF)3=cq26_Lcwv)cwnC&=GDeG|A8IGPHpMW z%|AKTkA9@iZ|Ii|x8@cPYtxGX;m1a8@3zyLHrE!oFdK8(Yh@@+BaPof zoeqk+d9Pc5QL|VI?XS)+p(YFK@-0}Id2_LG1WL;jDN6w8g>oIR27($^$-FbQtQF4; zyAjRIf07hM!gK35m1QzV`?aiZqidBvAB#*$LG_B5W6P-@#QzMuQJl|Yb@D4Zt$KGS zd*aJbm%??r?=nzSNtJz2O`sm(7Rgr?dGa`tfW$)}?J{4U0B?L|=y@dK{e~(qr_zwP7hb<)_7XN$+Xb_2={CEDw(Ob`P@iIQyX9wbmw%%hr1qI6R6Cg* zL3f?2!P!$a=x}esaj6UhiTXoz{R`aDeeSQTMHzbK9OVCz)N`KcJ0Ro{PGlKpkiLbK zQZr?t;K4nLbNoGxCl68Xm;U_!=mnTY%ofV#9k@`%xk#e4+8{JvIsB?$m!B(C8 z8ToW0>R=Lm=1Y8NJz7^eqn;W}yo#LQ)Q~bY)Xdk<7LfZD+VkYaX>igd<#@SbOm%YQ5$7#@!qxR9E+ptx!k6y}Dt4u*{P*sIE zopVo9k9?tho33;NlOilbwu;*Fp(n|Tsz#*@mJqvdb3EX=`$NWjCH8 zv2=2LRhKxDSq6i;cljZ{dBUE|D;4%)q5+j(2DSfcFhASd;`bPKM&A7C?6q$+G)>SOS5`Fdx zIZ`0~uCVXZ@yY=|o4EVrRjqZ_O6;qf6X{NTn2<3%Huvw!sY`Uf`N>Sjam}b=hg{Pb z{(4{;kv~Zrm@~yWm}pk{^Ji-*H11^VBbhq4sma-b)NJ9|wc5W*QbQ~H^LeZ^h7`#Z zCB6wQ%y7$-!%Cd3Dx>$?!Z8;~8}^&*5=x7ItzF0m!lcN!No zPRp%dGW^qYr=5bOsodc6~I0~*I;UDJ@J;KO8m!{;+%ocZ147{=Ivgba=Aym#CU z7iG5>ld(|xl&Iv{dR|4&pq;UYc`{*9lx^Z#zu$`~TpfgM!pSa&rheoXnw+5MsT)nXSsp8s-R5c!AVZl)8;LE$TDt)Mr~EZaE|{|1w! ze@?ytZVD&V7n1_{uY9(;*(q7wDx(9$h3=>FNbJICD1Cka^o^BiBqOBzQEAthISDTz zTDGbb(ARoq0)zkxT*G_J$xfK8JbY|sK%ZsF7Tg`J|_qh_e1)J zrSZteNIEj#veCv0nVilo8&kPE9KqoD`k&@R2{|7aBdTu?BY&ir*8ni}Y~`sLAF0I8 z0w=66O)^{iX+p-E*PSTD>~>#B?a2_ZkRb2vENiKoT|A47tS`sx;p2Y38I;-!EU!Lg z`|1kKEic@-%BaqN!L3=~0Vu*0@O^l=CI0XuY!whED8EOfKd{NVWIn*&@@(A{4kP#9 zco|Nx1Ne*_P}P_NK8Te;7r+9$18J2v&+O^>HqQK8@8+Me1|UB1r`NN9<=I`HF3>Y2 z4frr!%;NrR;<x{2Dil;Q6d-FSCei^tGtzWi|Me|j=OzxEa&ZCz+kM5ME z+CI0K#T0M&FC4nljV6gs$5u1AG%+QF{+m~PlI8*I0AR(1SZi)?a_iI3YH~sH!B@Tv z6M!0XfAlJ5$Z5C8biFuV)NOHra`o$;x0Nn%LE`&BV?t3QOfgRAIE}Mv_uIJWWxTLE z0HkA5NXX}vN(N-giUrCI;q==KPOf)t?|<4)mxg@$vW?>`Vk5)Fy$i(mpR>k9Qc$Hq(@>Tk(91!Sq;U?mYV4c1z_5*8(o~c7gTd>p9Xh z{s3vPJten$6V^JfyUYU!+T~rU*gTi16(LW7CFyrzlzFz#pWI~Ye}0LJ-wkX%^RtXy zJv-&uoiP3&bua)%epEqN?`AB$1FnVRG27o`t4{St%m2C?y%tZ-ZM$@ww$ek6AVF%s zIT+$jY#l_*UZb=HcKYwHy}y2)0{N#jHx0sv?!uMD1SkRf(Y!uu;Y@ZF<_=8Ko!Bv94YajJSM_yS?{p;J;3HP;&V zT({~8Zb8Ms1pn-A5D;qwc8nFqe`_gWc50ale8!loa@%&3$Jbji0?~`02$3=a9QliXUC}qa1Qp;dbrBsFd&Te{E6X=l_nLyFetdzg-}=)Y*wI z2UmdX@TX^02$HbCF@SOAwzntuc;V($h-TuJMmxf>&927Px5nHzb$YHQx?27U#@ z5RH_3|8j*$>)Nz)W*J_v(M5`65M_4aHg zj=Q>MfrOmLK6q54hhID++kp(AGN2aj+qmEASbyn80`1aW(1Vepos*mq?0s%w18=d_ zv*yZc9fU3`V@$RWLDC!FevTg=(uVg1;#hc>?t~y+vQEDr0Lbw9+kB$msgjbCXgA<% zMcQ>d+Q~qmr+Y`^CwMzv1c7?i_O|zSo4ifM)bcSeh8=LLe(zQd zwSWs#56ku@D@yCnpIr9u114OuyFGT?fOWnIV$S#QuF=5D_gJeNsX6l5QBre;dkXGT zkAXmSKqisLA;F?v`#uIp{1huZonrNABD`X3^)?Paa%jWl{uj&B?}P!#Nh?U@N<4r4 F{{cSxQDp!C literal 0 HcmV?d00001 diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index 6e78ee5d45..f58d2c2bf2 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -62,6 +62,9 @@ Notable parameters: Example here describes use case for creation of new color coded review of png image sequence. Original representation's files are kept intact, review is created from transcoded files, but these files are removed in cleanup process. ![global_oiio_transcode](assets/global_oiio_transcode.png) +Another use case is to transcode in Maya only `beauty` render layers and use collected `Display` and `View` colorspaces from DCC. +![global_oiio_transcode_in_Maya](assets/global_oiio_transcode.png) + ## Profile filters Many of the settings are using a concept of **Profile filters** From a07e76ebb56d9dc325f11b40a5b336f4125725e0 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 23 Feb 2023 11:05:26 +0100 Subject: [PATCH 065/227] OP-4643 - updates to documentation Co-authored-by: Roy Nieterau --- website/docs/project_settings/settings_project_global.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index f58d2c2bf2..d904080ad1 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -51,7 +51,7 @@ OIIOTools transcoder plugin with configurable output presets. Any incoming repre `oiiotool` is used for transcoding, eg. `oiiotool` must be present in `vendor/bin/oiio` or environment variable `OPENPYPE_OIIO_PATHS` must be provided for custom oiio installation. Notable parameters: -- **`Delete Original Representation`** - keep or remove original representation. If old representation is kept, but there is new transcoded representation with 'Create review' tag, original representation looses its 'review' tag if present. +- **`Delete Original Representation`** - keep or remove original representation. If old representation is kept, but there is new transcoded representation with 'Create review' tag, original representation loses its 'review' tag if present. - **`Extension`** - target extension. If left empty, original extension is used. - **`Transcoding type`** - transcoding into colorspace or into display and viewer space could be used. Cannot use both at the same time. - **`Colorspace`** - target colorspace, which must be available in used color config. (If `Transcoding type` is `Use Colorspace` value in configuration is used OR if empty value collected on instance from DCC). From bae2ded2f711d68291d6a6d14e60cb19f856d1c5 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 23 Feb 2023 11:30:15 +0100 Subject: [PATCH 066/227] Fix - check existence only if not None review_path might be None --- openpype/modules/slack/plugins/publish/integrate_slack_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index 4e2557ccc7..86c97586d2 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -187,7 +187,7 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): repre_review_path = get_publish_repre_path( instance, repre, False ) - if os.path.exists(repre_review_path): + if repre_review_path and os.path.exists(repre_review_path): review_path = repre_review_path if "burnin" in tags: # burnin has precedence if exists break From d74e17a0e112c48714a87afeddf7750494fb6fca Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 21 Feb 2023 17:40:29 +0000 Subject: [PATCH 067/227] Implement get_multipart --- openpype/hosts/maya/api/lib_renderproducts.py | 82 ++++++++++++------- 1 file changed, 52 insertions(+), 30 deletions(-) diff --git a/openpype/hosts/maya/api/lib_renderproducts.py b/openpype/hosts/maya/api/lib_renderproducts.py index 60090e9f6d..e635414029 100644 --- a/openpype/hosts/maya/api/lib_renderproducts.py +++ b/openpype/hosts/maya/api/lib_renderproducts.py @@ -196,12 +196,18 @@ class ARenderProducts: """Constructor.""" self.layer = layer self.render_instance = render_instance - self.multipart = False + self.multipart = self.get_multipart() # Initialize self.layer_data = self._get_layer_data() self.layer_data.products = self.get_render_products() + def get_multipart(self): + raise NotImplementedError( + "The render product implementation does not have a " + "\"get_multipart\" method." + ) + def has_camera_token(self): # type: () -> bool """Check if camera token is in image prefix. @@ -344,7 +350,6 @@ class ARenderProducts: separator = file_prefix[matches[0].end(1):matches[1].start(1)] return separator - def _get_layer_data(self): # type: () -> LayerMetadata # ______________________________________________ @@ -531,16 +536,20 @@ class RenderProductsArnold(ARenderProducts): return prefix - def _get_aov_render_products(self, aov, cameras=None): - """Return all render products for the AOV""" - - products = [] - aov_name = self._get_attr(aov, "name") + def get_multipart(self): multipart = False multilayer = bool(self._get_attr("defaultArnoldDriver.multipart")) merge_AOVs = bool(self._get_attr("defaultArnoldDriver.mergeAOVs")) if multilayer or merge_AOVs: multipart = True + + return multipart + + def _get_aov_render_products(self, aov, cameras=None): + """Return all render products for the AOV""" + + products = [] + aov_name = self._get_attr(aov, "name") ai_drivers = cmds.listConnections("{}.outputs".format(aov), source=True, destination=False, @@ -594,7 +603,7 @@ class RenderProductsArnold(ARenderProducts): ext=ext, aov=aov_name, driver=ai_driver, - multipart=multipart, + multipart=self.multipart, camera=camera) products.append(product) @@ -731,6 +740,14 @@ class RenderProductsVray(ARenderProducts): renderer = "vray" + def get_multipart(self): + multipart = False + image_format = self._get_attr("vraySettings.imageFormatStr") + if image_format == "exr (multichannel)": + multipart = True + + return multipart + def get_renderer_prefix(self): # type: () -> str """Get image prefix for V-Ray. @@ -797,11 +814,6 @@ class RenderProductsVray(ARenderProducts): if default_ext in {"exr (multichannel)", "exr (deep)"}: default_ext = "exr" - # Define multipart. - multipart = False - if image_format_str == "exr (multichannel)": - multipart = True - products = [] # add beauty as default when not disabled @@ -813,7 +825,7 @@ class RenderProductsVray(ARenderProducts): productName="", ext=default_ext, camera=camera, - multipart=multipart + multipart=self.multipart ) ) @@ -826,10 +838,10 @@ class RenderProductsVray(ARenderProducts): productName="Alpha", ext=default_ext, camera=camera, - multipart=multipart + multipart=self.multipart ) ) - if multipart: + if self.multipart: # AOVs are merged in m-channel file, only main layer is rendered return products @@ -989,6 +1001,19 @@ class RenderProductsRedshift(ARenderProducts): renderer = "redshift" unmerged_aovs = {"Cryptomatte"} + def get_multipart(self): + # For Redshift we don't directly return upon forcing multilayer + # due to some AOVs still being written into separate files, + # like Cryptomatte. + # AOVs are merged in multi-channel file + multipart = False + force_layer = bool(self._get_attr("redshiftOptions.exrForceMultilayer")) # noqa + exMultipart = bool(self._get_attr("redshiftOptions.exrMultipart")) + if exMultipart or force_layer: + multipart = True + + return multipart + def get_renderer_prefix(self): """Get image prefix for Redshift. @@ -1028,16 +1053,6 @@ class RenderProductsRedshift(ARenderProducts): for c in self.get_renderable_cameras() ] - # For Redshift we don't directly return upon forcing multilayer - # due to some AOVs still being written into separate files, - # like Cryptomatte. - # AOVs are merged in multi-channel file - multipart = False - force_layer = bool(self._get_attr("redshiftOptions.exrForceMultilayer")) # noqa - exMultipart = bool(self._get_attr("redshiftOptions.exrMultipart")) - if exMultipart or force_layer: - multipart = True - # Get Redshift Extension from image format image_format = self._get_attr("redshiftOptions.imageFormat") # integer ext = mel.eval("redshiftGetImageExtension(%i)" % image_format) @@ -1059,7 +1074,7 @@ class RenderProductsRedshift(ARenderProducts): continue aov_type = self._get_attr(aov, "aovType") - if multipart and aov_type not in self.unmerged_aovs: + if self.multipart and aov_type not in self.unmerged_aovs: continue # Any AOVs that still get processed, like Cryptomatte @@ -1094,7 +1109,7 @@ class RenderProductsRedshift(ARenderProducts): productName=aov_light_group_name, aov=aov_name, ext=ext, - multipart=multipart, + multipart=self.multipart, camera=camera) products.append(product) @@ -1108,7 +1123,7 @@ class RenderProductsRedshift(ARenderProducts): product = RenderProduct(productName=aov_name, aov=aov_name, ext=ext, - multipart=multipart, + multipart=self.multipart, camera=camera) products.append(product) @@ -1124,7 +1139,7 @@ class RenderProductsRedshift(ARenderProducts): products.insert(0, RenderProduct(productName=beauty_name, ext=ext, - multipart=multipart, + multipart=self.multipart, camera=camera)) return products @@ -1144,6 +1159,10 @@ class RenderProductsRenderman(ARenderProducts): renderer = "renderman" unmerged_aovs = {"PxrCryptomatte"} + def get_multipart(self): + # Implemented as display specific in "get_render_products". + return False + def get_render_products(self): """Get all AOVs. @@ -1283,6 +1302,9 @@ class RenderProductsMayaHardware(ARenderProducts): {"label": "EXR(exr)", "index": 40, "extension": "exr"} ] + def get_multipart(self): + return False + def _get_extension(self, value): result = None if isinstance(value, int): From 069e1eed21a360d8a01effdacd3b3684954ea83c Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 22 Feb 2023 18:00:11 +0000 Subject: [PATCH 068/227] Fix Redshift expected files. --- openpype/hosts/maya/api/lib_renderproducts.py | 26 +++++++++++++++---- .../maya/plugins/publish/collect_render.py | 7 ++++- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/maya/api/lib_renderproducts.py b/openpype/hosts/maya/api/lib_renderproducts.py index e635414029..4e9e13d2a3 100644 --- a/openpype/hosts/maya/api/lib_renderproducts.py +++ b/openpype/hosts/maya/api/lib_renderproducts.py @@ -1001,6 +1001,20 @@ class RenderProductsRedshift(ARenderProducts): renderer = "redshift" unmerged_aovs = {"Cryptomatte"} + def get_files(self, product): + # When outputting AOVs we need to replace Redshift specific AOV tokens + # with Maya render tokens for generating file sequences. We validate to + # a specific AOV fileprefix so we only need to accout for one + # replacement. + if not product.multipart and product.driver: + file_prefix = self._get_attr(product.driver + ".filePrefix") + self.layer_data.filePrefix = file_prefix.replace( + "/", + "//" + ) + + return super(RenderProductsRedshift, self).get_files(product) + def get_multipart(self): # For Redshift we don't directly return upon forcing multilayer # due to some AOVs still being written into separate files, @@ -1009,7 +1023,7 @@ class RenderProductsRedshift(ARenderProducts): multipart = False force_layer = bool(self._get_attr("redshiftOptions.exrForceMultilayer")) # noqa exMultipart = bool(self._get_attr("redshiftOptions.exrMultipart")) - if exMultipart or force_layer: + if exMultipart and force_layer: multipart = True return multipart @@ -1109,8 +1123,9 @@ class RenderProductsRedshift(ARenderProducts): productName=aov_light_group_name, aov=aov_name, ext=ext, - multipart=self.multipart, - camera=camera) + multipart=False, + camera=camera, + driver=aov) products.append(product) if light_groups: @@ -1123,8 +1138,9 @@ class RenderProductsRedshift(ARenderProducts): product = RenderProduct(productName=aov_name, aov=aov_name, ext=ext, - multipart=self.multipart, - camera=camera) + multipart=False, + camera=camera, + driver=aov) products.append(product) # When a Beauty AOV is added manually, it will be rendered as diff --git a/openpype/hosts/maya/plugins/publish/collect_render.py b/openpype/hosts/maya/plugins/publish/collect_render.py index f2b5262187..338f148f85 100644 --- a/openpype/hosts/maya/plugins/publish/collect_render.py +++ b/openpype/hosts/maya/plugins/publish/collect_render.py @@ -42,6 +42,7 @@ Provides: import re import os import platform +import json from maya import cmds import maya.app.renderSetup.model.renderSetup as renderSetup @@ -183,7 +184,11 @@ class CollectMayaRender(pyblish.api.ContextPlugin): self.log.info("multipart: {}".format( multipart)) assert exp_files, "no file names were generated, this is bug" - self.log.info(exp_files) + self.log.info( + "expected files: {}".format( + json.dumps(exp_files, indent=4, sort_keys=True) + ) + ) # if we want to attach render to subset, check if we have AOV's # in expectedFiles. If so, raise error as we cannot attach AOV From 43020a6c9d6a8366048a294969deb5d6e6a999e8 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 23 Feb 2023 09:20:25 +0000 Subject: [PATCH 069/227] Only use force options as multipart identifier. --- openpype/hosts/maya/api/lib_renderproducts.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/api/lib_renderproducts.py b/openpype/hosts/maya/api/lib_renderproducts.py index 4e9e13d2a3..02e55601b9 100644 --- a/openpype/hosts/maya/api/lib_renderproducts.py +++ b/openpype/hosts/maya/api/lib_renderproducts.py @@ -1021,9 +1021,10 @@ class RenderProductsRedshift(ARenderProducts): # like Cryptomatte. # AOVs are merged in multi-channel file multipart = False - force_layer = bool(self._get_attr("redshiftOptions.exrForceMultilayer")) # noqa - exMultipart = bool(self._get_attr("redshiftOptions.exrMultipart")) - if exMultipart and force_layer: + force_layer = bool( + self._get_attr("redshiftOptions.exrForceMultilayer") + ) + if force_layer: multipart = True return multipart From 9c688309a9883654c0fb66fab992e6f28323670c Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 23 Feb 2023 11:53:55 +0000 Subject: [PATCH 070/227] Update openpype/hosts/maya/api/lib_renderproducts.py --- openpype/hosts/maya/api/lib_renderproducts.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/maya/api/lib_renderproducts.py b/openpype/hosts/maya/api/lib_renderproducts.py index 02e55601b9..463324284b 100644 --- a/openpype/hosts/maya/api/lib_renderproducts.py +++ b/openpype/hosts/maya/api/lib_renderproducts.py @@ -1320,6 +1320,7 @@ class RenderProductsMayaHardware(ARenderProducts): ] def get_multipart(self): + # MayaHardware does not support multipart EXRs. return False def _get_extension(self, value): From d760cbab77c39f3a4ae9b72924ad222304a8aa65 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 31 Jan 2023 06:55:36 +0000 Subject: [PATCH 071/227] Batch script for running Openpype on Deadline. --- tools/openpype_console.bat | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 tools/openpype_console.bat diff --git a/tools/openpype_console.bat b/tools/openpype_console.bat new file mode 100644 index 0000000000..414b5fdf66 --- /dev/null +++ b/tools/openpype_console.bat @@ -0,0 +1,3 @@ +cd "%~dp0\.." +echo %OPENPYPE_MONGO% +.poetry\bin\poetry.exe run python start.py %* From 5d9fe60013148054b3cbd70d3701febc7f2fe3a4 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 17 Feb 2023 16:25:35 +0000 Subject: [PATCH 072/227] Commenting for documentation --- tools/openpype_console.bat | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tools/openpype_console.bat b/tools/openpype_console.bat index 414b5fdf66..04b28c389f 100644 --- a/tools/openpype_console.bat +++ b/tools/openpype_console.bat @@ -1,3 +1,15 @@ +goto comment +SYNOPSIS + Helper script running scripts through the OpenPype environment. + +DESCRIPTION + This script is usually used as a replacement for building when tested farm integration like Deadline. + +EXAMPLE + +cmd> .\openpype_console.bat path/to/python_script.py +:comment + cd "%~dp0\.." echo %OPENPYPE_MONGO% .poetry\bin\poetry.exe run python start.py %* From 7c73995971a39ef5f77bfe277cd86cddc76b78cd Mon Sep 17 00:00:00 2001 From: Fabia Serra Arrizabalaga Date: Thu, 23 Feb 2023 00:52:40 +0100 Subject: [PATCH 073/227] Move get_workfile_build_placeholder_plugins to NukeHost class as workfile template builder expects --- openpype/hosts/nuke/api/__init__.py | 3 --- openpype/hosts/nuke/api/pipeline.py | 13 ++++++------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/nuke/api/__init__.py b/openpype/hosts/nuke/api/__init__.py index 3b00ca9f6f..1af5ff365d 100644 --- a/openpype/hosts/nuke/api/__init__.py +++ b/openpype/hosts/nuke/api/__init__.py @@ -30,7 +30,6 @@ from .pipeline import ( parse_container, update_container, - get_workfile_build_placeholder_plugins, ) from .lib import ( INSTANCE_DATA_KNOB, @@ -79,8 +78,6 @@ __all__ = ( "parse_container", "update_container", - "get_workfile_build_placeholder_plugins", - "INSTANCE_DATA_KNOB", "ROOT_DATA_KNOB", "maintained_selection", diff --git a/openpype/hosts/nuke/api/pipeline.py b/openpype/hosts/nuke/api/pipeline.py index 6dec60d81a..d5289010cb 100644 --- a/openpype/hosts/nuke/api/pipeline.py +++ b/openpype/hosts/nuke/api/pipeline.py @@ -101,6 +101,12 @@ class NukeHost( def get_workfile_extensions(self): return file_extensions() + def get_workfile_build_placeholder_plugins(self): + return [ + NukePlaceholderLoadPlugin, + NukePlaceholderCreatePlugin + ] + def get_containers(self): return ls() @@ -200,13 +206,6 @@ def _show_workfiles(): host_tools.show_workfiles(parent=None, on_top=False) -def get_workfile_build_placeholder_plugins(): - return [ - NukePlaceholderLoadPlugin, - NukePlaceholderCreatePlugin - ] - - def _install_menu(): # uninstall original avalon menu main_window = get_main_window() From 2921579164112453343a580f8f358c124eb9c1d7 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 11:57:51 +0100 Subject: [PATCH 074/227] OP-4643 - added Settings for ExtractColorTranscode --- .../defaults/project_settings/global.json | 4 + .../schemas/schema_global_publish.json | 73 +++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index cedc2d6876..8485bec67b 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -68,6 +68,10 @@ "output": [] } }, + "ExtractColorTranscode": { + "enabled": true, + "profiles": [] + }, "ExtractReview": { "enabled": true, "profiles": [ 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 5388d04bc9..46ae6ba554 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 @@ -197,6 +197,79 @@ } ] }, + { + "type": "dict", + "collapsible": true, + "key": "ExtractColorTranscode", + "label": "ExtractColorTranscode", + "checkbox_key": "enabled", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "list", + "key": "profiles", + "label": "Profiles", + "object_type": { + "type": "dict", + "children": [ + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + }, + { + "key": "hosts", + "label": "Host names", + "type": "hosts-enum", + "multiselection": true + }, + { + "key": "task_types", + "label": "Task types", + "type": "task-types-enum" + }, + { + "key": "task_names", + "label": "Task names", + "type": "list", + "object_type": "text" + }, + { + "key": "subsets", + "label": "Subset names", + "type": "list", + "object_type": "text" + }, + { + "type": "splitter" + }, + { + "key": "ext", + "label": "Output extension", + "type": "text" + }, + { + "key": "output_colorspace", + "label": "Output colorspace", + "type": "text" + }, + { + "key": "custom_tags", + "label": "Custom Tags", + "type": "list", + "object_type": "text" + } + ] + } + } + ] + }, { "type": "dict", "collapsible": true, From 8a67065fce1bd39de5870bb768365dffafdda8b2 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 11:58:51 +0100 Subject: [PATCH 075/227] OP-4643 - added ExtractColorTranscode Added method to convert from one colorspace to another to transcoding lib --- openpype/lib/transcoding.py | 53 ++++++++ .../publish/extract_color_transcode.py | 124 ++++++++++++++++++ 2 files changed, 177 insertions(+) create mode 100644 openpype/plugins/publish/extract_color_transcode.py diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 039255d937..2fc662f2a4 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1045,3 +1045,56 @@ def convert_ffprobe_fps_to_float(value): if divisor == 0.0: return 0.0 return dividend / divisor + + +def convert_colorspace_for_input_paths( + input_paths, + output_dir, + source_color_space, + target_color_space, + logger=None +): + """Convert source files from one color space to another. + + Filenames of input files are kept so make sure that output directory + is not the same directory as input files have. + - This way it can handle gaps and can keep input filenames without handling + frame template + + Args: + input_paths (str): Paths that should be converted. It is expected that + contains single file or image sequence of samy type. + output_dir (str): Path to directory where output will be rendered. + Must not be same as input's directory. + source_color_space (str): ocio valid color space of source files + target_color_space (str): ocio valid target color space + logger (logging.Logger): Logger used for logging. + + """ + if logger is None: + logger = logging.getLogger(__name__) + + input_arg = "-i" + oiio_cmd = [ + get_oiio_tools_path(), + + # Don't add any additional attributes + "--nosoftwareattrib", + "--colorconvert", source_color_space, target_color_space + ] + for input_path in input_paths: + # Prepare subprocess arguments + + oiio_cmd.extend([ + input_arg, input_path, + ]) + + # Add last argument - path to output + base_filename = os.path.basename(input_path) + output_path = os.path.join(output_dir, base_filename) + oiio_cmd.extend([ + "-o", output_path + ]) + + logger.debug("Conversion command: {}".format(" ".join(oiio_cmd))) + run_subprocess(oiio_cmd, logger=logger) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py new file mode 100644 index 0000000000..58508ab18f --- /dev/null +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -0,0 +1,124 @@ +import pyblish.api + +from openpype.pipeline import publish +from openpype.lib import ( + + is_oiio_supported, +) + +from openpype.lib.transcoding import ( + convert_colorspace_for_input_paths, + get_transcode_temp_directory, +) + +from openpype.lib.profiles_filtering import filter_profiles + + +class ExtractColorTranscode(publish.Extractor): + """ + Extractor to convert colors from one colorspace to different. + """ + + label = "Transcode color spaces" + order = pyblish.api.ExtractorOrder + 0.01 + + optional = True + + # Configurable by Settings + profiles = None + options = None + + def process(self, instance): + if not self.profiles: + self.log.warning("No profiles present for create burnin") + return + + if "representations" not in instance.data: + self.log.warning("No representations, skipping.") + return + + if not is_oiio_supported(): + self.log.warning("OIIO not supported, no transcoding possible.") + return + + colorspace_data = instance.data.get("colorspaceData") + if not colorspace_data: + # TODO get_colorspace ?? + self.log.warning("Instance has not colorspace data, skipping") + return + source_color_space = colorspace_data["colorspace"] + + host_name = instance.context.data["hostName"] + family = instance.data["family"] + task_data = instance.data["anatomyData"].get("task", {}) + task_name = task_data.get("name") + task_type = task_data.get("type") + subset = instance.data["subset"] + + filtering_criteria = { + "hosts": host_name, + "families": family, + "task_names": task_name, + "task_types": task_type, + "subset": subset + } + profile = filter_profiles(self.profiles, filtering_criteria, + logger=self.log) + + if not profile: + self.log.info(( + "Skipped instance. None of profiles in presets are for" + " Host: \"{}\" | Families: \"{}\" | Task \"{}\"" + " | Task type \"{}\" | Subset \"{}\" " + ).format(host_name, family, task_name, task_type, subset)) + return + + self.log.debug("profile: {}".format(profile)) + + target_colorspace = profile["output_colorspace"] + if not target_colorspace: + raise RuntimeError("Target colorspace must be set") + + repres = instance.data.get("representations") or [] + for idx, repre in enumerate(repres): + self.log.debug("repre ({}): `{}`".format(idx + 1, repre["name"])) + if not self.repre_is_valid(repre): + continue + + new_staging_dir = get_transcode_temp_directory() + repre["stagingDir"] = new_staging_dir + files_to_remove = repre["files"] + if not isinstance(files_to_remove, list): + files_to_remove = [files_to_remove] + instance.context.data["cleanupFullPaths"].extend(files_to_remove) + + convert_colorspace_for_input_paths( + repre["files"], + new_staging_dir, + source_color_space, + target_colorspace, + self.log + ) + + def repre_is_valid(self, repre): + """Validation if representation should be processed. + + Args: + repre (dict): Representation which should be checked. + + Returns: + bool: False if can't be processed else True. + """ + + if "review" not in (repre.get("tags") or []): + self.log.info(( + "Representation \"{}\" don't have \"review\" tag. Skipped." + ).format(repre["name"])) + return False + + if not repre.get("files"): + self.log.warning(( + "Representation \"{}\" have empty files. Skipped." + ).format(repre["name"])) + return False + return True From 76c00f9fa8888e991c61074ed894b09c211b55f0 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 12:05:57 +0100 Subject: [PATCH 076/227] OP-4643 - extractor must run just before ExtractReview Nuke render local is set to 0.01 --- openpype/plugins/publish/extract_color_transcode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 58508ab18f..5163cd4045 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -20,7 +20,7 @@ class ExtractColorTranscode(publish.Extractor): """ label = "Transcode color spaces" - order = pyblish.api.ExtractorOrder + 0.01 + order = pyblish.api.ExtractorOrder + 0.019 optional = True From 455eb379c26ac8494d1aff4fb257cd01e80dbdd5 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 14:03:22 +0100 Subject: [PATCH 077/227] OP-4643 - fix for full file paths --- .../publish/extract_color_transcode.py | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 5163cd4045..6ad7599f2c 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -1,3 +1,4 @@ +import os import pyblish.api from openpype.pipeline import publish @@ -41,13 +42,6 @@ class ExtractColorTranscode(publish.Extractor): self.log.warning("OIIO not supported, no transcoding possible.") return - colorspace_data = instance.data.get("colorspaceData") - if not colorspace_data: - # TODO get_colorspace ?? - self.log.warning("Instance has not colorspace data, skipping") - return - source_color_space = colorspace_data["colorspace"] - host_name = instance.context.data["hostName"] family = instance.data["family"] task_data = instance.data["anatomyData"].get("task", {}) @@ -82,18 +76,32 @@ class ExtractColorTranscode(publish.Extractor): repres = instance.data.get("representations") or [] for idx, repre in enumerate(repres): self.log.debug("repre ({}): `{}`".format(idx + 1, repre["name"])) - if not self.repre_is_valid(repre): + # if not self.repre_is_valid(repre): + # continue + + colorspace_data = repre.get("colorspaceData") + if not colorspace_data: + # TODO get_colorspace ?? + self.log.warning("Repre has not colorspace data, skipping") + continue + source_color_space = colorspace_data["colorspace"] + config_path = colorspace_data.get("configData", {}).get("path") + if not os.path.exists(config_path): + self.log.warning("Config file doesn't exist, skipping") continue new_staging_dir = get_transcode_temp_directory() + original_staging_dir = repre["stagingDir"] repre["stagingDir"] = new_staging_dir - files_to_remove = repre["files"] - if not isinstance(files_to_remove, list): - files_to_remove = [files_to_remove] - instance.context.data["cleanupFullPaths"].extend(files_to_remove) + files_to_convert = repre["files"] + if not isinstance(files_to_convert, list): + files_to_convert = [files_to_convert] + files_to_convert = [os.path.join(original_staging_dir, path) + for path in files_to_convert] + instance.context.data["cleanupFullPaths"].extend(files_to_convert) convert_colorspace_for_input_paths( - repre["files"], + files_to_convert, new_staging_dir, source_color_space, target_colorspace, From 52a5865341039d03da72dee00d4eb996334a4dbd Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 14:04:06 +0100 Subject: [PATCH 078/227] OP-4643 - pass path for ocio config --- openpype/lib/transcoding.py | 3 +++ openpype/plugins/publish/extract_color_transcode.py | 1 + 2 files changed, 4 insertions(+) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 2fc662f2a4..ab86e44304 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1050,6 +1050,7 @@ def convert_ffprobe_fps_to_float(value): def convert_colorspace_for_input_paths( input_paths, output_dir, + config_path, source_color_space, target_color_space, logger=None @@ -1066,6 +1067,7 @@ def convert_colorspace_for_input_paths( contains single file or image sequence of samy type. output_dir (str): Path to directory where output will be rendered. Must not be same as input's directory. + config_path (str): path to OCIO config file source_color_space (str): ocio valid color space of source files target_color_space (str): ocio valid target color space logger (logging.Logger): Logger used for logging. @@ -1080,6 +1082,7 @@ def convert_colorspace_for_input_paths( # Don't add any additional attributes "--nosoftwareattrib", + "--colorconfig", config_path, "--colorconvert", source_color_space, target_color_space ] for input_path in input_paths: diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 6ad7599f2c..fdb13a47e8 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -103,6 +103,7 @@ class ExtractColorTranscode(publish.Extractor): convert_colorspace_for_input_paths( files_to_convert, new_staging_dir, + config_path, source_color_space, target_colorspace, self.log From 0f1dcb64eb5c98ca1d78b48b4e9e4a9dcfc86ffa Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 14:15:33 +0100 Subject: [PATCH 079/227] OP-4643 - add custom_tags --- openpype/plugins/publish/extract_color_transcode.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index fdb13a47e8..ab932b2476 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -72,6 +72,7 @@ class ExtractColorTranscode(publish.Extractor): target_colorspace = profile["output_colorspace"] if not target_colorspace: raise RuntimeError("Target colorspace must be set") + custom_tags = profile["custom_tags"] repres = instance.data.get("representations") or [] for idx, repre in enumerate(repres): @@ -109,6 +110,11 @@ class ExtractColorTranscode(publish.Extractor): self.log ) + if custom_tags: + if not repre.get("custom_tags"): + repre["custom_tags"] = [] + repre["custom_tags"].extend(custom_tags) + def repre_is_valid(self, repre): """Validation if representation should be processed. From 4d29e43a41a49dd805390fd03d3d7602d15ad38f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 14:18:38 +0100 Subject: [PATCH 080/227] OP-4643 - added docstring --- openpype/plugins/publish/extract_color_transcode.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index ab932b2476..88e2eed90f 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -18,6 +18,17 @@ from openpype.lib.profiles_filtering import filter_profiles class ExtractColorTranscode(publish.Extractor): """ Extractor to convert colors from one colorspace to different. + + Expects "colorspaceData" on representation. This dictionary is collected + previously and denotes that representation files should be converted. + This dict contains source colorspace information, collected by hosts. + + Target colorspace is selected by profiles in the Settings, based on: + - families + - host + - task types + - task names + - subset names """ label = "Transcode color spaces" From 454f65dc50253afdb7c74c7c8b851fcbe54c372d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 18:15:44 +0100 Subject: [PATCH 081/227] OP-4643 - updated Settings schema --- .../schemas/schema_global_publish.json | 48 +++++++++++++------ 1 file changed, 34 insertions(+), 14 deletions(-) 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 46ae6ba554..c2c911d7d6 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 @@ -246,24 +246,44 @@ "type": "list", "object_type": "text" }, + { + "type": "boolean", + "key": "delete_original", + "label": "Delete Original Representation" + }, { "type": "splitter" }, { - "key": "ext", - "label": "Output extension", - "type": "text" - }, - { - "key": "output_colorspace", - "label": "Output colorspace", - "type": "text" - }, - { - "key": "custom_tags", - "label": "Custom Tags", - "type": "list", - "object_type": "text" + "key": "outputs", + "label": "Output Definitions", + "type": "dict-modifiable", + "highlight_content": true, + "object_type": { + "type": "dict", + "children": [ + { + "key": "output_extension", + "label": "Output extension", + "type": "text" + }, + { + "key": "output_colorspace", + "label": "Output colorspace", + "type": "text" + }, + { + "type": "schema", + "name": "schema_representation_tags" + }, + { + "key": "custom_tags", + "label": "Custom Tags", + "type": "list", + "object_type": "text" + } + ] + } } ] } From a197c6820209db57dc3182ca392e51239be57bb5 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 18:17:25 +0100 Subject: [PATCH 082/227] OP-4643 - skip video files Only frames currently supported. --- .../plugins/publish/extract_color_transcode.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 88e2eed90f..a0714c9a33 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -36,6 +36,9 @@ class ExtractColorTranscode(publish.Extractor): optional = True + # Supported extensions + supported_exts = ["exr", "jpg", "jpeg", "png", "dpx"] + # Configurable by Settings profiles = None options = None @@ -88,13 +91,7 @@ class ExtractColorTranscode(publish.Extractor): repres = instance.data.get("representations") or [] for idx, repre in enumerate(repres): self.log.debug("repre ({}): `{}`".format(idx + 1, repre["name"])) - # if not self.repre_is_valid(repre): - # continue - - colorspace_data = repre.get("colorspaceData") - if not colorspace_data: - # TODO get_colorspace ?? - self.log.warning("Repre has not colorspace data, skipping") + if not self._repre_is_valid(repre): continue source_color_space = colorspace_data["colorspace"] config_path = colorspace_data.get("configData", {}).get("path") @@ -136,9 +133,9 @@ class ExtractColorTranscode(publish.Extractor): bool: False if can't be processed else True. """ - if "review" not in (repre.get("tags") or []): - self.log.info(( - "Representation \"{}\" don't have \"review\" tag. Skipped." + if repre.get("ext") not in self.supported_exts: + self.log.warning(( + "Representation \"{}\" of unsupported extension. Skipped." ).format(repre["name"])) return False From bd1a9c7342098e05e095f422200090ce918a56b5 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 18:19:08 +0100 Subject: [PATCH 083/227] OP-4643 - refactored profile, delete of original Implemented multiple outputs from single input representation --- .../publish/extract_color_transcode.py | 156 ++++++++++++------ 1 file changed, 109 insertions(+), 47 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index a0714c9a33..b0c851d5f4 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -1,4 +1,6 @@ import os +import copy + import pyblish.api from openpype.pipeline import publish @@ -56,13 +58,94 @@ class ExtractColorTranscode(publish.Extractor): self.log.warning("OIIO not supported, no transcoding possible.") return + profile = self._get_profile(instance) + if not profile: + return + + repres = instance.data.get("representations") or [] + for idx, repre in enumerate(list(repres)): + self.log.debug("repre ({}): `{}`".format(idx + 1, repre["name"])) + if not self._repre_is_valid(repre): + continue + + colorspace_data = repre["colorspaceData"] + source_color_space = colorspace_data["colorspace"] + config_path = colorspace_data.get("configData", {}).get("path") + if not os.path.exists(config_path): + self.log.warning("Config file doesn't exist, skipping") + continue + + repre = self._handle_original_repre(repre, profile) + + for _, output_def in profile.get("outputs", {}).items(): + new_repre = copy.deepcopy(repre) + + new_staging_dir = get_transcode_temp_directory() + original_staging_dir = new_repre["stagingDir"] + new_repre["stagingDir"] = new_staging_dir + files_to_convert = new_repre["files"] + if not isinstance(files_to_convert, list): + files_to_convert = [files_to_convert] + + files_to_delete = copy.deepcopy(files_to_convert) + + output_extension = output_def["output_extension"] + files_to_convert = self._rename_output_files(files_to_convert, + output_extension) + + files_to_convert = [os.path.join(original_staging_dir, path) + for path in files_to_convert] + + target_colorspace = output_def["output_colorspace"] + if not target_colorspace: + raise RuntimeError("Target colorspace must be set") + + convert_colorspace_for_input_paths( + files_to_convert, + new_staging_dir, + config_path, + source_color_space, + target_colorspace, + self.log + ) + + instance.context.data["cleanupFullPaths"].extend( + files_to_delete) + + custom_tags = output_def.get("custom_tags") + if custom_tags: + if not new_repre.get("custom_tags"): + new_repre["custom_tags"] = [] + new_repre["custom_tags"].extend(custom_tags) + + # Add additional tags from output definition to representation + for tag in output_def["tags"]: + if tag not in new_repre["tags"]: + new_repre["tags"].append(tag) + + instance.data["representations"].append(new_repre) + + def _rename_output_files(self, files_to_convert, output_extension): + """Change extension of converted files.""" + if output_extension: + output_extension = output_extension.replace('.', '') + renamed_files = [] + for file_name in files_to_convert: + file_name, _ = os.path.splitext(file_name) + new_file_name = '{}.{}'.format(file_name, + output_extension) + renamed_files.append(new_file_name) + files_to_convert = renamed_files + return files_to_convert + + def _get_profile(self, instance): + """Returns profile if and how repre should be color transcoded.""" host_name = instance.context.data["hostName"] family = instance.data["family"] task_data = instance.data["anatomyData"].get("task", {}) task_name = task_data.get("name") task_type = task_data.get("type") subset = instance.data["subset"] - filtering_criteria = { "hosts": host_name, "families": family, @@ -75,55 +158,15 @@ class ExtractColorTranscode(publish.Extractor): if not profile: self.log.info(( - "Skipped instance. None of profiles in presets are for" - " Host: \"{}\" | Families: \"{}\" | Task \"{}\"" - " | Task type \"{}\" | Subset \"{}\" " - ).format(host_name, family, task_name, task_type, subset)) - return + "Skipped instance. None of profiles in presets are for" + " Host: \"{}\" | Families: \"{}\" | Task \"{}\"" + " | Task type \"{}\" | Subset \"{}\" " + ).format(host_name, family, task_name, task_type, subset)) self.log.debug("profile: {}".format(profile)) + return profile - target_colorspace = profile["output_colorspace"] - if not target_colorspace: - raise RuntimeError("Target colorspace must be set") - custom_tags = profile["custom_tags"] - - repres = instance.data.get("representations") or [] - for idx, repre in enumerate(repres): - self.log.debug("repre ({}): `{}`".format(idx + 1, repre["name"])) - if not self._repre_is_valid(repre): - continue - source_color_space = colorspace_data["colorspace"] - config_path = colorspace_data.get("configData", {}).get("path") - if not os.path.exists(config_path): - self.log.warning("Config file doesn't exist, skipping") - continue - - new_staging_dir = get_transcode_temp_directory() - original_staging_dir = repre["stagingDir"] - repre["stagingDir"] = new_staging_dir - files_to_convert = repre["files"] - if not isinstance(files_to_convert, list): - files_to_convert = [files_to_convert] - files_to_convert = [os.path.join(original_staging_dir, path) - for path in files_to_convert] - instance.context.data["cleanupFullPaths"].extend(files_to_convert) - - convert_colorspace_for_input_paths( - files_to_convert, - new_staging_dir, - config_path, - source_color_space, - target_colorspace, - self.log - ) - - if custom_tags: - if not repre.get("custom_tags"): - repre["custom_tags"] = [] - repre["custom_tags"].extend(custom_tags) - - def repre_is_valid(self, repre): + def _repre_is_valid(self, repre): """Validation if representation should be processed. Args: @@ -144,4 +187,23 @@ class ExtractColorTranscode(publish.Extractor): "Representation \"{}\" have empty files. Skipped." ).format(repre["name"])) return False + + if not repre.get("colorspaceData"): + self.log.warning("Repre has not colorspace data, skipping") + return False + return True + + def _handle_original_repre(self, repre, profile): + delete_original = profile["delete_original"] + + if delete_original: + if not repre.get("tags"): + repre["tags"] = [] + + if "review" in repre["tags"]: + repre["tags"].remove("review") + if "delete" not in repre["tags"]: + repre["tags"].append("delete") + + return repre From bb85bc330a514f088fd2fad6037f60976e0e993b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 18:23:01 +0100 Subject: [PATCH 084/227] OP-4643 - switched logging levels Do not use warning unnecessary. --- openpype/plugins/publish/extract_color_transcode.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index b0c851d5f4..4d38514b8b 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -47,11 +47,11 @@ class ExtractColorTranscode(publish.Extractor): def process(self, instance): if not self.profiles: - self.log.warning("No profiles present for create burnin") + self.log.debug("No profiles present for color transcode") return if "representations" not in instance.data: - self.log.warning("No representations, skipping.") + self.log.debug("No representations, skipping.") return if not is_oiio_supported(): @@ -177,19 +177,19 @@ class ExtractColorTranscode(publish.Extractor): """ if repre.get("ext") not in self.supported_exts: - self.log.warning(( + self.log.debug(( "Representation \"{}\" of unsupported extension. Skipped." ).format(repre["name"])) return False if not repre.get("files"): - self.log.warning(( + self.log.debug(( "Representation \"{}\" have empty files. Skipped." ).format(repre["name"])) return False if not repre.get("colorspaceData"): - self.log.warning("Repre has not colorspace data, skipping") + self.log.debug("Repre has no colorspace data. Skipped.") return False return True From 1fd17b9f597dfef41852572deae8edbec65c3280 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 18:46:14 +0100 Subject: [PATCH 085/227] OP-4643 - propagate new extension to representation --- .../publish/extract_color_transcode.py | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 4d38514b8b..62cf8f0dee 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -90,8 +90,13 @@ class ExtractColorTranscode(publish.Extractor): files_to_delete = copy.deepcopy(files_to_convert) output_extension = output_def["output_extension"] - files_to_convert = self._rename_output_files(files_to_convert, - output_extension) + output_extension = output_extension.replace('.', '') + if output_extension: + new_repre["name"] = output_extension + new_repre["ext"] = output_extension + + files_to_convert = self._rename_output_files( + files_to_convert, output_extension) files_to_convert = [os.path.join(original_staging_dir, path) for path in files_to_convert] @@ -127,15 +132,13 @@ class ExtractColorTranscode(publish.Extractor): def _rename_output_files(self, files_to_convert, output_extension): """Change extension of converted files.""" - if output_extension: - output_extension = output_extension.replace('.', '') - renamed_files = [] - for file_name in files_to_convert: - file_name, _ = os.path.splitext(file_name) - new_file_name = '{}.{}'.format(file_name, - output_extension) - renamed_files.append(new_file_name) - files_to_convert = renamed_files + renamed_files = [] + for file_name in files_to_convert: + file_name, _ = os.path.splitext(file_name) + new_file_name = '{}.{}'.format(file_name, + output_extension) + renamed_files.append(new_file_name) + files_to_convert = renamed_files return files_to_convert def _get_profile(self, instance): From ffec1179ad1c9c07fae71db44ff2dc96b6fd7ec2 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 18:46:35 +0100 Subject: [PATCH 086/227] OP-4643 - added label to Settings --- .../projects_schema/schemas/schema_global_publish.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 c2c911d7d6..7155510fef 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 @@ -201,10 +201,14 @@ "type": "dict", "collapsible": true, "key": "ExtractColorTranscode", - "label": "ExtractColorTranscode", + "label": "ExtractColorTranscode (ImageIO)", "checkbox_key": "enabled", "is_group": true, "children": [ + { + "type": "label", + "label": "Configure output format(s) and color spaces for matching representations. Empty 'Output extension' denotes keeping source extension." + }, { "type": "boolean", "key": "enabled", From 5a562dc821b2cbefc780509b44c084bba786e80c Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 16 Jan 2023 18:22:08 +0100 Subject: [PATCH 087/227] OP-4643 - refactored according to review Function turned into single filepath input. --- openpype/lib/transcoding.py | 43 ++++++----- .../publish/extract_color_transcode.py | 72 ++++++++++--------- 2 files changed, 57 insertions(+), 58 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index ab86e44304..e1bd22d109 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1047,12 +1047,12 @@ def convert_ffprobe_fps_to_float(value): return dividend / divisor -def convert_colorspace_for_input_paths( - input_paths, - output_dir, +def convert_colorspace( + input_path, + out_filepath, config_path, - source_color_space, - target_color_space, + source_colorspace, + target_colorspace, logger=None ): """Convert source files from one color space to another. @@ -1063,13 +1063,13 @@ def convert_colorspace_for_input_paths( frame template Args: - input_paths (str): Paths that should be converted. It is expected that + input_path (str): Paths that should be converted. It is expected that contains single file or image sequence of samy type. - output_dir (str): Path to directory where output will be rendered. + out_filepath (str): Path to directory where output will be rendered. Must not be same as input's directory. config_path (str): path to OCIO config file - source_color_space (str): ocio valid color space of source files - target_color_space (str): ocio valid target color space + source_colorspace (str): ocio valid color space of source files + target_colorspace (str): ocio valid target color space logger (logging.Logger): Logger used for logging. """ @@ -1083,21 +1083,18 @@ def convert_colorspace_for_input_paths( # Don't add any additional attributes "--nosoftwareattrib", "--colorconfig", config_path, - "--colorconvert", source_color_space, target_color_space + "--colorconvert", source_colorspace, target_colorspace ] - for input_path in input_paths: - # Prepare subprocess arguments + # Prepare subprocess arguments - oiio_cmd.extend([ - input_arg, input_path, - ]) + oiio_cmd.extend([ + input_arg, input_path, + ]) - # Add last argument - path to output - base_filename = os.path.basename(input_path) - output_path = os.path.join(output_dir, base_filename) - oiio_cmd.extend([ - "-o", output_path - ]) + # Add last argument - path to output + oiio_cmd.extend([ + "-o", out_filepath + ]) - logger.debug("Conversion command: {}".format(" ".join(oiio_cmd))) - run_subprocess(oiio_cmd, logger=logger) + logger.debug("Conversion command: {}".format(" ".join(oiio_cmd))) + run_subprocess(oiio_cmd, logger=logger) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 62cf8f0dee..3a05426432 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -10,7 +10,7 @@ from openpype.lib import ( ) from openpype.lib.transcoding import ( - convert_colorspace_for_input_paths, + convert_colorspace, get_transcode_temp_directory, ) @@ -69,7 +69,7 @@ class ExtractColorTranscode(publish.Extractor): continue colorspace_data = repre["colorspaceData"] - source_color_space = colorspace_data["colorspace"] + source_colorspace = colorspace_data["colorspace"] config_path = colorspace_data.get("configData", {}).get("path") if not os.path.exists(config_path): self.log.warning("Config file doesn't exist, skipping") @@ -80,8 +80,8 @@ class ExtractColorTranscode(publish.Extractor): for _, output_def in profile.get("outputs", {}).items(): new_repre = copy.deepcopy(repre) - new_staging_dir = get_transcode_temp_directory() original_staging_dir = new_repre["stagingDir"] + new_staging_dir = get_transcode_temp_directory() new_repre["stagingDir"] = new_staging_dir files_to_convert = new_repre["files"] if not isinstance(files_to_convert, list): @@ -92,27 +92,28 @@ class ExtractColorTranscode(publish.Extractor): output_extension = output_def["output_extension"] output_extension = output_extension.replace('.', '') if output_extension: - new_repre["name"] = output_extension + if new_repre["name"] == new_repre["ext"]: + new_repre["name"] = output_extension new_repre["ext"] = output_extension - files_to_convert = self._rename_output_files( - files_to_convert, output_extension) - - files_to_convert = [os.path.join(original_staging_dir, path) - for path in files_to_convert] - target_colorspace = output_def["output_colorspace"] if not target_colorspace: raise RuntimeError("Target colorspace must be set") - convert_colorspace_for_input_paths( - files_to_convert, - new_staging_dir, - config_path, - source_color_space, - target_colorspace, - self.log - ) + for file_name in files_to_convert: + input_filepath = os.path.join(original_staging_dir, + file_name) + output_path = self._get_output_file_path(input_filepath, + new_staging_dir, + output_extension) + convert_colorspace( + input_filepath, + output_path, + config_path, + source_colorspace, + target_colorspace, + self.log + ) instance.context.data["cleanupFullPaths"].extend( files_to_delete) @@ -130,16 +131,16 @@ class ExtractColorTranscode(publish.Extractor): instance.data["representations"].append(new_repre) - def _rename_output_files(self, files_to_convert, output_extension): - """Change extension of converted files.""" - renamed_files = [] - for file_name in files_to_convert: - file_name, _ = os.path.splitext(file_name) - new_file_name = '{}.{}'.format(file_name, - output_extension) - renamed_files.append(new_file_name) - files_to_convert = renamed_files - return files_to_convert + def _get_output_file_path(self, input_filepath, output_dir, + output_extension): + """Create output file name path.""" + file_name = os.path.basename(input_filepath) + file_name, input_extension = os.path.splitext(file_name) + if not output_extension: + output_extension = input_extension + new_file_name = '{}.{}'.format(file_name, + output_extension) + return os.path.join(output_dir, new_file_name) def _get_profile(self, instance): """Returns profile if and how repre should be color transcoded.""" @@ -161,10 +162,10 @@ class ExtractColorTranscode(publish.Extractor): if not profile: self.log.info(( - "Skipped instance. None of profiles in presets are for" - " Host: \"{}\" | Families: \"{}\" | Task \"{}\"" - " | Task type \"{}\" | Subset \"{}\" " - ).format(host_name, family, task_name, task_type, subset)) + "Skipped instance. None of profiles in presets are for" + " Host: \"{}\" | Families: \"{}\" | Task \"{}\"" + " | Task type \"{}\" | Subset \"{}\" " + ).format(host_name, family, task_name, task_type, subset)) self.log.debug("profile: {}".format(profile)) return profile @@ -181,18 +182,19 @@ class ExtractColorTranscode(publish.Extractor): if repre.get("ext") not in self.supported_exts: self.log.debug(( - "Representation \"{}\" of unsupported extension. Skipped." + "Representation '{}' of unsupported extension. Skipped." ).format(repre["name"])) return False if not repre.get("files"): self.log.debug(( - "Representation \"{}\" have empty files. Skipped." + "Representation '{}' have empty files. Skipped." ).format(repre["name"])) return False if not repre.get("colorspaceData"): - self.log.debug("Repre has no colorspace data. Skipped.") + self.log.debug("Representation '{}' has no colorspace data. " + "Skipped.") return False return True From e0a163bc2e836c9292e52757abab43d26bc44c07 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 26 Jan 2023 12:53:02 +0100 Subject: [PATCH 088/227] OP-4643 - updated schema Co-authored-by: Toke Jepsen --- .../projects_schema/schemas/schema_global_publish.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 7155510fef..80c18ce118 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 @@ -267,8 +267,8 @@ "type": "dict", "children": [ { - "key": "output_extension", - "label": "Output extension", + "key": "extension", + "label": "Extension", "type": "text" }, { From 657c3156dfc046405cee116c6ccb141e60901d23 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 26 Jan 2023 12:54:46 +0100 Subject: [PATCH 089/227] OP-4643 - updated plugin name in schema MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jakub Ježek --- .../projects_schema/schemas/schema_global_publish.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 80c18ce118..357cbfb287 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 @@ -200,8 +200,8 @@ { "type": "dict", "collapsible": true, - "key": "ExtractColorTranscode", - "label": "ExtractColorTranscode (ImageIO)", + "key": "ExtractOIIOTranscode", + "label": "Extract OIIO Transcode", "checkbox_key": "enabled", "is_group": true, "children": [ From 4b5c14e46686e471f20bef6909d6287b2eae6859 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 26 Jan 2023 12:55:57 +0100 Subject: [PATCH 090/227] OP-4643 - updated key in schema Co-authored-by: Toke Jepsen --- .../projects_schema/schemas/schema_global_publish.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 357cbfb287..0281b0ded6 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 @@ -272,8 +272,8 @@ "type": "text" }, { - "key": "output_colorspace", - "label": "Output colorspace", + "key": "colorspace", + "label": "Colorspace", "type": "text" }, { From 756661f71b762fb738bd793984787941f2051e4d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 26 Jan 2023 12:57:03 +0100 Subject: [PATCH 091/227] OP-4643 - changed oiio_cmd creation Co-authored-by: Toke Jepsen --- openpype/lib/transcoding.py | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index e1bd22d109..f22628dd28 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1076,25 +1076,15 @@ def convert_colorspace( if logger is None: logger = logging.getLogger(__name__) - input_arg = "-i" oiio_cmd = [ get_oiio_tools_path(), - + input_path, # Don't add any additional attributes "--nosoftwareattrib", "--colorconfig", config_path, - "--colorconvert", source_colorspace, target_colorspace - ] - # Prepare subprocess arguments - - oiio_cmd.extend([ - input_arg, input_path, - ]) - - # Add last argument - path to output - oiio_cmd.extend([ + "--colorconvert", source_colorspace, target_colorspace, "-o", out_filepath - ]) + ] logger.debug("Conversion command: {}".format(" ".join(oiio_cmd))) run_subprocess(oiio_cmd, logger=logger) From c3bf4734fdabc019c966bae93e131235f2560f34 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 26 Jan 2023 13:44:45 +0100 Subject: [PATCH 092/227] OP-4643 - updated new keys into settings --- .../settings/defaults/project_settings/global.json | 2 +- .../projects_schema/schemas/schema_global_publish.json | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 8485bec67b..a5e2d25a88 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -68,7 +68,7 @@ "output": [] } }, - "ExtractColorTranscode": { + "ExtractOIIOTranscode": { "enabled": true, "profiles": [] }, 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 0281b0ded6..74b81b13af 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 @@ -276,6 +276,16 @@ "label": "Colorspace", "type": "text" }, + { + "key": "display", + "label": "Display", + "type": "text" + }, + { + "key": "view", + "label": "View", + "type": "text" + }, { "type": "schema", "name": "schema_representation_tags" From 2b20ede6d86924a19e5bf5ad9697f451a85a757e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 26 Jan 2023 13:45:42 +0100 Subject: [PATCH 093/227] OP-4643 - renanmed plugin, added new keys into outputs --- openpype/plugins/publish/extract_color_transcode.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 3a05426432..cc63b35988 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -17,7 +17,7 @@ from openpype.lib.transcoding import ( from openpype.lib.profiles_filtering import filter_profiles -class ExtractColorTranscode(publish.Extractor): +class ExtractOIIOTranscode(publish.Extractor): """ Extractor to convert colors from one colorspace to different. @@ -89,14 +89,14 @@ class ExtractColorTranscode(publish.Extractor): files_to_delete = copy.deepcopy(files_to_convert) - output_extension = output_def["output_extension"] + output_extension = output_def["extension"] output_extension = output_extension.replace('.', '') if output_extension: if new_repre["name"] == new_repre["ext"]: new_repre["name"] = output_extension new_repre["ext"] = output_extension - target_colorspace = output_def["output_colorspace"] + target_colorspace = output_def["colorspace"] if not target_colorspace: raise RuntimeError("Target colorspace must be set") From 8b47a44d04aace508cc05608a18c4f7cce23cb9b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 26 Jan 2023 18:03:13 +0100 Subject: [PATCH 094/227] OP-4643 - fixed config path key --- openpype/plugins/publish/extract_color_transcode.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index cc63b35988..245faeb306 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -70,8 +70,8 @@ class ExtractOIIOTranscode(publish.Extractor): colorspace_data = repre["colorspaceData"] source_colorspace = colorspace_data["colorspace"] - config_path = colorspace_data.get("configData", {}).get("path") - if not os.path.exists(config_path): + config_path = colorspace_data.get("config", {}).get("path") + if not config_path or not os.path.exists(config_path): self.log.warning("Config file doesn't exist, skipping") continue From ed7faeef8f0a7678e99f70645d57a72864cc9461 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 26 Jan 2023 18:03:42 +0100 Subject: [PATCH 095/227] OP-4643 - fixed renaming files --- openpype/plugins/publish/extract_color_transcode.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 245faeb306..c079dcf70e 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -96,6 +96,14 @@ class ExtractOIIOTranscode(publish.Extractor): new_repre["name"] = output_extension new_repre["ext"] = output_extension + renamed_files = [] + _, orig_ext = os.path.splitext(files_to_convert[0]) + for file_name in files_to_convert: + file_name = file_name.replace(orig_ext, + "."+output_extension) + renamed_files.append(file_name) + new_repre["files"] = renamed_files + target_colorspace = output_def["colorspace"] if not target_colorspace: raise RuntimeError("Target colorspace must be set") From 4b1418a79242eb63cfd4295d70d474b992c276ea Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 26 Jan 2023 18:04:44 +0100 Subject: [PATCH 096/227] OP-4643 - updated to calculate sequence format --- .../publish/extract_color_transcode.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index c079dcf70e..09c86909cb 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -1,5 +1,6 @@ import os import copy +import clique import pyblish.api @@ -108,6 +109,8 @@ class ExtractOIIOTranscode(publish.Extractor): if not target_colorspace: raise RuntimeError("Target colorspace must be set") + files_to_convert = self._translate_to_sequence( + files_to_convert) for file_name in files_to_convert: input_filepath = os.path.join(original_staging_dir, file_name) @@ -139,6 +142,40 @@ class ExtractOIIOTranscode(publish.Extractor): instance.data["representations"].append(new_repre) + def _translate_to_sequence(self, files_to_convert): + """Returns original list of files or single sequence format filename. + + Uses clique to find frame sequence, in this case it merges all frames + into sequence format (%0X) and returns it. + If sequence not found, it returns original list + + Args: + files_to_convert (list): list of file names + Returns: + (list) of [file.%04.exr] or [fileA.exr, fileB.exr] + """ + pattern = [clique.PATTERNS["frames"]] + collections, remainder = clique.assemble( + files_to_convert, patterns=pattern, + assume_padded_when_ambiguous=True) + + if collections: + if len(collections) > 1: + raise ValueError( + "Too many collections {}".format(collections)) + + collection = collections[0] + padding = collection.padding + padding_str = "%0{}".format(padding) + frames = list(collection.indexes) + frame_str = "{}-{}#".format(frames[0], frames[-1]) + file_name = "{}{}{}".format(collection.head, frame_str, + collection.tail) + + files_to_convert = [file_name] + + return files_to_convert + def _get_output_file_path(self, input_filepath, output_dir, output_extension): """Create output file name path.""" From 382074b54cc0eb7a7c6d403853f4350d485fa00a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 26 Jan 2023 18:54:02 +0100 Subject: [PATCH 097/227] OP-4643 - implemented display and viewer color space --- openpype/lib/transcoding.py | 23 +++++++++++++++++-- .../publish/extract_color_transcode.py | 13 +++++++++-- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index f22628dd28..cc9cd4e1eb 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1053,6 +1053,8 @@ def convert_colorspace( config_path, source_colorspace, target_colorspace, + view, + display, logger=None ): """Convert source files from one color space to another. @@ -1070,8 +1072,11 @@ def convert_colorspace( config_path (str): path to OCIO config file source_colorspace (str): ocio valid color space of source files target_colorspace (str): ocio valid target color space + view (str): name for viewer space (ocio valid) + display (str): name for display-referred reference space (ocio valid) logger (logging.Logger): Logger used for logging. - + Raises: + ValueError: if misconfigured """ if logger is None: logger = logging.getLogger(__name__) @@ -1082,9 +1087,23 @@ def convert_colorspace( # Don't add any additional attributes "--nosoftwareattrib", "--colorconfig", config_path, - "--colorconvert", source_colorspace, target_colorspace, "-o", out_filepath ] + if all([target_colorspace, view, display]): + raise ValueError("Colorspace and both screen and display" + " cannot be set together." + "Choose colorspace or screen and display") + if not target_colorspace and not all([view, display]): + raise ValueError("Both screen and display must be set.") + + if target_colorspace: + oiio_cmd.extend(["--colorconvert", + source_colorspace, + target_colorspace]) + if view and display: + oiio_cmd.extend(["--iscolorspace", source_colorspace]) + oiio_cmd.extend(["--ociodisplay", display, view]) + logger.debug("Conversion command: {}".format(" ".join(oiio_cmd))) run_subprocess(oiio_cmd, logger=logger) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 09c86909cb..cd8421c0cd 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -106,8 +106,15 @@ class ExtractOIIOTranscode(publish.Extractor): new_repre["files"] = renamed_files target_colorspace = output_def["colorspace"] - if not target_colorspace: - raise RuntimeError("Target colorspace must be set") + view = output_def["view"] or colorspace_data.get("view") + display = (output_def["display"] or + colorspace_data.get("display")) + # both could be already collected by DCC, + # but could be overwritten + if view: + new_repre["colorspaceData"]["view"] = view + if display: + new_repre["colorspaceData"]["view"] = display files_to_convert = self._translate_to_sequence( files_to_convert) @@ -123,6 +130,8 @@ class ExtractOIIOTranscode(publish.Extractor): config_path, source_colorspace, target_colorspace, + view, + display, self.log ) From 8873c9f9056b9b672fe95be0707c9ab8753726b8 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 26 Jan 2023 19:03:10 +0100 Subject: [PATCH 098/227] OP-4643 - fix wrong order of deletion of representation --- openpype/plugins/publish/extract_color_transcode.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index cd8421c0cd..9cca5cc969 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -69,6 +69,8 @@ class ExtractOIIOTranscode(publish.Extractor): if not self._repre_is_valid(repre): continue + added_representations = False + colorspace_data = repre["colorspaceData"] source_colorspace = colorspace_data["colorspace"] config_path = colorspace_data.get("config", {}).get("path") @@ -76,8 +78,6 @@ class ExtractOIIOTranscode(publish.Extractor): self.log.warning("Config file doesn't exist, skipping") continue - repre = self._handle_original_repre(repre, profile) - for _, output_def in profile.get("outputs", {}).items(): new_repre = copy.deepcopy(repre) @@ -150,6 +150,10 @@ class ExtractOIIOTranscode(publish.Extractor): new_repre["tags"].append(tag) instance.data["representations"].append(new_repre) + added_representations = True + + if added_representations: + self._mark_original_repre_for_deletion(repre, profile) def _translate_to_sequence(self, files_to_convert): """Returns original list of files or single sequence format filename. @@ -253,7 +257,8 @@ class ExtractOIIOTranscode(publish.Extractor): return True - def _handle_original_repre(self, repre, profile): + def _mark_original_repre_for_deletion(self, repre, profile): + """If new transcoded representation created, delete old.""" delete_original = profile["delete_original"] if delete_original: @@ -264,5 +269,3 @@ class ExtractOIIOTranscode(publish.Extractor): repre["tags"].remove("review") if "delete" not in repre["tags"]: repre["tags"].append("delete") - - return repre From 176f53117fe49410135121dc0b858eadb8041270 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 27 Jan 2023 11:26:27 +0100 Subject: [PATCH 099/227] OP-4643 - updated docstring, standardized arguments --- openpype/lib/transcoding.py | 19 +++++++---------- .../publish/extract_color_transcode.py | 21 +++++++++---------- 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index cc9cd4e1eb..0f6d35affe 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1049,7 +1049,7 @@ def convert_ffprobe_fps_to_float(value): def convert_colorspace( input_path, - out_filepath, + output_path, config_path, source_colorspace, target_colorspace, @@ -1057,18 +1057,13 @@ def convert_colorspace( display, logger=None ): - """Convert source files from one color space to another. - - Filenames of input files are kept so make sure that output directory - is not the same directory as input files have. - - This way it can handle gaps and can keep input filenames without handling - frame template + """Convert source file from one color space to another. Args: - input_path (str): Paths that should be converted. It is expected that - contains single file or image sequence of samy type. - out_filepath (str): Path to directory where output will be rendered. - Must not be same as input's directory. + input_path (str): Path that should be converted. It is expected that + contains single file or image sequence of same type + (sequence in format 'file.FRAMESTART-FRAMEEND#.exr', see oiio docs) + output_path (str): Path to output filename. config_path (str): path to OCIO config file source_colorspace (str): ocio valid color space of source files target_colorspace (str): ocio valid target color space @@ -1087,7 +1082,7 @@ def convert_colorspace( # Don't add any additional attributes "--nosoftwareattrib", "--colorconfig", config_path, - "-o", out_filepath + "-o", output_path ] if all([target_colorspace, view, display]): diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 9cca5cc969..c4cef15ea6 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -119,13 +119,13 @@ class ExtractOIIOTranscode(publish.Extractor): files_to_convert = self._translate_to_sequence( files_to_convert) for file_name in files_to_convert: - input_filepath = os.path.join(original_staging_dir, - file_name) - output_path = self._get_output_file_path(input_filepath, + input_path = os.path.join(original_staging_dir, + file_name) + output_path = self._get_output_file_path(input_path, new_staging_dir, output_extension) convert_colorspace( - input_filepath, + input_path, output_path, config_path, source_colorspace, @@ -156,16 +156,17 @@ class ExtractOIIOTranscode(publish.Extractor): self._mark_original_repre_for_deletion(repre, profile) def _translate_to_sequence(self, files_to_convert): - """Returns original list of files or single sequence format filename. + """Returns original list or list with filename formatted in single + sequence format. Uses clique to find frame sequence, in this case it merges all frames - into sequence format (%0X) and returns it. + into sequence format (FRAMESTART-FRAMEEND#) and returns it. If sequence not found, it returns original list Args: files_to_convert (list): list of file names Returns: - (list) of [file.%04.exr] or [fileA.exr, fileB.exr] + (list) of [file.1001-1010#.exr] or [fileA.exr, fileB.exr] """ pattern = [clique.PATTERNS["frames"]] collections, remainder = clique.assemble( @@ -178,8 +179,6 @@ class ExtractOIIOTranscode(publish.Extractor): "Too many collections {}".format(collections)) collection = collections[0] - padding = collection.padding - padding_str = "%0{}".format(padding) frames = list(collection.indexes) frame_str = "{}-{}#".format(frames[0], frames[-1]) file_name = "{}{}{}".format(collection.head, frame_str, @@ -189,10 +188,10 @@ class ExtractOIIOTranscode(publish.Extractor): return files_to_convert - def _get_output_file_path(self, input_filepath, output_dir, + def _get_output_file_path(self, input_path, output_dir, output_extension): """Create output file name path.""" - file_name = os.path.basename(input_filepath) + file_name = os.path.basename(input_path) file_name, input_extension = os.path.splitext(file_name) if not output_extension: output_extension = input_extension From 04ae5a28b415197f36d198e409e8343bc4bc6022 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 27 Jan 2023 11:27:06 +0100 Subject: [PATCH 100/227] OP-4643 - fix wrong assignment --- openpype/plugins/publish/extract_color_transcode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index c4cef15ea6..4e899a519c 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -114,7 +114,7 @@ class ExtractOIIOTranscode(publish.Extractor): if view: new_repre["colorspaceData"]["view"] = view if display: - new_repre["colorspaceData"]["view"] = display + new_repre["colorspaceData"]["display"] = display files_to_convert = self._translate_to_sequence( files_to_convert) From c6571b9dfd520a77877c84a86c60999ab5257a3a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 27 Jan 2023 11:59:13 +0100 Subject: [PATCH 101/227] OP-4643 - fix files to delete --- .../publish/extract_color_transcode.py | 45 ++++++++++++------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 4e899a519c..99e684ba21 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -84,26 +84,18 @@ class ExtractOIIOTranscode(publish.Extractor): original_staging_dir = new_repre["stagingDir"] new_staging_dir = get_transcode_temp_directory() new_repre["stagingDir"] = new_staging_dir - files_to_convert = new_repre["files"] - if not isinstance(files_to_convert, list): - files_to_convert = [files_to_convert] - files_to_delete = copy.deepcopy(files_to_convert) + if isinstance(new_repre["files"], list): + files_to_convert = copy.deepcopy(new_repre["files"]) + else: + files_to_convert = [new_repre["files"]] output_extension = output_def["extension"] output_extension = output_extension.replace('.', '') if output_extension: - if new_repre["name"] == new_repre["ext"]: - new_repre["name"] = output_extension - new_repre["ext"] = output_extension - - renamed_files = [] - _, orig_ext = os.path.splitext(files_to_convert[0]) - for file_name in files_to_convert: - file_name = file_name.replace(orig_ext, - "."+output_extension) - renamed_files.append(file_name) - new_repre["files"] = renamed_files + self._rename_in_representation(new_repre, + files_to_convert, + output_extension) target_colorspace = output_def["colorspace"] view = output_def["view"] or colorspace_data.get("view") @@ -135,8 +127,12 @@ class ExtractOIIOTranscode(publish.Extractor): self.log ) - instance.context.data["cleanupFullPaths"].extend( - files_to_delete) + # cleanup temporary transcoded files + for file_name in new_repre["files"]: + transcoded_file_path = os.path.join(new_staging_dir, + file_name) + instance.context.data["cleanupFullPaths"].append( + transcoded_file_path) custom_tags = output_def.get("custom_tags") if custom_tags: @@ -155,6 +151,21 @@ class ExtractOIIOTranscode(publish.Extractor): if added_representations: self._mark_original_repre_for_deletion(repre, profile) + def _rename_in_representation(self, new_repre, files_to_convert, + output_extension): + """Replace old extension with new one everywhere in representation.""" + if new_repre["name"] == new_repre["ext"]: + new_repre["name"] = output_extension + new_repre["ext"] = output_extension + + renamed_files = [] + for file_name in files_to_convert: + file_name, _ = os.path.splitext(file_name) + file_name = '{}.{}'.format(file_name, + output_extension) + renamed_files.append(file_name) + new_repre["files"] = renamed_files + def _translate_to_sequence(self, files_to_convert): """Returns original list or list with filename formatted in single sequence format. From 8598c1ec393a1c8c0f05ad12a2a66bbf7eb89136 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 27 Jan 2023 13:17:59 +0100 Subject: [PATCH 102/227] OP-4643 - moved output argument to the end --- openpype/lib/transcoding.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 0f6d35affe..e74dab4ccc 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1081,8 +1081,7 @@ def convert_colorspace( input_path, # Don't add any additional attributes "--nosoftwareattrib", - "--colorconfig", config_path, - "-o", output_path + "--colorconfig", config_path ] if all([target_colorspace, view, display]): @@ -1100,5 +1099,7 @@ def convert_colorspace( oiio_cmd.extend(["--iscolorspace", source_colorspace]) oiio_cmd.extend(["--ociodisplay", display, view]) + oiio_cmd.extend(["-o", output_path]) + logger.debug("Conversion command: {}".format(" ".join(oiio_cmd))) run_subprocess(oiio_cmd, logger=logger) From afe0a97bc5ace60a5f7740d22b3d1937e1b69fdf Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 27 Jan 2023 13:18:33 +0100 Subject: [PATCH 103/227] OP-4643 - fix no tags in repre --- openpype/plugins/publish/extract_color_transcode.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 99e684ba21..3d897c6d9f 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -142,6 +142,8 @@ class ExtractOIIOTranscode(publish.Extractor): # Add additional tags from output definition to representation for tag in output_def["tags"]: + if not new_repre.get("tags"): + new_repre["tags"] = [] if tag not in new_repre["tags"]: new_repre["tags"].append(tag) From 190a79a836d6e981e57f9f23be58d146193c4d6c Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 27 Jan 2023 13:26:07 +0100 Subject: [PATCH 104/227] OP-4643 - changed docstring Elaborated more that 'target_colorspace' and ('view', 'display') are disjunctive. --- openpype/lib/transcoding.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index e74dab4ccc..f7d5e222c8 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1053,8 +1053,8 @@ def convert_colorspace( config_path, source_colorspace, target_colorspace, - view, - display, + view=None, + display=None, logger=None ): """Convert source file from one color space to another. @@ -1067,7 +1067,9 @@ def convert_colorspace( config_path (str): path to OCIO config file source_colorspace (str): ocio valid color space of source files target_colorspace (str): ocio valid target color space + if filled, 'view' and 'display' must be empty view (str): name for viewer space (ocio valid) + both 'view' and 'display' must be filled (if 'target_colorspace') display (str): name for display-referred reference space (ocio valid) logger (logging.Logger): Logger used for logging. Raises: From 4967d91010dc03df5640364350095f0fae9aa52c Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 1 Feb 2023 11:14:07 +0100 Subject: [PATCH 105/227] OP-4663 - fix double dots in extension Co-authored-by: Toke Jepsen --- openpype/plugins/publish/extract_color_transcode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 3d897c6d9f..bfed69c300 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -207,7 +207,7 @@ class ExtractOIIOTranscode(publish.Extractor): file_name = os.path.basename(input_path) file_name, input_extension = os.path.splitext(file_name) if not output_extension: - output_extension = input_extension + output_extension = input_extension.replace(".", "") new_file_name = '{}.{}'.format(file_name, output_extension) return os.path.join(output_dir, new_file_name) From 43df616692568352b9734715e3a48b71a60e2221 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 1 Feb 2023 16:11:45 +0100 Subject: [PATCH 106/227] OP-4643 - update documentation in Settings schema --- .../schemas/projects_schema/schemas/schema_global_publish.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 74b81b13af..3956f403f4 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 @@ -207,7 +207,7 @@ "children": [ { "type": "label", - "label": "Configure output format(s) and color spaces for matching representations. Empty 'Output extension' denotes keeping source extension." + "label": "Configure Output Definition(s) for new representation(s). \nEmpty 'Extension' denotes keeping source extension. \nName(key) of output definition will be used as new representation name \nunless 'passthrough' value is used to keep existing name. \nFill either 'Colorspace' (for target colorspace) or \nboth 'Display' and 'View' (for display and viewer colorspaces)." }, { "type": "boolean", From 5b72bafcfc9a33302f588e29d314d91b98186f87 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 1 Feb 2023 16:13:59 +0100 Subject: [PATCH 107/227] OP-4643 - name of new representation from output definition key --- .../publish/extract_color_transcode.py | 49 +++++++++++++++---- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index bfed69c300..e39ea3add9 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -32,6 +32,25 @@ class ExtractOIIOTranscode(publish.Extractor): - task types - task names - subset names + + Can produce one or more representations (with different extensions) based + on output definition in format: + "output_name: { + "extension": "png", + "colorspace": "ACES - ACEScg", + "display": "", + "view": "", + "tags": [], + "custom_tags": [] + } + + If 'extension' is empty original representation extension is used. + 'output_name' will be used as name of new representation. In case of value + 'passthrough' name of original representation will be used. + + 'colorspace' denotes target colorspace to be transcoded into. Could be + empty if transcoding should be only into display and viewer colorspace. + (In that case both 'display' and 'view' must be filled.) """ label = "Transcode color spaces" @@ -78,7 +97,7 @@ class ExtractOIIOTranscode(publish.Extractor): self.log.warning("Config file doesn't exist, skipping") continue - for _, output_def in profile.get("outputs", {}).items(): + for output_name, output_def in profile.get("outputs", {}).items(): new_repre = copy.deepcopy(repre) original_staging_dir = new_repre["stagingDir"] @@ -92,10 +111,10 @@ class ExtractOIIOTranscode(publish.Extractor): output_extension = output_def["extension"] output_extension = output_extension.replace('.', '') - if output_extension: - self._rename_in_representation(new_repre, - files_to_convert, - output_extension) + self._rename_in_representation(new_repre, + files_to_convert, + output_name, + output_extension) target_colorspace = output_def["colorspace"] view = output_def["view"] or colorspace_data.get("view") @@ -154,10 +173,22 @@ class ExtractOIIOTranscode(publish.Extractor): self._mark_original_repre_for_deletion(repre, profile) def _rename_in_representation(self, new_repre, files_to_convert, - output_extension): - """Replace old extension with new one everywhere in representation.""" - if new_repre["name"] == new_repre["ext"]: - new_repre["name"] = output_extension + output_name, output_extension): + """Replace old extension with new one everywhere in representation. + + Args: + new_repre (dict) + files_to_convert (list): of filenames from repre["files"], + standardized to always list + output_name (str): key of output definition from Settings, + if "" token used, keep original repre name + output_extension (str): extension from output definition + """ + if output_name != "passthrough": + new_repre["name"] = output_name + if not output_extension: + return + new_repre["ext"] = output_extension renamed_files = [] From 7eacd1f30f2ce92adcec6ce8e48fb62bfb92a148 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 1 Feb 2023 16:42:48 +0100 Subject: [PATCH 108/227] OP-4643 - updated docstring for convert_colorspace --- openpype/lib/transcoding.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index f7d5e222c8..b6edd863f8 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1062,8 +1062,11 @@ def convert_colorspace( Args: input_path (str): Path that should be converted. It is expected that contains single file or image sequence of same type - (sequence in format 'file.FRAMESTART-FRAMEEND#.exr', see oiio docs) + (sequence in format 'file.FRAMESTART-FRAMEEND#.ext', see oiio docs, + eg `big.1-3#.tif`) output_path (str): Path to output filename. + (must follow format of 'input_path', eg. single file or + sequence in 'file.FRAMESTART-FRAMEEND#.ext', `output.1-3#.tif`) config_path (str): path to OCIO config file source_colorspace (str): ocio valid color space of source files target_colorspace (str): ocio valid target color space From 440c4e0c100a413f62aaaf582201384124ba916b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 7 Feb 2023 18:22:10 +0100 Subject: [PATCH 109/227] OP-4643 - remove review from old representation If new representation gets created and adds 'review' tag it becomes new reviewable representation. --- .../publish/extract_color_transcode.py | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index e39ea3add9..d10b887a0b 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -89,6 +89,7 @@ class ExtractOIIOTranscode(publish.Extractor): continue added_representations = False + added_review = False colorspace_data = repre["colorspaceData"] source_colorspace = colorspace_data["colorspace"] @@ -166,11 +167,15 @@ class ExtractOIIOTranscode(publish.Extractor): if tag not in new_repre["tags"]: new_repre["tags"].append(tag) + if tag == "review": + added_review = True + instance.data["representations"].append(new_repre) added_representations = True if added_representations: - self._mark_original_repre_for_deletion(repre, profile) + self._mark_original_repre_for_deletion(repre, profile, + added_review) def _rename_in_representation(self, new_repre, files_to_convert, output_name, output_extension): @@ -300,15 +305,16 @@ class ExtractOIIOTranscode(publish.Extractor): return True - def _mark_original_repre_for_deletion(self, repre, profile): + def _mark_original_repre_for_deletion(self, repre, profile, added_review): """If new transcoded representation created, delete old.""" + if not repre.get("tags"): + repre["tags"] = [] + delete_original = profile["delete_original"] if delete_original: - if not repre.get("tags"): - repre["tags"] = [] - - if "review" in repre["tags"]: - repre["tags"].remove("review") if "delete" not in repre["tags"]: repre["tags"].append("delete") + + if added_review and "review" in repre["tags"]: + repre["tags"].remove("review") From 212cbe79a2eb05f3b426a01d836d42964838bbbb Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 7 Feb 2023 18:23:42 +0100 Subject: [PATCH 110/227] OP-4643 - remove representation that should be deleted Or old revieable representation would be reviewed too. --- openpype/plugins/publish/extract_color_transcode.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index d10b887a0b..93ee1ec44d 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -177,6 +177,11 @@ class ExtractOIIOTranscode(publish.Extractor): self._mark_original_repre_for_deletion(repre, profile, added_review) + for repre in tuple(instance.data["representations"]): + tags = repre.get("tags") or [] + if "delete" in tags and "thumbnail" not in tags: + instance.data["representations"].remove(repre) + def _rename_in_representation(self, new_repre, files_to_convert, output_name, output_extension): """Replace old extension with new one everywhere in representation. From 6295d0f34a9efc8e86ebbbf1dbe4b40a391dbf7b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 14 Feb 2023 14:54:25 +0100 Subject: [PATCH 111/227] OP-4643 - fix logging Wrong variable used --- openpype/plugins/publish/extract_review.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index dcb43d7fa2..0f6dacba18 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -169,7 +169,7 @@ class ExtractReview(pyblish.api.InstancePlugin): "Skipped representation. All output definitions from" " selected profile does not match to representation's" " custom tags. \"{}\"" - ).format(str(tags))) + ).format(str(custom_tags))) continue outputs_per_representations.append((repre, outputs)) From 03e8661323622c39d91c647d2f6cdd615a4ca99c Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 14 Feb 2023 15:14:14 +0100 Subject: [PATCH 112/227] OP-4643 - allow new repre to stay One might want to delete outputs with 'delete' tag, but repre must stay there at least until extract_review. More universal new tag might be created for this. --- openpype/plugins/publish/extract_color_transcode.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 93ee1ec44d..4a03e623fd 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -161,15 +161,17 @@ class ExtractOIIOTranscode(publish.Extractor): new_repre["custom_tags"].extend(custom_tags) # Add additional tags from output definition to representation + if not new_repre.get("tags"): + new_repre["tags"] = [] for tag in output_def["tags"]: - if not new_repre.get("tags"): - new_repre["tags"] = [] if tag not in new_repre["tags"]: new_repre["tags"].append(tag) if tag == "review": added_review = True + new_repre["tags"].append("newly_added") + instance.data["representations"].append(new_repre) added_representations = True @@ -179,6 +181,12 @@ class ExtractOIIOTranscode(publish.Extractor): for repre in tuple(instance.data["representations"]): tags = repre.get("tags") or [] + # TODO implement better way, for now do not delete new repre + # new repre might have 'delete' tag to removed, but it first must + # be there for review to be created + if "newly_added" in tags: + tags.remove("newly_added") + continue if "delete" in tags and "thumbnail" not in tags: instance.data["representations"].remove(repre) From f4140d7664cb3a36c032f4ae4b6d0a240fc01d3a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 15 Feb 2023 16:04:59 +0100 Subject: [PATCH 113/227] OP-4642 - added additional command arguments to Settings --- .../schemas/schema_global_publish.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) 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 3956f403f4..5333d514b5 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 @@ -286,6 +286,20 @@ "label": "View", "type": "text" }, + { + "key": "oiiotool_args", + "label": "OIIOtool arguments", + "type": "dict", + "highlight_content": true, + "children": [ + { + "key": "additional_command_args", + "label": "Additional command line arguments", + "type": "list", + "object_type": "text" + } + ] + }, { "type": "schema", "name": "schema_representation_tags" From b1d30058b0cdb26e46618d37807b01d400c89e33 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 15 Feb 2023 16:08:06 +0100 Subject: [PATCH 114/227] OP-4642 - added additional command arguments for oiiotool Some extension requires special command line arguments (.dpx and binary depth). --- openpype/lib/transcoding.py | 6 ++++++ openpype/plugins/publish/extract_color_transcode.py | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index b6edd863f8..982cee7a46 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1055,6 +1055,7 @@ def convert_colorspace( target_colorspace, view=None, display=None, + additional_command_args=None, logger=None ): """Convert source file from one color space to another. @@ -1074,6 +1075,8 @@ def convert_colorspace( view (str): name for viewer space (ocio valid) both 'view' and 'display' must be filled (if 'target_colorspace') display (str): name for display-referred reference space (ocio valid) + additional_command_args (list): arguments for oiiotool (like binary + depth for .dpx) logger (logging.Logger): Logger used for logging. Raises: ValueError: if misconfigured @@ -1096,6 +1099,9 @@ def convert_colorspace( if not target_colorspace and not all([view, display]): raise ValueError("Both screen and display must be set.") + if additional_command_args: + oiio_cmd.extend(additional_command_args) + if target_colorspace: oiio_cmd.extend(["--colorconvert", source_colorspace, diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 4a03e623fd..3de404125d 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -128,6 +128,9 @@ class ExtractOIIOTranscode(publish.Extractor): if display: new_repre["colorspaceData"]["display"] = display + additional_command_args = (output_def["oiiotool_args"] + ["additional_command_args"]) + files_to_convert = self._translate_to_sequence( files_to_convert) for file_name in files_to_convert: @@ -144,6 +147,7 @@ class ExtractOIIOTranscode(publish.Extractor): target_colorspace, view, display, + additional_command_args, self.log ) From e8a79b4f7673a37657b54a62f74c81985a2b5636 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 15 Feb 2023 16:21:25 +0100 Subject: [PATCH 115/227] OP-4642 - refactored newly added representations --- openpype/plugins/publish/extract_color_transcode.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 3de404125d..8c4ef59de9 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -82,6 +82,7 @@ class ExtractOIIOTranscode(publish.Extractor): if not profile: return + new_representations = [] repres = instance.data.get("representations") or [] for idx, repre in enumerate(list(repres)): self.log.debug("repre ({}): `{}`".format(idx + 1, repre["name"])) @@ -174,9 +175,7 @@ class ExtractOIIOTranscode(publish.Extractor): if tag == "review": added_review = True - new_repre["tags"].append("newly_added") - - instance.data["representations"].append(new_repre) + new_representations.append(new_repre) added_representations = True if added_representations: @@ -185,15 +184,11 @@ class ExtractOIIOTranscode(publish.Extractor): for repre in tuple(instance.data["representations"]): tags = repre.get("tags") or [] - # TODO implement better way, for now do not delete new repre - # new repre might have 'delete' tag to removed, but it first must - # be there for review to be created - if "newly_added" in tags: - tags.remove("newly_added") - continue if "delete" in tags and "thumbnail" not in tags: instance.data["representations"].remove(repre) + instance.data["representations"].extend(new_representations) + def _rename_in_representation(self, new_repre, files_to_convert, output_name, output_extension): """Replace old extension with new one everywhere in representation. From e5ec6c4812aa5d5c59a04d834b631afb0917bd2e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 15 Feb 2023 16:24:53 +0100 Subject: [PATCH 116/227] OP-4642 - refactored query of representations line 73 returns if no representations. --- openpype/plugins/publish/extract_color_transcode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 8c4ef59de9..de36ea7d5f 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -83,7 +83,7 @@ class ExtractOIIOTranscode(publish.Extractor): return new_representations = [] - repres = instance.data.get("representations") or [] + repres = instance.data["representations"] for idx, repre in enumerate(list(repres)): self.log.debug("repre ({}): `{}`".format(idx + 1, repre["name"])) if not self._repre_is_valid(repre): From 6235faab82af4ac7d3a50315b4c1dc9223a73c0a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 10:44:10 +0100 Subject: [PATCH 117/227] OP-4643 - fixed subset filtering Co-authored-by: Toke Jepsen --- openpype/plugins/publish/extract_color_transcode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index de36ea7d5f..71124b527a 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -273,7 +273,7 @@ class ExtractOIIOTranscode(publish.Extractor): "families": family, "task_names": task_name, "task_types": task_type, - "subset": subset + "subsets": subset } profile = filter_profiles(self.profiles, filtering_criteria, logger=self.log) From b304d63461704f7c2e0709bd5165683a0d890a10 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 12:12:35 +0100 Subject: [PATCH 118/227] OP-4643 - split command line arguments to separate items Reuse existing method from ExtractReview, put it into transcoding.py --- openpype/lib/transcoding.py | 29 +++++++++++++++++++++- openpype/plugins/publish/extract_review.py | 27 +++----------------- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 982cee7a46..4d2f72fc41 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1100,7 +1100,7 @@ def convert_colorspace( raise ValueError("Both screen and display must be set.") if additional_command_args: - oiio_cmd.extend(additional_command_args) + oiio_cmd.extend(split_cmd_args(additional_command_args)) if target_colorspace: oiio_cmd.extend(["--colorconvert", @@ -1114,3 +1114,30 @@ def convert_colorspace( logger.debug("Conversion command: {}".format(" ".join(oiio_cmd))) run_subprocess(oiio_cmd, logger=logger) + + +def split_cmd_args(in_args): + """Makes sure all entered arguments are separated in individual items. + + Split each argument string with " -" to identify if string contains + one or more arguments. + Args: + in_args (list): of arguments ['-n', '-d uint10'] + Returns + (list): ['-n', '-d', 'unint10'] + """ + splitted_args = [] + for arg in in_args: + sub_args = arg.split(" -") + if len(sub_args) == 1: + if arg and arg not in splitted_args: + splitted_args.append(arg) + continue + + for idx, arg in enumerate(sub_args): + if idx != 0: + arg = "-" + arg + + if arg and arg not in splitted_args: + splitted_args.append(arg) + return splitted_args diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index 0f6dacba18..e80141fc4a 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -22,6 +22,7 @@ from openpype.lib.transcoding import ( should_convert_for_ffmpeg, convert_input_paths_for_ffmpeg, get_transcode_temp_directory, + split_cmd_args ) @@ -670,7 +671,7 @@ class ExtractReview(pyblish.api.InstancePlugin): res_filters = self.rescaling_filters(temp_data, output_def, new_repre) ffmpeg_video_filters.extend(res_filters) - ffmpeg_input_args = self.split_ffmpeg_args(ffmpeg_input_args) + ffmpeg_input_args = split_cmd_args(ffmpeg_input_args) lut_filters = self.lut_filters(new_repre, instance, ffmpeg_input_args) ffmpeg_video_filters.extend(lut_filters) @@ -723,28 +724,6 @@ class ExtractReview(pyblish.api.InstancePlugin): ffmpeg_output_args ) - def split_ffmpeg_args(self, in_args): - """Makes sure all entered arguments are separated in individual items. - - Split each argument string with " -" to identify if string contains - one or more arguments. - """ - splitted_args = [] - for arg in in_args: - sub_args = arg.split(" -") - if len(sub_args) == 1: - if arg and arg not in splitted_args: - splitted_args.append(arg) - continue - - for idx, arg in enumerate(sub_args): - if idx != 0: - arg = "-" + arg - - if arg and arg not in splitted_args: - splitted_args.append(arg) - return splitted_args - def ffmpeg_full_args( self, input_args, video_filters, audio_filters, output_args ): @@ -764,7 +743,7 @@ class ExtractReview(pyblish.api.InstancePlugin): Returns: list: Containing all arguments ready to run in subprocess. """ - output_args = self.split_ffmpeg_args(output_args) + output_args = split_cmd_args(output_args) video_args_dentifiers = ["-vf", "-filter:v"] audio_args_dentifiers = ["-af", "-filter:a"] From 5d0dc43494452813d19f7ffa511b841e59b64209 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 13:02:41 +0100 Subject: [PATCH 119/227] OP-4643 - refactor - changed existence check --- openpype/plugins/publish/extract_color_transcode.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 71124b527a..456e40008d 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -161,12 +161,12 @@ class ExtractOIIOTranscode(publish.Extractor): custom_tags = output_def.get("custom_tags") if custom_tags: - if not new_repre.get("custom_tags"): + if new_repre.get("custom_tags") is None: new_repre["custom_tags"] = [] new_repre["custom_tags"].extend(custom_tags) # Add additional tags from output definition to representation - if not new_repre.get("tags"): + if new_repre.get("tags") is None: new_repre["tags"] = [] for tag in output_def["tags"]: if tag not in new_repre["tags"]: From 8651a693f990d52e43c14845e82efc3033b5a054 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 13:11:11 +0100 Subject: [PATCH 120/227] Revert "Fix - added missed scopes for Slack bot" This reverts commit 5e0c4a3ab1432e120b8f0c324f899070f1a5f831. --- openpype/modules/slack/manifest.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/modules/slack/manifest.yml b/openpype/modules/slack/manifest.yml index 233c39fbaf..7a65cc5915 100644 --- a/openpype/modules/slack/manifest.yml +++ b/openpype/modules/slack/manifest.yml @@ -19,8 +19,6 @@ oauth_config: - chat:write.public - files:write - channels:read - - users:read - - usergroups:read settings: org_deploy_enabled: false socket_mode_enabled: false From 68313f9215a54d94f4c18119a56679b7c277f937 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 13:10:11 +0100 Subject: [PATCH 121/227] OP-4643 - changed label MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jakub Ježek --- .../schemas/projects_schema/schemas/schema_global_publish.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 5333d514b5..3e9467af61 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 @@ -294,7 +294,7 @@ "children": [ { "key": "additional_command_args", - "label": "Additional command line arguments", + "label": "Arguments", "type": "list", "object_type": "text" } From 68a0892a1d20a17f2504382a3c16586de5039108 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 15:06:16 +0100 Subject: [PATCH 122/227] OP-4643 - added documentation --- .../assets/global_oiio_transcode.png | Bin 0 -> 29010 bytes .../project_settings/settings_project_global.md | 15 +++++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 website/docs/project_settings/assets/global_oiio_transcode.png diff --git a/website/docs/project_settings/assets/global_oiio_transcode.png b/website/docs/project_settings/assets/global_oiio_transcode.png new file mode 100644 index 0000000000000000000000000000000000000000..99396d5bb3f16d434a92c6079515128488b82489 GIT binary patch literal 29010 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 literal 0 HcmV?d00001 diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index 37fed93e69..52671d2db6 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -45,6 +45,21 @@ The _input pattern_ matching uses REGEX expression syntax (try [regexr.com](http The **colorspace name** value is a raw string input and no validation is run after saving project settings. We recommend to open the specified `config.ocio` file and copy pasting the exact colorspace names. ::: +### Extract OIIO Transcode +There is profile configurable (see lower) plugin which allows to transcode any incoming representation to one or multiple new representations (configured in `Output Definitions`) with different target colorspaces. +Plugin expects instances with filled dictionary `colorspaceData` on a representation. This data contains information about source colorspace and must be collected for transcoding. +`oiiotool` is used for transcoding, eg. `oiiotool` must be present in `vendor/bin/oiio` or environment variable `OPENPYPE_OIIO_PATHS` must be provided for custom oiio installation. + +Notable parameters: +- **`Delete Original Representation`** - keep or remove original representation. If old representation is kept, but there is new transcoded representation with 'Create review' tag, original representation looses its 'review' tag if present. +- **`Extension`** - target extension, could be empty - original extension is used +- **`Colorspace`** - target colorspace - must be available in used color config +- **`Display & View`** - transcoding into colorspace OR into display and viewer space could be used. (It is disjunctive: Colorspace & nothing in Display and View or opposite) +- **`Arguments`** - special additional command line arguments for `oiiotool` + + +Example here describes use case for creation of new color coded review of png image sequence. Original representation's files are kept intact, review is created from transcoded files, but these files are removed in cleanup process. +![global_oiio_transcode](assets/global_oiio_transcode.png) ## Profile filters From 265a08abcdf88d3180fc3a1da362351c28083b9b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 16:41:42 +0100 Subject: [PATCH 123/227] OP-4643 - updates to documentation Co-authored-by: Toke Jepsen --- website/docs/project_settings/settings_project_global.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index 52671d2db6..cc661a21fa 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -46,7 +46,7 @@ The **colorspace name** value is a raw string input and no validation is run aft ::: ### Extract OIIO Transcode -There is profile configurable (see lower) plugin which allows to transcode any incoming representation to one or multiple new representations (configured in `Output Definitions`) with different target colorspaces. +There is profile configurable plugin which allows to transcode any incoming representation to one or multiple new representations (configured in `Output Definitions`) with different target colorspaces. Plugin expects instances with filled dictionary `colorspaceData` on a representation. This data contains information about source colorspace and must be collected for transcoding. `oiiotool` is used for transcoding, eg. `oiiotool` must be present in `vendor/bin/oiio` or environment variable `OPENPYPE_OIIO_PATHS` must be provided for custom oiio installation. From b2d40c1cc39fbc42b0365a3edc9fb638db5c3584 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 16:42:06 +0100 Subject: [PATCH 124/227] OP-4643 - updates to documentation Co-authored-by: Toke Jepsen --- website/docs/project_settings/settings_project_global.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index cc661a21fa..8e557a381c 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -52,7 +52,7 @@ Plugin expects instances with filled dictionary `colorspaceData` on a representa Notable parameters: - **`Delete Original Representation`** - keep or remove original representation. If old representation is kept, but there is new transcoded representation with 'Create review' tag, original representation looses its 'review' tag if present. -- **`Extension`** - target extension, could be empty - original extension is used +- **`Extension`** - target extension. If left empty, original extension is used. - **`Colorspace`** - target colorspace - must be available in used color config - **`Display & View`** - transcoding into colorspace OR into display and viewer space could be used. (It is disjunctive: Colorspace & nothing in Display and View or opposite) - **`Arguments`** - special additional command line arguments for `oiiotool` From 2d601023f7db52033736e770b6ec3879fda04de5 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 16:42:29 +0100 Subject: [PATCH 125/227] OP-4643 - updates to documentation Co-authored-by: Toke Jepsen --- website/docs/project_settings/settings_project_global.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index 8e557a381c..166400cb7f 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -53,7 +53,7 @@ Plugin expects instances with filled dictionary `colorspaceData` on a representa Notable parameters: - **`Delete Original Representation`** - keep or remove original representation. If old representation is kept, but there is new transcoded representation with 'Create review' tag, original representation looses its 'review' tag if present. - **`Extension`** - target extension. If left empty, original extension is used. -- **`Colorspace`** - target colorspace - must be available in used color config +- **`Colorspace`** - target colorspace, which must be available in used color config. - **`Display & View`** - transcoding into colorspace OR into display and viewer space could be used. (It is disjunctive: Colorspace & nothing in Display and View or opposite) - **`Arguments`** - special additional command line arguments for `oiiotool` From 6260ea0a91cce8f1a67707aa55553e6ea6afbcab Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 16:42:48 +0100 Subject: [PATCH 126/227] OP-4643 - updates to documentation Co-authored-by: Toke Jepsen --- website/docs/project_settings/settings_project_global.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index 166400cb7f..908191f122 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -54,7 +54,7 @@ Notable parameters: - **`Delete Original Representation`** - keep or remove original representation. If old representation is kept, but there is new transcoded representation with 'Create review' tag, original representation looses its 'review' tag if present. - **`Extension`** - target extension. If left empty, original extension is used. - **`Colorspace`** - target colorspace, which must be available in used color config. -- **`Display & View`** - transcoding into colorspace OR into display and viewer space could be used. (It is disjunctive: Colorspace & nothing in Display and View or opposite) +- **`Display & View`** - transcoding into colorspace or into display and viewer space could be used. Cannot use both `Colorspace` and `Display & View` at the same time. - **`Arguments`** - special additional command line arguments for `oiiotool` From c1c8ca234f97c6b3f64bd91c52f91969656077e1 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 16:43:06 +0100 Subject: [PATCH 127/227] OP-4643 - updates to documentation Co-authored-by: Toke Jepsen --- website/docs/project_settings/settings_project_global.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index 908191f122..0a73868d2d 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -55,7 +55,7 @@ Notable parameters: - **`Extension`** - target extension. If left empty, original extension is used. - **`Colorspace`** - target colorspace, which must be available in used color config. - **`Display & View`** - transcoding into colorspace or into display and viewer space could be used. Cannot use both `Colorspace` and `Display & View` at the same time. -- **`Arguments`** - special additional command line arguments for `oiiotool` +- **`Arguments`** - special additional command line arguments for `oiiotool`. Example here describes use case for creation of new color coded review of png image sequence. Original representation's files are kept intact, review is created from transcoded files, but these files are removed in cleanup process. From 92768e004993311fae5e74ccc9e552ba8f171a2c Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 17:21:20 +0100 Subject: [PATCH 128/227] OP-4643 - updates to documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jakub Ježek --- website/docs/project_settings/settings_project_global.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index 0a73868d2d..9e2ee187cc 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -46,8 +46,8 @@ The **colorspace name** value is a raw string input and no validation is run aft ::: ### Extract OIIO Transcode -There is profile configurable plugin which allows to transcode any incoming representation to one or multiple new representations (configured in `Output Definitions`) with different target colorspaces. -Plugin expects instances with filled dictionary `colorspaceData` on a representation. This data contains information about source colorspace and must be collected for transcoding. +OIIOTools transcoder plugin with configurable output presets. Any incoming representation with `colorspaceData` is convertable to single or multiple representations with different target colorspaces or display and viewer names found in linked **config.ocio** file. + `oiiotool` is used for transcoding, eg. `oiiotool` must be present in `vendor/bin/oiio` or environment variable `OPENPYPE_OIIO_PATHS` must be provided for custom oiio installation. Notable parameters: From 94ee02879286ef75ce60001f8c94d818a24aea57 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 17:56:21 +0100 Subject: [PATCH 129/227] Revert "OP-4643 - split command line arguments to separate items" This reverts commit deaad39437501f18fc3ba4be8b1fc5f0ee3be65d. --- openpype/lib/transcoding.py | 29 +--------------------- openpype/plugins/publish/extract_review.py | 27 +++++++++++++++++--- 2 files changed, 25 insertions(+), 31 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 4d2f72fc41..982cee7a46 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1100,7 +1100,7 @@ def convert_colorspace( raise ValueError("Both screen and display must be set.") if additional_command_args: - oiio_cmd.extend(split_cmd_args(additional_command_args)) + oiio_cmd.extend(additional_command_args) if target_colorspace: oiio_cmd.extend(["--colorconvert", @@ -1114,30 +1114,3 @@ def convert_colorspace( logger.debug("Conversion command: {}".format(" ".join(oiio_cmd))) run_subprocess(oiio_cmd, logger=logger) - - -def split_cmd_args(in_args): - """Makes sure all entered arguments are separated in individual items. - - Split each argument string with " -" to identify if string contains - one or more arguments. - Args: - in_args (list): of arguments ['-n', '-d uint10'] - Returns - (list): ['-n', '-d', 'unint10'] - """ - splitted_args = [] - for arg in in_args: - sub_args = arg.split(" -") - if len(sub_args) == 1: - if arg and arg not in splitted_args: - splitted_args.append(arg) - continue - - for idx, arg in enumerate(sub_args): - if idx != 0: - arg = "-" + arg - - if arg and arg not in splitted_args: - splitted_args.append(arg) - return splitted_args diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index e80141fc4a..0f6dacba18 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -22,7 +22,6 @@ from openpype.lib.transcoding import ( should_convert_for_ffmpeg, convert_input_paths_for_ffmpeg, get_transcode_temp_directory, - split_cmd_args ) @@ -671,7 +670,7 @@ class ExtractReview(pyblish.api.InstancePlugin): res_filters = self.rescaling_filters(temp_data, output_def, new_repre) ffmpeg_video_filters.extend(res_filters) - ffmpeg_input_args = split_cmd_args(ffmpeg_input_args) + ffmpeg_input_args = self.split_ffmpeg_args(ffmpeg_input_args) lut_filters = self.lut_filters(new_repre, instance, ffmpeg_input_args) ffmpeg_video_filters.extend(lut_filters) @@ -724,6 +723,28 @@ class ExtractReview(pyblish.api.InstancePlugin): ffmpeg_output_args ) + def split_ffmpeg_args(self, in_args): + """Makes sure all entered arguments are separated in individual items. + + Split each argument string with " -" to identify if string contains + one or more arguments. + """ + splitted_args = [] + for arg in in_args: + sub_args = arg.split(" -") + if len(sub_args) == 1: + if arg and arg not in splitted_args: + splitted_args.append(arg) + continue + + for idx, arg in enumerate(sub_args): + if idx != 0: + arg = "-" + arg + + if arg and arg not in splitted_args: + splitted_args.append(arg) + return splitted_args + def ffmpeg_full_args( self, input_args, video_filters, audio_filters, output_args ): @@ -743,7 +764,7 @@ class ExtractReview(pyblish.api.InstancePlugin): Returns: list: Containing all arguments ready to run in subprocess. """ - output_args = split_cmd_args(output_args) + output_args = self.split_ffmpeg_args(output_args) video_args_dentifiers = ["-vf", "-filter:v"] audio_args_dentifiers = ["-af", "-filter:a"] From 840f6811345241dd4e058d2d5940847c5b31c69f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 18:02:17 +0100 Subject: [PATCH 130/227] OP-4643 - different splitting for oiio It seems that logic in ExtractReview does different thing. --- openpype/lib/transcoding.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 982cee7a46..376297ff32 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1100,7 +1100,7 @@ def convert_colorspace( raise ValueError("Both screen and display must be set.") if additional_command_args: - oiio_cmd.extend(additional_command_args) + oiio_cmd.extend(split_cmd_args(additional_command_args)) if target_colorspace: oiio_cmd.extend(["--colorconvert", @@ -1114,3 +1114,21 @@ def convert_colorspace( logger.debug("Conversion command: {}".format(" ".join(oiio_cmd))) run_subprocess(oiio_cmd, logger=logger) + + +def split_cmd_args(in_args): + """Makes sure all entered arguments are separated in individual items. + + Split each argument string with " -" to identify if string contains + one or more arguments. + Args: + in_args (list): of arguments ['-n', '-d uint10'] + Returns + (list): ['-n', '-d', 'unint10'] + """ + splitted_args = [] + for arg in in_args: + if not arg.strip(): + continue + splitted_args.extend(arg.split(" ")) + return splitted_args From 5a7e84ab90042c3565e5ba13f11cf5674f02e4f8 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 18:14:57 +0100 Subject: [PATCH 131/227] OP-4643 - allow colorspace to be empty and collected from DCC --- openpype/plugins/publish/extract_color_transcode.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 456e40008d..82b92ec93e 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -118,7 +118,8 @@ class ExtractOIIOTranscode(publish.Extractor): output_name, output_extension) - target_colorspace = output_def["colorspace"] + target_colorspace = (output_def["colorspace"] or + colorspace_data.get("colorspace")) view = output_def["view"] or colorspace_data.get("view") display = (output_def["display"] or colorspace_data.get("display")) From 4abac5ec783c29465d4eb6347ffbd87b6315c2df Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 22 Feb 2023 12:22:53 +0100 Subject: [PATCH 132/227] OP-4643 - fix colorspace from DCC representation["colorspaceData"]["colorspace"] is only input colorspace --- openpype/plugins/publish/extract_color_transcode.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 82b92ec93e..456e40008d 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -118,8 +118,7 @@ class ExtractOIIOTranscode(publish.Extractor): output_name, output_extension) - target_colorspace = (output_def["colorspace"] or - colorspace_data.get("colorspace")) + target_colorspace = output_def["colorspace"] view = output_def["view"] or colorspace_data.get("view") display = (output_def["display"] or colorspace_data.get("display")) From 6cb8cbd6fc03b2d63f09bac0b25634cb1ef3f827 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 22 Feb 2023 17:33:20 +0100 Subject: [PATCH 133/227] OP-4643 - added explicit enum for transcoding type As transcoding info (colorspace, display) might be collected from DCC, it must be explicit which should be used. --- openpype/lib/transcoding.py | 2 +- .../plugins/publish/extract_color_transcode.py | 15 +++++++++++---- .../schemas/schema_global_publish.json | 9 +++++++++ 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 376297ff32..c0bda2aa37 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1052,7 +1052,7 @@ def convert_colorspace( output_path, config_path, source_colorspace, - target_colorspace, + target_colorspace=None, view=None, display=None, additional_command_args=None, diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 456e40008d..b0921688e9 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -118,10 +118,17 @@ class ExtractOIIOTranscode(publish.Extractor): output_name, output_extension) - target_colorspace = output_def["colorspace"] - view = output_def["view"] or colorspace_data.get("view") - display = (output_def["display"] or - colorspace_data.get("display")) + transcoding_type = output_def["transcoding_type"] + + target_colorspace = view = display = None + if transcoding_type == "colorspace": + target_colorspace = (output_def["colorspace"] or + colorspace_data.get("colorspace")) + else: + view = output_def["view"] or colorspace_data.get("view") + display = (output_def["display"] or + colorspace_data.get("display")) + # both could be already collected by DCC, # but could be overwritten if view: 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 3e9467af61..76574e8b9b 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 @@ -271,6 +271,15 @@ "label": "Extension", "type": "text" }, + { + "type": "enum", + "key": "transcoding_type", + "label": "Transcoding type", + "enum_items": [ + { "colorspace": "Use Colorspace" }, + { "display": "Use Display&View" } + ] + }, { "key": "colorspace", "label": "Colorspace", From 58ae146027ebe31b9fe2d7fcc6d4323efb93c883 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 22 Feb 2023 17:34:03 +0100 Subject: [PATCH 134/227] OP-4643 - added explicit enum for transcoding type As transcoding info (colorspace, display) might be collected from DCC, it must be explicit which should be used. --- .../assets/global_oiio_transcode.png | Bin 29010 -> 17936 bytes .../settings_project_global.md | 5 +++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/website/docs/project_settings/assets/global_oiio_transcode.png b/website/docs/project_settings/assets/global_oiio_transcode.png index 99396d5bb3f16d434a92c6079515128488b82489..d818ecfe19f93366e5f4664734d68c7b448d7b4f 100644 GIT binary patch literal 17936 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 DbV~re literal 29010 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 diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index 9e2ee187cc..6e78ee5d45 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -53,8 +53,9 @@ OIIOTools transcoder plugin with configurable output presets. Any incoming repre Notable parameters: - **`Delete Original Representation`** - keep or remove original representation. If old representation is kept, but there is new transcoded representation with 'Create review' tag, original representation looses its 'review' tag if present. - **`Extension`** - target extension. If left empty, original extension is used. -- **`Colorspace`** - target colorspace, which must be available in used color config. -- **`Display & View`** - transcoding into colorspace or into display and viewer space could be used. Cannot use both `Colorspace` and `Display & View` at the same time. +- **`Transcoding type`** - transcoding into colorspace or into display and viewer space could be used. Cannot use both at the same time. +- **`Colorspace`** - target colorspace, which must be available in used color config. (If `Transcoding type` is `Use Colorspace` value in configuration is used OR if empty value collected on instance from DCC). +- **`Display & View`** - display and viewer colorspace. (If `Transcoding type` is `Use Display&View` values in configuration is used OR if empty values collected on instance from DCC). - **`Arguments`** - special additional command line arguments for `oiiotool`. From aae1430904962212e3c31345eb317d74cbd2385b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 22 Feb 2023 17:42:07 +0100 Subject: [PATCH 135/227] OP-4643 - added use case for Maya to documentation --- .../assets/global_oiio_transcode2.png | Bin 0 -> 17960 bytes .../project_settings/settings_project_global.md | 3 +++ 2 files changed, 3 insertions(+) create mode 100644 website/docs/project_settings/assets/global_oiio_transcode2.png diff --git a/website/docs/project_settings/assets/global_oiio_transcode2.png b/website/docs/project_settings/assets/global_oiio_transcode2.png new file mode 100644 index 0000000000000000000000000000000000000000..906f780830a96b4bc6f26d98484dd3f9cc3885f2 GIT binary patch literal 17960 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 F{{cSxQDp!C literal 0 HcmV?d00001 diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index 6e78ee5d45..f58d2c2bf2 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -62,6 +62,9 @@ Notable parameters: Example here describes use case for creation of new color coded review of png image sequence. Original representation's files are kept intact, review is created from transcoded files, but these files are removed in cleanup process. ![global_oiio_transcode](assets/global_oiio_transcode.png) +Another use case is to transcode in Maya only `beauty` render layers and use collected `Display` and `View` colorspaces from DCC. +![global_oiio_transcode_in_Maya](assets/global_oiio_transcode.png) + ## Profile filters Many of the settings are using a concept of **Profile filters** From 82e4e3e5b76d195be63bfce7017e3cc2ede23704 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 23 Feb 2023 11:05:26 +0100 Subject: [PATCH 136/227] OP-4643 - updates to documentation Co-authored-by: Roy Nieterau --- website/docs/project_settings/settings_project_global.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index f58d2c2bf2..d904080ad1 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -51,7 +51,7 @@ OIIOTools transcoder plugin with configurable output presets. Any incoming repre `oiiotool` is used for transcoding, eg. `oiiotool` must be present in `vendor/bin/oiio` or environment variable `OPENPYPE_OIIO_PATHS` must be provided for custom oiio installation. Notable parameters: -- **`Delete Original Representation`** - keep or remove original representation. If old representation is kept, but there is new transcoded representation with 'Create review' tag, original representation looses its 'review' tag if present. +- **`Delete Original Representation`** - keep or remove original representation. If old representation is kept, but there is new transcoded representation with 'Create review' tag, original representation loses its 'review' tag if present. - **`Extension`** - target extension. If left empty, original extension is used. - **`Transcoding type`** - transcoding into colorspace or into display and viewer space could be used. Cannot use both at the same time. - **`Colorspace`** - target colorspace, which must be available in used color config. (If `Transcoding type` is `Use Colorspace` value in configuration is used OR if empty value collected on instance from DCC). From 04109103303c436873a1898de53a54735c524f10 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 11:57:51 +0100 Subject: [PATCH 137/227] OP-4643 - added Settings for ExtractColorTranscode --- .../defaults/project_settings/global.json | 4 + .../schemas/schema_global_publish.json | 73 +++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index cedc2d6876..8485bec67b 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -68,6 +68,10 @@ "output": [] } }, + "ExtractColorTranscode": { + "enabled": true, + "profiles": [] + }, "ExtractReview": { "enabled": true, "profiles": [ 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 5388d04bc9..46ae6ba554 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 @@ -197,6 +197,79 @@ } ] }, + { + "type": "dict", + "collapsible": true, + "key": "ExtractColorTranscode", + "label": "ExtractColorTranscode", + "checkbox_key": "enabled", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "list", + "key": "profiles", + "label": "Profiles", + "object_type": { + "type": "dict", + "children": [ + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + }, + { + "key": "hosts", + "label": "Host names", + "type": "hosts-enum", + "multiselection": true + }, + { + "key": "task_types", + "label": "Task types", + "type": "task-types-enum" + }, + { + "key": "task_names", + "label": "Task names", + "type": "list", + "object_type": "text" + }, + { + "key": "subsets", + "label": "Subset names", + "type": "list", + "object_type": "text" + }, + { + "type": "splitter" + }, + { + "key": "ext", + "label": "Output extension", + "type": "text" + }, + { + "key": "output_colorspace", + "label": "Output colorspace", + "type": "text" + }, + { + "key": "custom_tags", + "label": "Custom Tags", + "type": "list", + "object_type": "text" + } + ] + } + } + ] + }, { "type": "dict", "collapsible": true, From 2f1888bbfbd8dbabcd50ed4d48ab2230d810ba53 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 11:58:51 +0100 Subject: [PATCH 138/227] OP-4643 - added ExtractColorTranscode Added method to convert from one colorspace to another to transcoding lib --- openpype/lib/transcoding.py | 53 ++++++++ .../publish/extract_color_transcode.py | 124 ++++++++++++++++++ 2 files changed, 177 insertions(+) create mode 100644 openpype/plugins/publish/extract_color_transcode.py diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 039255d937..2fc662f2a4 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1045,3 +1045,56 @@ def convert_ffprobe_fps_to_float(value): if divisor == 0.0: return 0.0 return dividend / divisor + + +def convert_colorspace_for_input_paths( + input_paths, + output_dir, + source_color_space, + target_color_space, + logger=None +): + """Convert source files from one color space to another. + + Filenames of input files are kept so make sure that output directory + is not the same directory as input files have. + - This way it can handle gaps and can keep input filenames without handling + frame template + + Args: + input_paths (str): Paths that should be converted. It is expected that + contains single file or image sequence of samy type. + output_dir (str): Path to directory where output will be rendered. + Must not be same as input's directory. + source_color_space (str): ocio valid color space of source files + target_color_space (str): ocio valid target color space + logger (logging.Logger): Logger used for logging. + + """ + if logger is None: + logger = logging.getLogger(__name__) + + input_arg = "-i" + oiio_cmd = [ + get_oiio_tools_path(), + + # Don't add any additional attributes + "--nosoftwareattrib", + "--colorconvert", source_color_space, target_color_space + ] + for input_path in input_paths: + # Prepare subprocess arguments + + oiio_cmd.extend([ + input_arg, input_path, + ]) + + # Add last argument - path to output + base_filename = os.path.basename(input_path) + output_path = os.path.join(output_dir, base_filename) + oiio_cmd.extend([ + "-o", output_path + ]) + + logger.debug("Conversion command: {}".format(" ".join(oiio_cmd))) + run_subprocess(oiio_cmd, logger=logger) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py new file mode 100644 index 0000000000..58508ab18f --- /dev/null +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -0,0 +1,124 @@ +import pyblish.api + +from openpype.pipeline import publish +from openpype.lib import ( + + is_oiio_supported, +) + +from openpype.lib.transcoding import ( + convert_colorspace_for_input_paths, + get_transcode_temp_directory, +) + +from openpype.lib.profiles_filtering import filter_profiles + + +class ExtractColorTranscode(publish.Extractor): + """ + Extractor to convert colors from one colorspace to different. + """ + + label = "Transcode color spaces" + order = pyblish.api.ExtractorOrder + 0.01 + + optional = True + + # Configurable by Settings + profiles = None + options = None + + def process(self, instance): + if not self.profiles: + self.log.warning("No profiles present for create burnin") + return + + if "representations" not in instance.data: + self.log.warning("No representations, skipping.") + return + + if not is_oiio_supported(): + self.log.warning("OIIO not supported, no transcoding possible.") + return + + colorspace_data = instance.data.get("colorspaceData") + if not colorspace_data: + # TODO get_colorspace ?? + self.log.warning("Instance has not colorspace data, skipping") + return + source_color_space = colorspace_data["colorspace"] + + host_name = instance.context.data["hostName"] + family = instance.data["family"] + task_data = instance.data["anatomyData"].get("task", {}) + task_name = task_data.get("name") + task_type = task_data.get("type") + subset = instance.data["subset"] + + filtering_criteria = { + "hosts": host_name, + "families": family, + "task_names": task_name, + "task_types": task_type, + "subset": subset + } + profile = filter_profiles(self.profiles, filtering_criteria, + logger=self.log) + + if not profile: + self.log.info(( + "Skipped instance. None of profiles in presets are for" + " Host: \"{}\" | Families: \"{}\" | Task \"{}\"" + " | Task type \"{}\" | Subset \"{}\" " + ).format(host_name, family, task_name, task_type, subset)) + return + + self.log.debug("profile: {}".format(profile)) + + target_colorspace = profile["output_colorspace"] + if not target_colorspace: + raise RuntimeError("Target colorspace must be set") + + repres = instance.data.get("representations") or [] + for idx, repre in enumerate(repres): + self.log.debug("repre ({}): `{}`".format(idx + 1, repre["name"])) + if not self.repre_is_valid(repre): + continue + + new_staging_dir = get_transcode_temp_directory() + repre["stagingDir"] = new_staging_dir + files_to_remove = repre["files"] + if not isinstance(files_to_remove, list): + files_to_remove = [files_to_remove] + instance.context.data["cleanupFullPaths"].extend(files_to_remove) + + convert_colorspace_for_input_paths( + repre["files"], + new_staging_dir, + source_color_space, + target_colorspace, + self.log + ) + + def repre_is_valid(self, repre): + """Validation if representation should be processed. + + Args: + repre (dict): Representation which should be checked. + + Returns: + bool: False if can't be processed else True. + """ + + if "review" not in (repre.get("tags") or []): + self.log.info(( + "Representation \"{}\" don't have \"review\" tag. Skipped." + ).format(repre["name"])) + return False + + if not repre.get("files"): + self.log.warning(( + "Representation \"{}\" have empty files. Skipped." + ).format(repre["name"])) + return False + return True From b932994e15ab43e5df93bcf3e81e71622594c6a2 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 12:05:57 +0100 Subject: [PATCH 139/227] OP-4643 - extractor must run just before ExtractReview Nuke render local is set to 0.01 --- openpype/plugins/publish/extract_color_transcode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 58508ab18f..5163cd4045 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -20,7 +20,7 @@ class ExtractColorTranscode(publish.Extractor): """ label = "Transcode color spaces" - order = pyblish.api.ExtractorOrder + 0.01 + order = pyblish.api.ExtractorOrder + 0.019 optional = True From 48f24ef17d8929e84ac16868ad2d6a733d47b1f1 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 14:03:22 +0100 Subject: [PATCH 140/227] OP-4643 - fix for full file paths --- .../publish/extract_color_transcode.py | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 5163cd4045..6ad7599f2c 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -1,3 +1,4 @@ +import os import pyblish.api from openpype.pipeline import publish @@ -41,13 +42,6 @@ class ExtractColorTranscode(publish.Extractor): self.log.warning("OIIO not supported, no transcoding possible.") return - colorspace_data = instance.data.get("colorspaceData") - if not colorspace_data: - # TODO get_colorspace ?? - self.log.warning("Instance has not colorspace data, skipping") - return - source_color_space = colorspace_data["colorspace"] - host_name = instance.context.data["hostName"] family = instance.data["family"] task_data = instance.data["anatomyData"].get("task", {}) @@ -82,18 +76,32 @@ class ExtractColorTranscode(publish.Extractor): repres = instance.data.get("representations") or [] for idx, repre in enumerate(repres): self.log.debug("repre ({}): `{}`".format(idx + 1, repre["name"])) - if not self.repre_is_valid(repre): + # if not self.repre_is_valid(repre): + # continue + + colorspace_data = repre.get("colorspaceData") + if not colorspace_data: + # TODO get_colorspace ?? + self.log.warning("Repre has not colorspace data, skipping") + continue + source_color_space = colorspace_data["colorspace"] + config_path = colorspace_data.get("configData", {}).get("path") + if not os.path.exists(config_path): + self.log.warning("Config file doesn't exist, skipping") continue new_staging_dir = get_transcode_temp_directory() + original_staging_dir = repre["stagingDir"] repre["stagingDir"] = new_staging_dir - files_to_remove = repre["files"] - if not isinstance(files_to_remove, list): - files_to_remove = [files_to_remove] - instance.context.data["cleanupFullPaths"].extend(files_to_remove) + files_to_convert = repre["files"] + if not isinstance(files_to_convert, list): + files_to_convert = [files_to_convert] + files_to_convert = [os.path.join(original_staging_dir, path) + for path in files_to_convert] + instance.context.data["cleanupFullPaths"].extend(files_to_convert) convert_colorspace_for_input_paths( - repre["files"], + files_to_convert, new_staging_dir, source_color_space, target_colorspace, From ec299f0d3ca379f73e6f06949506148e78ca5fa1 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 14:04:06 +0100 Subject: [PATCH 141/227] OP-4643 - pass path for ocio config --- openpype/lib/transcoding.py | 3 +++ openpype/plugins/publish/extract_color_transcode.py | 1 + 2 files changed, 4 insertions(+) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 2fc662f2a4..ab86e44304 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1050,6 +1050,7 @@ def convert_ffprobe_fps_to_float(value): def convert_colorspace_for_input_paths( input_paths, output_dir, + config_path, source_color_space, target_color_space, logger=None @@ -1066,6 +1067,7 @@ def convert_colorspace_for_input_paths( contains single file or image sequence of samy type. output_dir (str): Path to directory where output will be rendered. Must not be same as input's directory. + config_path (str): path to OCIO config file source_color_space (str): ocio valid color space of source files target_color_space (str): ocio valid target color space logger (logging.Logger): Logger used for logging. @@ -1080,6 +1082,7 @@ def convert_colorspace_for_input_paths( # Don't add any additional attributes "--nosoftwareattrib", + "--colorconfig", config_path, "--colorconvert", source_color_space, target_color_space ] for input_path in input_paths: diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 6ad7599f2c..fdb13a47e8 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -103,6 +103,7 @@ class ExtractColorTranscode(publish.Extractor): convert_colorspace_for_input_paths( files_to_convert, new_staging_dir, + config_path, source_color_space, target_colorspace, self.log From 2bc8377dbcf856012489a49051da9952ab75546b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 14:15:33 +0100 Subject: [PATCH 142/227] OP-4643 - add custom_tags --- openpype/plugins/publish/extract_color_transcode.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index fdb13a47e8..ab932b2476 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -72,6 +72,7 @@ class ExtractColorTranscode(publish.Extractor): target_colorspace = profile["output_colorspace"] if not target_colorspace: raise RuntimeError("Target colorspace must be set") + custom_tags = profile["custom_tags"] repres = instance.data.get("representations") or [] for idx, repre in enumerate(repres): @@ -109,6 +110,11 @@ class ExtractColorTranscode(publish.Extractor): self.log ) + if custom_tags: + if not repre.get("custom_tags"): + repre["custom_tags"] = [] + repre["custom_tags"].extend(custom_tags) + def repre_is_valid(self, repre): """Validation if representation should be processed. From 4a80b7bb34efdf1dbd7c8f554d42f1caff035385 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 14:18:38 +0100 Subject: [PATCH 143/227] OP-4643 - added docstring --- openpype/plugins/publish/extract_color_transcode.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index ab932b2476..88e2eed90f 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -18,6 +18,17 @@ from openpype.lib.profiles_filtering import filter_profiles class ExtractColorTranscode(publish.Extractor): """ Extractor to convert colors from one colorspace to different. + + Expects "colorspaceData" on representation. This dictionary is collected + previously and denotes that representation files should be converted. + This dict contains source colorspace information, collected by hosts. + + Target colorspace is selected by profiles in the Settings, based on: + - families + - host + - task types + - task names + - subset names """ label = "Transcode color spaces" From f92c74605b793db31bbee90ccbed656571e69c39 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 18:15:44 +0100 Subject: [PATCH 144/227] OP-4643 - updated Settings schema --- .../schemas/schema_global_publish.json | 48 +++++++++++++------ 1 file changed, 34 insertions(+), 14 deletions(-) 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 46ae6ba554..c2c911d7d6 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 @@ -246,24 +246,44 @@ "type": "list", "object_type": "text" }, + { + "type": "boolean", + "key": "delete_original", + "label": "Delete Original Representation" + }, { "type": "splitter" }, { - "key": "ext", - "label": "Output extension", - "type": "text" - }, - { - "key": "output_colorspace", - "label": "Output colorspace", - "type": "text" - }, - { - "key": "custom_tags", - "label": "Custom Tags", - "type": "list", - "object_type": "text" + "key": "outputs", + "label": "Output Definitions", + "type": "dict-modifiable", + "highlight_content": true, + "object_type": { + "type": "dict", + "children": [ + { + "key": "output_extension", + "label": "Output extension", + "type": "text" + }, + { + "key": "output_colorspace", + "label": "Output colorspace", + "type": "text" + }, + { + "type": "schema", + "name": "schema_representation_tags" + }, + { + "key": "custom_tags", + "label": "Custom Tags", + "type": "list", + "object_type": "text" + } + ] + } } ] } From 2f79021aca117bf3ebea7a2bd1103a6c7e958525 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 18:17:25 +0100 Subject: [PATCH 145/227] OP-4643 - skip video files Only frames currently supported. --- .../plugins/publish/extract_color_transcode.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 88e2eed90f..a0714c9a33 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -36,6 +36,9 @@ class ExtractColorTranscode(publish.Extractor): optional = True + # Supported extensions + supported_exts = ["exr", "jpg", "jpeg", "png", "dpx"] + # Configurable by Settings profiles = None options = None @@ -88,13 +91,7 @@ class ExtractColorTranscode(publish.Extractor): repres = instance.data.get("representations") or [] for idx, repre in enumerate(repres): self.log.debug("repre ({}): `{}`".format(idx + 1, repre["name"])) - # if not self.repre_is_valid(repre): - # continue - - colorspace_data = repre.get("colorspaceData") - if not colorspace_data: - # TODO get_colorspace ?? - self.log.warning("Repre has not colorspace data, skipping") + if not self._repre_is_valid(repre): continue source_color_space = colorspace_data["colorspace"] config_path = colorspace_data.get("configData", {}).get("path") @@ -136,9 +133,9 @@ class ExtractColorTranscode(publish.Extractor): bool: False if can't be processed else True. """ - if "review" not in (repre.get("tags") or []): - self.log.info(( - "Representation \"{}\" don't have \"review\" tag. Skipped." + if repre.get("ext") not in self.supported_exts: + self.log.warning(( + "Representation \"{}\" of unsupported extension. Skipped." ).format(repre["name"])) return False From e63dc4075629efe7499a510742c61a0f5e9c46fd Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 18:19:08 +0100 Subject: [PATCH 146/227] OP-4643 - refactored profile, delete of original Implemented multiple outputs from single input representation --- .../publish/extract_color_transcode.py | 156 ++++++++++++------ 1 file changed, 109 insertions(+), 47 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index a0714c9a33..b0c851d5f4 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -1,4 +1,6 @@ import os +import copy + import pyblish.api from openpype.pipeline import publish @@ -56,13 +58,94 @@ class ExtractColorTranscode(publish.Extractor): self.log.warning("OIIO not supported, no transcoding possible.") return + profile = self._get_profile(instance) + if not profile: + return + + repres = instance.data.get("representations") or [] + for idx, repre in enumerate(list(repres)): + self.log.debug("repre ({}): `{}`".format(idx + 1, repre["name"])) + if not self._repre_is_valid(repre): + continue + + colorspace_data = repre["colorspaceData"] + source_color_space = colorspace_data["colorspace"] + config_path = colorspace_data.get("configData", {}).get("path") + if not os.path.exists(config_path): + self.log.warning("Config file doesn't exist, skipping") + continue + + repre = self._handle_original_repre(repre, profile) + + for _, output_def in profile.get("outputs", {}).items(): + new_repre = copy.deepcopy(repre) + + new_staging_dir = get_transcode_temp_directory() + original_staging_dir = new_repre["stagingDir"] + new_repre["stagingDir"] = new_staging_dir + files_to_convert = new_repre["files"] + if not isinstance(files_to_convert, list): + files_to_convert = [files_to_convert] + + files_to_delete = copy.deepcopy(files_to_convert) + + output_extension = output_def["output_extension"] + files_to_convert = self._rename_output_files(files_to_convert, + output_extension) + + files_to_convert = [os.path.join(original_staging_dir, path) + for path in files_to_convert] + + target_colorspace = output_def["output_colorspace"] + if not target_colorspace: + raise RuntimeError("Target colorspace must be set") + + convert_colorspace_for_input_paths( + files_to_convert, + new_staging_dir, + config_path, + source_color_space, + target_colorspace, + self.log + ) + + instance.context.data["cleanupFullPaths"].extend( + files_to_delete) + + custom_tags = output_def.get("custom_tags") + if custom_tags: + if not new_repre.get("custom_tags"): + new_repre["custom_tags"] = [] + new_repre["custom_tags"].extend(custom_tags) + + # Add additional tags from output definition to representation + for tag in output_def["tags"]: + if tag not in new_repre["tags"]: + new_repre["tags"].append(tag) + + instance.data["representations"].append(new_repre) + + def _rename_output_files(self, files_to_convert, output_extension): + """Change extension of converted files.""" + if output_extension: + output_extension = output_extension.replace('.', '') + renamed_files = [] + for file_name in files_to_convert: + file_name, _ = os.path.splitext(file_name) + new_file_name = '{}.{}'.format(file_name, + output_extension) + renamed_files.append(new_file_name) + files_to_convert = renamed_files + return files_to_convert + + def _get_profile(self, instance): + """Returns profile if and how repre should be color transcoded.""" host_name = instance.context.data["hostName"] family = instance.data["family"] task_data = instance.data["anatomyData"].get("task", {}) task_name = task_data.get("name") task_type = task_data.get("type") subset = instance.data["subset"] - filtering_criteria = { "hosts": host_name, "families": family, @@ -75,55 +158,15 @@ class ExtractColorTranscode(publish.Extractor): if not profile: self.log.info(( - "Skipped instance. None of profiles in presets are for" - " Host: \"{}\" | Families: \"{}\" | Task \"{}\"" - " | Task type \"{}\" | Subset \"{}\" " - ).format(host_name, family, task_name, task_type, subset)) - return + "Skipped instance. None of profiles in presets are for" + " Host: \"{}\" | Families: \"{}\" | Task \"{}\"" + " | Task type \"{}\" | Subset \"{}\" " + ).format(host_name, family, task_name, task_type, subset)) self.log.debug("profile: {}".format(profile)) + return profile - target_colorspace = profile["output_colorspace"] - if not target_colorspace: - raise RuntimeError("Target colorspace must be set") - custom_tags = profile["custom_tags"] - - repres = instance.data.get("representations") or [] - for idx, repre in enumerate(repres): - self.log.debug("repre ({}): `{}`".format(idx + 1, repre["name"])) - if not self._repre_is_valid(repre): - continue - source_color_space = colorspace_data["colorspace"] - config_path = colorspace_data.get("configData", {}).get("path") - if not os.path.exists(config_path): - self.log.warning("Config file doesn't exist, skipping") - continue - - new_staging_dir = get_transcode_temp_directory() - original_staging_dir = repre["stagingDir"] - repre["stagingDir"] = new_staging_dir - files_to_convert = repre["files"] - if not isinstance(files_to_convert, list): - files_to_convert = [files_to_convert] - files_to_convert = [os.path.join(original_staging_dir, path) - for path in files_to_convert] - instance.context.data["cleanupFullPaths"].extend(files_to_convert) - - convert_colorspace_for_input_paths( - files_to_convert, - new_staging_dir, - config_path, - source_color_space, - target_colorspace, - self.log - ) - - if custom_tags: - if not repre.get("custom_tags"): - repre["custom_tags"] = [] - repre["custom_tags"].extend(custom_tags) - - def repre_is_valid(self, repre): + def _repre_is_valid(self, repre): """Validation if representation should be processed. Args: @@ -144,4 +187,23 @@ class ExtractColorTranscode(publish.Extractor): "Representation \"{}\" have empty files. Skipped." ).format(repre["name"])) return False + + if not repre.get("colorspaceData"): + self.log.warning("Repre has not colorspace data, skipping") + return False + return True + + def _handle_original_repre(self, repre, profile): + delete_original = profile["delete_original"] + + if delete_original: + if not repre.get("tags"): + repre["tags"] = [] + + if "review" in repre["tags"]: + repre["tags"].remove("review") + if "delete" not in repre["tags"]: + repre["tags"].append("delete") + + return repre From 7341c618274ccdc8f469473ff05698499f6d5b72 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 18:23:01 +0100 Subject: [PATCH 147/227] OP-4643 - switched logging levels Do not use warning unnecessary. --- openpype/plugins/publish/extract_color_transcode.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index b0c851d5f4..4d38514b8b 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -47,11 +47,11 @@ class ExtractColorTranscode(publish.Extractor): def process(self, instance): if not self.profiles: - self.log.warning("No profiles present for create burnin") + self.log.debug("No profiles present for color transcode") return if "representations" not in instance.data: - self.log.warning("No representations, skipping.") + self.log.debug("No representations, skipping.") return if not is_oiio_supported(): @@ -177,19 +177,19 @@ class ExtractColorTranscode(publish.Extractor): """ if repre.get("ext") not in self.supported_exts: - self.log.warning(( + self.log.debug(( "Representation \"{}\" of unsupported extension. Skipped." ).format(repre["name"])) return False if not repre.get("files"): - self.log.warning(( + self.log.debug(( "Representation \"{}\" have empty files. Skipped." ).format(repre["name"])) return False if not repre.get("colorspaceData"): - self.log.warning("Repre has not colorspace data, skipping") + self.log.debug("Repre has no colorspace data. Skipped.") return False return True From d5cc450e9cd18f7360d65140e48ea5eab810c8e2 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 18:46:14 +0100 Subject: [PATCH 148/227] OP-4643 - propagate new extension to representation --- .../publish/extract_color_transcode.py | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 4d38514b8b..62cf8f0dee 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -90,8 +90,13 @@ class ExtractColorTranscode(publish.Extractor): files_to_delete = copy.deepcopy(files_to_convert) output_extension = output_def["output_extension"] - files_to_convert = self._rename_output_files(files_to_convert, - output_extension) + output_extension = output_extension.replace('.', '') + if output_extension: + new_repre["name"] = output_extension + new_repre["ext"] = output_extension + + files_to_convert = self._rename_output_files( + files_to_convert, output_extension) files_to_convert = [os.path.join(original_staging_dir, path) for path in files_to_convert] @@ -127,15 +132,13 @@ class ExtractColorTranscode(publish.Extractor): def _rename_output_files(self, files_to_convert, output_extension): """Change extension of converted files.""" - if output_extension: - output_extension = output_extension.replace('.', '') - renamed_files = [] - for file_name in files_to_convert: - file_name, _ = os.path.splitext(file_name) - new_file_name = '{}.{}'.format(file_name, - output_extension) - renamed_files.append(new_file_name) - files_to_convert = renamed_files + renamed_files = [] + for file_name in files_to_convert: + file_name, _ = os.path.splitext(file_name) + new_file_name = '{}.{}'.format(file_name, + output_extension) + renamed_files.append(new_file_name) + files_to_convert = renamed_files return files_to_convert def _get_profile(self, instance): From 65b454c42c77fb75afc06dd34cf36c40ea52a751 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 18:46:35 +0100 Subject: [PATCH 149/227] OP-4643 - added label to Settings --- .../projects_schema/schemas/schema_global_publish.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 c2c911d7d6..7155510fef 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 @@ -201,10 +201,14 @@ "type": "dict", "collapsible": true, "key": "ExtractColorTranscode", - "label": "ExtractColorTranscode", + "label": "ExtractColorTranscode (ImageIO)", "checkbox_key": "enabled", "is_group": true, "children": [ + { + "type": "label", + "label": "Configure output format(s) and color spaces for matching representations. Empty 'Output extension' denotes keeping source extension." + }, { "type": "boolean", "key": "enabled", From 9fc4070e066499a89898c450263119dbf8e2f99a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 16 Jan 2023 18:22:08 +0100 Subject: [PATCH 150/227] OP-4643 - refactored according to review Function turned into single filepath input. --- openpype/lib/transcoding.py | 43 ++++++----- .../publish/extract_color_transcode.py | 72 ++++++++++--------- 2 files changed, 57 insertions(+), 58 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index ab86e44304..e1bd22d109 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1047,12 +1047,12 @@ def convert_ffprobe_fps_to_float(value): return dividend / divisor -def convert_colorspace_for_input_paths( - input_paths, - output_dir, +def convert_colorspace( + input_path, + out_filepath, config_path, - source_color_space, - target_color_space, + source_colorspace, + target_colorspace, logger=None ): """Convert source files from one color space to another. @@ -1063,13 +1063,13 @@ def convert_colorspace_for_input_paths( frame template Args: - input_paths (str): Paths that should be converted. It is expected that + input_path (str): Paths that should be converted. It is expected that contains single file or image sequence of samy type. - output_dir (str): Path to directory where output will be rendered. + out_filepath (str): Path to directory where output will be rendered. Must not be same as input's directory. config_path (str): path to OCIO config file - source_color_space (str): ocio valid color space of source files - target_color_space (str): ocio valid target color space + source_colorspace (str): ocio valid color space of source files + target_colorspace (str): ocio valid target color space logger (logging.Logger): Logger used for logging. """ @@ -1083,21 +1083,18 @@ def convert_colorspace_for_input_paths( # Don't add any additional attributes "--nosoftwareattrib", "--colorconfig", config_path, - "--colorconvert", source_color_space, target_color_space + "--colorconvert", source_colorspace, target_colorspace ] - for input_path in input_paths: - # Prepare subprocess arguments + # Prepare subprocess arguments - oiio_cmd.extend([ - input_arg, input_path, - ]) + oiio_cmd.extend([ + input_arg, input_path, + ]) - # Add last argument - path to output - base_filename = os.path.basename(input_path) - output_path = os.path.join(output_dir, base_filename) - oiio_cmd.extend([ - "-o", output_path - ]) + # Add last argument - path to output + oiio_cmd.extend([ + "-o", out_filepath + ]) - logger.debug("Conversion command: {}".format(" ".join(oiio_cmd))) - run_subprocess(oiio_cmd, logger=logger) + logger.debug("Conversion command: {}".format(" ".join(oiio_cmd))) + run_subprocess(oiio_cmd, logger=logger) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 62cf8f0dee..3a05426432 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -10,7 +10,7 @@ from openpype.lib import ( ) from openpype.lib.transcoding import ( - convert_colorspace_for_input_paths, + convert_colorspace, get_transcode_temp_directory, ) @@ -69,7 +69,7 @@ class ExtractColorTranscode(publish.Extractor): continue colorspace_data = repre["colorspaceData"] - source_color_space = colorspace_data["colorspace"] + source_colorspace = colorspace_data["colorspace"] config_path = colorspace_data.get("configData", {}).get("path") if not os.path.exists(config_path): self.log.warning("Config file doesn't exist, skipping") @@ -80,8 +80,8 @@ class ExtractColorTranscode(publish.Extractor): for _, output_def in profile.get("outputs", {}).items(): new_repre = copy.deepcopy(repre) - new_staging_dir = get_transcode_temp_directory() original_staging_dir = new_repre["stagingDir"] + new_staging_dir = get_transcode_temp_directory() new_repre["stagingDir"] = new_staging_dir files_to_convert = new_repre["files"] if not isinstance(files_to_convert, list): @@ -92,27 +92,28 @@ class ExtractColorTranscode(publish.Extractor): output_extension = output_def["output_extension"] output_extension = output_extension.replace('.', '') if output_extension: - new_repre["name"] = output_extension + if new_repre["name"] == new_repre["ext"]: + new_repre["name"] = output_extension new_repre["ext"] = output_extension - files_to_convert = self._rename_output_files( - files_to_convert, output_extension) - - files_to_convert = [os.path.join(original_staging_dir, path) - for path in files_to_convert] - target_colorspace = output_def["output_colorspace"] if not target_colorspace: raise RuntimeError("Target colorspace must be set") - convert_colorspace_for_input_paths( - files_to_convert, - new_staging_dir, - config_path, - source_color_space, - target_colorspace, - self.log - ) + for file_name in files_to_convert: + input_filepath = os.path.join(original_staging_dir, + file_name) + output_path = self._get_output_file_path(input_filepath, + new_staging_dir, + output_extension) + convert_colorspace( + input_filepath, + output_path, + config_path, + source_colorspace, + target_colorspace, + self.log + ) instance.context.data["cleanupFullPaths"].extend( files_to_delete) @@ -130,16 +131,16 @@ class ExtractColorTranscode(publish.Extractor): instance.data["representations"].append(new_repre) - def _rename_output_files(self, files_to_convert, output_extension): - """Change extension of converted files.""" - renamed_files = [] - for file_name in files_to_convert: - file_name, _ = os.path.splitext(file_name) - new_file_name = '{}.{}'.format(file_name, - output_extension) - renamed_files.append(new_file_name) - files_to_convert = renamed_files - return files_to_convert + def _get_output_file_path(self, input_filepath, output_dir, + output_extension): + """Create output file name path.""" + file_name = os.path.basename(input_filepath) + file_name, input_extension = os.path.splitext(file_name) + if not output_extension: + output_extension = input_extension + new_file_name = '{}.{}'.format(file_name, + output_extension) + return os.path.join(output_dir, new_file_name) def _get_profile(self, instance): """Returns profile if and how repre should be color transcoded.""" @@ -161,10 +162,10 @@ class ExtractColorTranscode(publish.Extractor): if not profile: self.log.info(( - "Skipped instance. None of profiles in presets are for" - " Host: \"{}\" | Families: \"{}\" | Task \"{}\"" - " | Task type \"{}\" | Subset \"{}\" " - ).format(host_name, family, task_name, task_type, subset)) + "Skipped instance. None of profiles in presets are for" + " Host: \"{}\" | Families: \"{}\" | Task \"{}\"" + " | Task type \"{}\" | Subset \"{}\" " + ).format(host_name, family, task_name, task_type, subset)) self.log.debug("profile: {}".format(profile)) return profile @@ -181,18 +182,19 @@ class ExtractColorTranscode(publish.Extractor): if repre.get("ext") not in self.supported_exts: self.log.debug(( - "Representation \"{}\" of unsupported extension. Skipped." + "Representation '{}' of unsupported extension. Skipped." ).format(repre["name"])) return False if not repre.get("files"): self.log.debug(( - "Representation \"{}\" have empty files. Skipped." + "Representation '{}' have empty files. Skipped." ).format(repre["name"])) return False if not repre.get("colorspaceData"): - self.log.debug("Repre has no colorspace data. Skipped.") + self.log.debug("Representation '{}' has no colorspace data. " + "Skipped.") return False return True From 5eb771333b9d0b0a7b2abf5a2487284f5043f478 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 26 Jan 2023 12:53:02 +0100 Subject: [PATCH 151/227] OP-4643 - updated schema Co-authored-by: Toke Jepsen --- .../projects_schema/schemas/schema_global_publish.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 7155510fef..80c18ce118 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 @@ -267,8 +267,8 @@ "type": "dict", "children": [ { - "key": "output_extension", - "label": "Output extension", + "key": "extension", + "label": "Extension", "type": "text" }, { From 49a06f873bc1a77e1dde35a9e6c7f1603508e6e4 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 26 Jan 2023 12:54:46 +0100 Subject: [PATCH 152/227] OP-4643 - updated plugin name in schema MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jakub Ježek --- .../projects_schema/schemas/schema_global_publish.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 80c18ce118..357cbfb287 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 @@ -200,8 +200,8 @@ { "type": "dict", "collapsible": true, - "key": "ExtractColorTranscode", - "label": "ExtractColorTranscode (ImageIO)", + "key": "ExtractOIIOTranscode", + "label": "Extract OIIO Transcode", "checkbox_key": "enabled", "is_group": true, "children": [ From 2ec5221b282b4222f5ac179f63f5650050f3b331 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 26 Jan 2023 12:55:57 +0100 Subject: [PATCH 153/227] OP-4643 - updated key in schema Co-authored-by: Toke Jepsen --- .../projects_schema/schemas/schema_global_publish.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 357cbfb287..0281b0ded6 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 @@ -272,8 +272,8 @@ "type": "text" }, { - "key": "output_colorspace", - "label": "Output colorspace", + "key": "colorspace", + "label": "Colorspace", "type": "text" }, { From 83d21d9d7793350d6374c2c240f4f70e688c2e88 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 26 Jan 2023 12:57:03 +0100 Subject: [PATCH 154/227] OP-4643 - changed oiio_cmd creation Co-authored-by: Toke Jepsen --- openpype/lib/transcoding.py | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index e1bd22d109..f22628dd28 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1076,25 +1076,15 @@ def convert_colorspace( if logger is None: logger = logging.getLogger(__name__) - input_arg = "-i" oiio_cmd = [ get_oiio_tools_path(), - + input_path, # Don't add any additional attributes "--nosoftwareattrib", "--colorconfig", config_path, - "--colorconvert", source_colorspace, target_colorspace - ] - # Prepare subprocess arguments - - oiio_cmd.extend([ - input_arg, input_path, - ]) - - # Add last argument - path to output - oiio_cmd.extend([ + "--colorconvert", source_colorspace, target_colorspace, "-o", out_filepath - ]) + ] logger.debug("Conversion command: {}".format(" ".join(oiio_cmd))) run_subprocess(oiio_cmd, logger=logger) From 40f8cd4a93bfe76dcb91ee683c78033417f40525 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 26 Jan 2023 13:44:45 +0100 Subject: [PATCH 155/227] OP-4643 - updated new keys into settings --- .../settings/defaults/project_settings/global.json | 2 +- .../projects_schema/schemas/schema_global_publish.json | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 8485bec67b..a5e2d25a88 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -68,7 +68,7 @@ "output": [] } }, - "ExtractColorTranscode": { + "ExtractOIIOTranscode": { "enabled": true, "profiles": [] }, 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 0281b0ded6..74b81b13af 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 @@ -276,6 +276,16 @@ "label": "Colorspace", "type": "text" }, + { + "key": "display", + "label": "Display", + "type": "text" + }, + { + "key": "view", + "label": "View", + "type": "text" + }, { "type": "schema", "name": "schema_representation_tags" From e64389f11be2d072e9d8d59dce5334eebb727776 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 26 Jan 2023 13:45:42 +0100 Subject: [PATCH 156/227] OP-4643 - renanmed plugin, added new keys into outputs --- openpype/plugins/publish/extract_color_transcode.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 3a05426432..cc63b35988 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -17,7 +17,7 @@ from openpype.lib.transcoding import ( from openpype.lib.profiles_filtering import filter_profiles -class ExtractColorTranscode(publish.Extractor): +class ExtractOIIOTranscode(publish.Extractor): """ Extractor to convert colors from one colorspace to different. @@ -89,14 +89,14 @@ class ExtractColorTranscode(publish.Extractor): files_to_delete = copy.deepcopy(files_to_convert) - output_extension = output_def["output_extension"] + output_extension = output_def["extension"] output_extension = output_extension.replace('.', '') if output_extension: if new_repre["name"] == new_repre["ext"]: new_repre["name"] = output_extension new_repre["ext"] = output_extension - target_colorspace = output_def["output_colorspace"] + target_colorspace = output_def["colorspace"] if not target_colorspace: raise RuntimeError("Target colorspace must be set") From 99d687c9a1366984116b5ef43d65cab465886b12 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 26 Jan 2023 18:03:13 +0100 Subject: [PATCH 157/227] OP-4643 - fixed config path key --- openpype/plugins/publish/extract_color_transcode.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index cc63b35988..245faeb306 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -70,8 +70,8 @@ class ExtractOIIOTranscode(publish.Extractor): colorspace_data = repre["colorspaceData"] source_colorspace = colorspace_data["colorspace"] - config_path = colorspace_data.get("configData", {}).get("path") - if not os.path.exists(config_path): + config_path = colorspace_data.get("config", {}).get("path") + if not config_path or not os.path.exists(config_path): self.log.warning("Config file doesn't exist, skipping") continue From 2a7fd01aada28eebf603e6bd8cc6d6b1a8a560a6 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 26 Jan 2023 18:03:42 +0100 Subject: [PATCH 158/227] OP-4643 - fixed renaming files --- openpype/plugins/publish/extract_color_transcode.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 245faeb306..c079dcf70e 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -96,6 +96,14 @@ class ExtractOIIOTranscode(publish.Extractor): new_repre["name"] = output_extension new_repre["ext"] = output_extension + renamed_files = [] + _, orig_ext = os.path.splitext(files_to_convert[0]) + for file_name in files_to_convert: + file_name = file_name.replace(orig_ext, + "."+output_extension) + renamed_files.append(file_name) + new_repre["files"] = renamed_files + target_colorspace = output_def["colorspace"] if not target_colorspace: raise RuntimeError("Target colorspace must be set") From 34d519524e9998c5436baf5b53f8740bf2eceff3 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 26 Jan 2023 18:04:44 +0100 Subject: [PATCH 159/227] OP-4643 - updated to calculate sequence format --- .../publish/extract_color_transcode.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index c079dcf70e..09c86909cb 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -1,5 +1,6 @@ import os import copy +import clique import pyblish.api @@ -108,6 +109,8 @@ class ExtractOIIOTranscode(publish.Extractor): if not target_colorspace: raise RuntimeError("Target colorspace must be set") + files_to_convert = self._translate_to_sequence( + files_to_convert) for file_name in files_to_convert: input_filepath = os.path.join(original_staging_dir, file_name) @@ -139,6 +142,40 @@ class ExtractOIIOTranscode(publish.Extractor): instance.data["representations"].append(new_repre) + def _translate_to_sequence(self, files_to_convert): + """Returns original list of files or single sequence format filename. + + Uses clique to find frame sequence, in this case it merges all frames + into sequence format (%0X) and returns it. + If sequence not found, it returns original list + + Args: + files_to_convert (list): list of file names + Returns: + (list) of [file.%04.exr] or [fileA.exr, fileB.exr] + """ + pattern = [clique.PATTERNS["frames"]] + collections, remainder = clique.assemble( + files_to_convert, patterns=pattern, + assume_padded_when_ambiguous=True) + + if collections: + if len(collections) > 1: + raise ValueError( + "Too many collections {}".format(collections)) + + collection = collections[0] + padding = collection.padding + padding_str = "%0{}".format(padding) + frames = list(collection.indexes) + frame_str = "{}-{}#".format(frames[0], frames[-1]) + file_name = "{}{}{}".format(collection.head, frame_str, + collection.tail) + + files_to_convert = [file_name] + + return files_to_convert + def _get_output_file_path(self, input_filepath, output_dir, output_extension): """Create output file name path.""" From be176bbeb2feb40751be9c208fb4b0dd236f66ae Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 26 Jan 2023 18:54:02 +0100 Subject: [PATCH 160/227] OP-4643 - implemented display and viewer color space --- openpype/lib/transcoding.py | 23 +++++++++++++++++-- .../publish/extract_color_transcode.py | 13 +++++++++-- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index f22628dd28..cc9cd4e1eb 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1053,6 +1053,8 @@ def convert_colorspace( config_path, source_colorspace, target_colorspace, + view, + display, logger=None ): """Convert source files from one color space to another. @@ -1070,8 +1072,11 @@ def convert_colorspace( config_path (str): path to OCIO config file source_colorspace (str): ocio valid color space of source files target_colorspace (str): ocio valid target color space + view (str): name for viewer space (ocio valid) + display (str): name for display-referred reference space (ocio valid) logger (logging.Logger): Logger used for logging. - + Raises: + ValueError: if misconfigured """ if logger is None: logger = logging.getLogger(__name__) @@ -1082,9 +1087,23 @@ def convert_colorspace( # Don't add any additional attributes "--nosoftwareattrib", "--colorconfig", config_path, - "--colorconvert", source_colorspace, target_colorspace, "-o", out_filepath ] + if all([target_colorspace, view, display]): + raise ValueError("Colorspace and both screen and display" + " cannot be set together." + "Choose colorspace or screen and display") + if not target_colorspace and not all([view, display]): + raise ValueError("Both screen and display must be set.") + + if target_colorspace: + oiio_cmd.extend(["--colorconvert", + source_colorspace, + target_colorspace]) + if view and display: + oiio_cmd.extend(["--iscolorspace", source_colorspace]) + oiio_cmd.extend(["--ociodisplay", display, view]) + logger.debug("Conversion command: {}".format(" ".join(oiio_cmd))) run_subprocess(oiio_cmd, logger=logger) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 09c86909cb..cd8421c0cd 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -106,8 +106,15 @@ class ExtractOIIOTranscode(publish.Extractor): new_repre["files"] = renamed_files target_colorspace = output_def["colorspace"] - if not target_colorspace: - raise RuntimeError("Target colorspace must be set") + view = output_def["view"] or colorspace_data.get("view") + display = (output_def["display"] or + colorspace_data.get("display")) + # both could be already collected by DCC, + # but could be overwritten + if view: + new_repre["colorspaceData"]["view"] = view + if display: + new_repre["colorspaceData"]["view"] = display files_to_convert = self._translate_to_sequence( files_to_convert) @@ -123,6 +130,8 @@ class ExtractOIIOTranscode(publish.Extractor): config_path, source_colorspace, target_colorspace, + view, + display, self.log ) From 3dba4f3eb14c545038bb340614023f974c2c405a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 26 Jan 2023 19:03:10 +0100 Subject: [PATCH 161/227] OP-4643 - fix wrong order of deletion of representation --- openpype/plugins/publish/extract_color_transcode.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index cd8421c0cd..9cca5cc969 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -69,6 +69,8 @@ class ExtractOIIOTranscode(publish.Extractor): if not self._repre_is_valid(repre): continue + added_representations = False + colorspace_data = repre["colorspaceData"] source_colorspace = colorspace_data["colorspace"] config_path = colorspace_data.get("config", {}).get("path") @@ -76,8 +78,6 @@ class ExtractOIIOTranscode(publish.Extractor): self.log.warning("Config file doesn't exist, skipping") continue - repre = self._handle_original_repre(repre, profile) - for _, output_def in profile.get("outputs", {}).items(): new_repre = copy.deepcopy(repre) @@ -150,6 +150,10 @@ class ExtractOIIOTranscode(publish.Extractor): new_repre["tags"].append(tag) instance.data["representations"].append(new_repre) + added_representations = True + + if added_representations: + self._mark_original_repre_for_deletion(repre, profile) def _translate_to_sequence(self, files_to_convert): """Returns original list of files or single sequence format filename. @@ -253,7 +257,8 @@ class ExtractOIIOTranscode(publish.Extractor): return True - def _handle_original_repre(self, repre, profile): + def _mark_original_repre_for_deletion(self, repre, profile): + """If new transcoded representation created, delete old.""" delete_original = profile["delete_original"] if delete_original: @@ -264,5 +269,3 @@ class ExtractOIIOTranscode(publish.Extractor): repre["tags"].remove("review") if "delete" not in repre["tags"]: repre["tags"].append("delete") - - return repre From 7e9d707226dd1956e457e1d29a0d2df58334e26d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 27 Jan 2023 11:26:27 +0100 Subject: [PATCH 162/227] OP-4643 - updated docstring, standardized arguments --- openpype/lib/transcoding.py | 19 +++++++---------- .../publish/extract_color_transcode.py | 21 +++++++++---------- 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index cc9cd4e1eb..0f6d35affe 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1049,7 +1049,7 @@ def convert_ffprobe_fps_to_float(value): def convert_colorspace( input_path, - out_filepath, + output_path, config_path, source_colorspace, target_colorspace, @@ -1057,18 +1057,13 @@ def convert_colorspace( display, logger=None ): - """Convert source files from one color space to another. - - Filenames of input files are kept so make sure that output directory - is not the same directory as input files have. - - This way it can handle gaps and can keep input filenames without handling - frame template + """Convert source file from one color space to another. Args: - input_path (str): Paths that should be converted. It is expected that - contains single file or image sequence of samy type. - out_filepath (str): Path to directory where output will be rendered. - Must not be same as input's directory. + input_path (str): Path that should be converted. It is expected that + contains single file or image sequence of same type + (sequence in format 'file.FRAMESTART-FRAMEEND#.exr', see oiio docs) + output_path (str): Path to output filename. config_path (str): path to OCIO config file source_colorspace (str): ocio valid color space of source files target_colorspace (str): ocio valid target color space @@ -1087,7 +1082,7 @@ def convert_colorspace( # Don't add any additional attributes "--nosoftwareattrib", "--colorconfig", config_path, - "-o", out_filepath + "-o", output_path ] if all([target_colorspace, view, display]): diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 9cca5cc969..c4cef15ea6 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -119,13 +119,13 @@ class ExtractOIIOTranscode(publish.Extractor): files_to_convert = self._translate_to_sequence( files_to_convert) for file_name in files_to_convert: - input_filepath = os.path.join(original_staging_dir, - file_name) - output_path = self._get_output_file_path(input_filepath, + input_path = os.path.join(original_staging_dir, + file_name) + output_path = self._get_output_file_path(input_path, new_staging_dir, output_extension) convert_colorspace( - input_filepath, + input_path, output_path, config_path, source_colorspace, @@ -156,16 +156,17 @@ class ExtractOIIOTranscode(publish.Extractor): self._mark_original_repre_for_deletion(repre, profile) def _translate_to_sequence(self, files_to_convert): - """Returns original list of files or single sequence format filename. + """Returns original list or list with filename formatted in single + sequence format. Uses clique to find frame sequence, in this case it merges all frames - into sequence format (%0X) and returns it. + into sequence format (FRAMESTART-FRAMEEND#) and returns it. If sequence not found, it returns original list Args: files_to_convert (list): list of file names Returns: - (list) of [file.%04.exr] or [fileA.exr, fileB.exr] + (list) of [file.1001-1010#.exr] or [fileA.exr, fileB.exr] """ pattern = [clique.PATTERNS["frames"]] collections, remainder = clique.assemble( @@ -178,8 +179,6 @@ class ExtractOIIOTranscode(publish.Extractor): "Too many collections {}".format(collections)) collection = collections[0] - padding = collection.padding - padding_str = "%0{}".format(padding) frames = list(collection.indexes) frame_str = "{}-{}#".format(frames[0], frames[-1]) file_name = "{}{}{}".format(collection.head, frame_str, @@ -189,10 +188,10 @@ class ExtractOIIOTranscode(publish.Extractor): return files_to_convert - def _get_output_file_path(self, input_filepath, output_dir, + def _get_output_file_path(self, input_path, output_dir, output_extension): """Create output file name path.""" - file_name = os.path.basename(input_filepath) + file_name = os.path.basename(input_path) file_name, input_extension = os.path.splitext(file_name) if not output_extension: output_extension = input_extension From c50d9917a4428270db3cc0797da1c4f941d2022e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 27 Jan 2023 11:27:06 +0100 Subject: [PATCH 163/227] OP-4643 - fix wrong assignment --- openpype/plugins/publish/extract_color_transcode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index c4cef15ea6..4e899a519c 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -114,7 +114,7 @@ class ExtractOIIOTranscode(publish.Extractor): if view: new_repre["colorspaceData"]["view"] = view if display: - new_repre["colorspaceData"]["view"] = display + new_repre["colorspaceData"]["display"] = display files_to_convert = self._translate_to_sequence( files_to_convert) From f226dc60cf055d836614b540b0eca31611f568ce Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 27 Jan 2023 11:59:13 +0100 Subject: [PATCH 164/227] OP-4643 - fix files to delete --- .../publish/extract_color_transcode.py | 45 ++++++++++++------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 4e899a519c..99e684ba21 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -84,26 +84,18 @@ class ExtractOIIOTranscode(publish.Extractor): original_staging_dir = new_repre["stagingDir"] new_staging_dir = get_transcode_temp_directory() new_repre["stagingDir"] = new_staging_dir - files_to_convert = new_repre["files"] - if not isinstance(files_to_convert, list): - files_to_convert = [files_to_convert] - files_to_delete = copy.deepcopy(files_to_convert) + if isinstance(new_repre["files"], list): + files_to_convert = copy.deepcopy(new_repre["files"]) + else: + files_to_convert = [new_repre["files"]] output_extension = output_def["extension"] output_extension = output_extension.replace('.', '') if output_extension: - if new_repre["name"] == new_repre["ext"]: - new_repre["name"] = output_extension - new_repre["ext"] = output_extension - - renamed_files = [] - _, orig_ext = os.path.splitext(files_to_convert[0]) - for file_name in files_to_convert: - file_name = file_name.replace(orig_ext, - "."+output_extension) - renamed_files.append(file_name) - new_repre["files"] = renamed_files + self._rename_in_representation(new_repre, + files_to_convert, + output_extension) target_colorspace = output_def["colorspace"] view = output_def["view"] or colorspace_data.get("view") @@ -135,8 +127,12 @@ class ExtractOIIOTranscode(publish.Extractor): self.log ) - instance.context.data["cleanupFullPaths"].extend( - files_to_delete) + # cleanup temporary transcoded files + for file_name in new_repre["files"]: + transcoded_file_path = os.path.join(new_staging_dir, + file_name) + instance.context.data["cleanupFullPaths"].append( + transcoded_file_path) custom_tags = output_def.get("custom_tags") if custom_tags: @@ -155,6 +151,21 @@ class ExtractOIIOTranscode(publish.Extractor): if added_representations: self._mark_original_repre_for_deletion(repre, profile) + def _rename_in_representation(self, new_repre, files_to_convert, + output_extension): + """Replace old extension with new one everywhere in representation.""" + if new_repre["name"] == new_repre["ext"]: + new_repre["name"] = output_extension + new_repre["ext"] = output_extension + + renamed_files = [] + for file_name in files_to_convert: + file_name, _ = os.path.splitext(file_name) + file_name = '{}.{}'.format(file_name, + output_extension) + renamed_files.append(file_name) + new_repre["files"] = renamed_files + def _translate_to_sequence(self, files_to_convert): """Returns original list or list with filename formatted in single sequence format. From 97a2014c125d7b8f766754e14ac4d74889a5f469 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 27 Jan 2023 13:17:59 +0100 Subject: [PATCH 165/227] OP-4643 - moved output argument to the end --- openpype/lib/transcoding.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 0f6d35affe..e74dab4ccc 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1081,8 +1081,7 @@ def convert_colorspace( input_path, # Don't add any additional attributes "--nosoftwareattrib", - "--colorconfig", config_path, - "-o", output_path + "--colorconfig", config_path ] if all([target_colorspace, view, display]): @@ -1100,5 +1099,7 @@ def convert_colorspace( oiio_cmd.extend(["--iscolorspace", source_colorspace]) oiio_cmd.extend(["--ociodisplay", display, view]) + oiio_cmd.extend(["-o", output_path]) + logger.debug("Conversion command: {}".format(" ".join(oiio_cmd))) run_subprocess(oiio_cmd, logger=logger) From 3d2f4319369d6459263cd79e69bec3898ee86efa Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 27 Jan 2023 13:18:33 +0100 Subject: [PATCH 166/227] OP-4643 - fix no tags in repre --- openpype/plugins/publish/extract_color_transcode.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 99e684ba21..3d897c6d9f 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -142,6 +142,8 @@ class ExtractOIIOTranscode(publish.Extractor): # Add additional tags from output definition to representation for tag in output_def["tags"]: + if not new_repre.get("tags"): + new_repre["tags"] = [] if tag not in new_repre["tags"]: new_repre["tags"].append(tag) From 016111ab3381e2ba6c9b5b47fe361a25208c1a6b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 27 Jan 2023 13:26:07 +0100 Subject: [PATCH 167/227] OP-4643 - changed docstring Elaborated more that 'target_colorspace' and ('view', 'display') are disjunctive. --- openpype/lib/transcoding.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index e74dab4ccc..f7d5e222c8 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1053,8 +1053,8 @@ def convert_colorspace( config_path, source_colorspace, target_colorspace, - view, - display, + view=None, + display=None, logger=None ): """Convert source file from one color space to another. @@ -1067,7 +1067,9 @@ def convert_colorspace( config_path (str): path to OCIO config file source_colorspace (str): ocio valid color space of source files target_colorspace (str): ocio valid target color space + if filled, 'view' and 'display' must be empty view (str): name for viewer space (ocio valid) + both 'view' and 'display' must be filled (if 'target_colorspace') display (str): name for display-referred reference space (ocio valid) logger (logging.Logger): Logger used for logging. Raises: From e1d68ec387572f180844ca8677110cc32d8cf9df Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 1 Feb 2023 11:14:07 +0100 Subject: [PATCH 168/227] OP-4663 - fix double dots in extension Co-authored-by: Toke Jepsen --- openpype/plugins/publish/extract_color_transcode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 3d897c6d9f..bfed69c300 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -207,7 +207,7 @@ class ExtractOIIOTranscode(publish.Extractor): file_name = os.path.basename(input_path) file_name, input_extension = os.path.splitext(file_name) if not output_extension: - output_extension = input_extension + output_extension = input_extension.replace(".", "") new_file_name = '{}.{}'.format(file_name, output_extension) return os.path.join(output_dir, new_file_name) From b5246cdf6587975cec5638666e82196e84854de3 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 1 Feb 2023 16:11:45 +0100 Subject: [PATCH 169/227] OP-4643 - update documentation in Settings schema --- .../schemas/projects_schema/schemas/schema_global_publish.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 74b81b13af..3956f403f4 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 @@ -207,7 +207,7 @@ "children": [ { "type": "label", - "label": "Configure output format(s) and color spaces for matching representations. Empty 'Output extension' denotes keeping source extension." + "label": "Configure Output Definition(s) for new representation(s). \nEmpty 'Extension' denotes keeping source extension. \nName(key) of output definition will be used as new representation name \nunless 'passthrough' value is used to keep existing name. \nFill either 'Colorspace' (for target colorspace) or \nboth 'Display' and 'View' (for display and viewer colorspaces)." }, { "type": "boolean", From b4085288c34f0a035f5be5a536bb6cfdcc4f1a2c Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 1 Feb 2023 16:13:59 +0100 Subject: [PATCH 170/227] OP-4643 - name of new representation from output definition key --- .../publish/extract_color_transcode.py | 49 +++++++++++++++---- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index bfed69c300..e39ea3add9 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -32,6 +32,25 @@ class ExtractOIIOTranscode(publish.Extractor): - task types - task names - subset names + + Can produce one or more representations (with different extensions) based + on output definition in format: + "output_name: { + "extension": "png", + "colorspace": "ACES - ACEScg", + "display": "", + "view": "", + "tags": [], + "custom_tags": [] + } + + If 'extension' is empty original representation extension is used. + 'output_name' will be used as name of new representation. In case of value + 'passthrough' name of original representation will be used. + + 'colorspace' denotes target colorspace to be transcoded into. Could be + empty if transcoding should be only into display and viewer colorspace. + (In that case both 'display' and 'view' must be filled.) """ label = "Transcode color spaces" @@ -78,7 +97,7 @@ class ExtractOIIOTranscode(publish.Extractor): self.log.warning("Config file doesn't exist, skipping") continue - for _, output_def in profile.get("outputs", {}).items(): + for output_name, output_def in profile.get("outputs", {}).items(): new_repre = copy.deepcopy(repre) original_staging_dir = new_repre["stagingDir"] @@ -92,10 +111,10 @@ class ExtractOIIOTranscode(publish.Extractor): output_extension = output_def["extension"] output_extension = output_extension.replace('.', '') - if output_extension: - self._rename_in_representation(new_repre, - files_to_convert, - output_extension) + self._rename_in_representation(new_repre, + files_to_convert, + output_name, + output_extension) target_colorspace = output_def["colorspace"] view = output_def["view"] or colorspace_data.get("view") @@ -154,10 +173,22 @@ class ExtractOIIOTranscode(publish.Extractor): self._mark_original_repre_for_deletion(repre, profile) def _rename_in_representation(self, new_repre, files_to_convert, - output_extension): - """Replace old extension with new one everywhere in representation.""" - if new_repre["name"] == new_repre["ext"]: - new_repre["name"] = output_extension + output_name, output_extension): + """Replace old extension with new one everywhere in representation. + + Args: + new_repre (dict) + files_to_convert (list): of filenames from repre["files"], + standardized to always list + output_name (str): key of output definition from Settings, + if "" token used, keep original repre name + output_extension (str): extension from output definition + """ + if output_name != "passthrough": + new_repre["name"] = output_name + if not output_extension: + return + new_repre["ext"] = output_extension renamed_files = [] From 925c7a9564fa1e2c8cc61d025de35d75af155939 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 1 Feb 2023 16:42:48 +0100 Subject: [PATCH 171/227] OP-4643 - updated docstring for convert_colorspace --- openpype/lib/transcoding.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index f7d5e222c8..b6edd863f8 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1062,8 +1062,11 @@ def convert_colorspace( Args: input_path (str): Path that should be converted. It is expected that contains single file or image sequence of same type - (sequence in format 'file.FRAMESTART-FRAMEEND#.exr', see oiio docs) + (sequence in format 'file.FRAMESTART-FRAMEEND#.ext', see oiio docs, + eg `big.1-3#.tif`) output_path (str): Path to output filename. + (must follow format of 'input_path', eg. single file or + sequence in 'file.FRAMESTART-FRAMEEND#.ext', `output.1-3#.tif`) config_path (str): path to OCIO config file source_colorspace (str): ocio valid color space of source files target_colorspace (str): ocio valid target color space From d96775867a333566ff0681dbdb86688c3bda7f3d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 7 Feb 2023 18:22:10 +0100 Subject: [PATCH 172/227] OP-4643 - remove review from old representation If new representation gets created and adds 'review' tag it becomes new reviewable representation. --- .../publish/extract_color_transcode.py | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index e39ea3add9..d10b887a0b 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -89,6 +89,7 @@ class ExtractOIIOTranscode(publish.Extractor): continue added_representations = False + added_review = False colorspace_data = repre["colorspaceData"] source_colorspace = colorspace_data["colorspace"] @@ -166,11 +167,15 @@ class ExtractOIIOTranscode(publish.Extractor): if tag not in new_repre["tags"]: new_repre["tags"].append(tag) + if tag == "review": + added_review = True + instance.data["representations"].append(new_repre) added_representations = True if added_representations: - self._mark_original_repre_for_deletion(repre, profile) + self._mark_original_repre_for_deletion(repre, profile, + added_review) def _rename_in_representation(self, new_repre, files_to_convert, output_name, output_extension): @@ -300,15 +305,16 @@ class ExtractOIIOTranscode(publish.Extractor): return True - def _mark_original_repre_for_deletion(self, repre, profile): + def _mark_original_repre_for_deletion(self, repre, profile, added_review): """If new transcoded representation created, delete old.""" + if not repre.get("tags"): + repre["tags"] = [] + delete_original = profile["delete_original"] if delete_original: - if not repre.get("tags"): - repre["tags"] = [] - - if "review" in repre["tags"]: - repre["tags"].remove("review") if "delete" not in repre["tags"]: repre["tags"].append("delete") + + if added_review and "review" in repre["tags"]: + repre["tags"].remove("review") From 7540f61791958b6043ad1a466900a41fc8b27e4c Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 7 Feb 2023 18:23:42 +0100 Subject: [PATCH 173/227] OP-4643 - remove representation that should be deleted Or old revieable representation would be reviewed too. --- openpype/plugins/publish/extract_color_transcode.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index d10b887a0b..93ee1ec44d 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -177,6 +177,11 @@ class ExtractOIIOTranscode(publish.Extractor): self._mark_original_repre_for_deletion(repre, profile, added_review) + for repre in tuple(instance.data["representations"]): + tags = repre.get("tags") or [] + if "delete" in tags and "thumbnail" not in tags: + instance.data["representations"].remove(repre) + def _rename_in_representation(self, new_repre, files_to_convert, output_name, output_extension): """Replace old extension with new one everywhere in representation. From 82b44da739625242d4e2a0ffddec317cad25e806 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 14 Feb 2023 14:54:25 +0100 Subject: [PATCH 174/227] OP-4643 - fix logging Wrong variable used --- openpype/plugins/publish/extract_review.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index dcb43d7fa2..0f6dacba18 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -169,7 +169,7 @@ class ExtractReview(pyblish.api.InstancePlugin): "Skipped representation. All output definitions from" " selected profile does not match to representation's" " custom tags. \"{}\"" - ).format(str(tags))) + ).format(str(custom_tags))) continue outputs_per_representations.append((repre, outputs)) From 1d12316ee18889a2b18e02db06edf082e6a61d70 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 14 Feb 2023 15:14:14 +0100 Subject: [PATCH 175/227] OP-4643 - allow new repre to stay One might want to delete outputs with 'delete' tag, but repre must stay there at least until extract_review. More universal new tag might be created for this. --- openpype/plugins/publish/extract_color_transcode.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 93ee1ec44d..4a03e623fd 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -161,15 +161,17 @@ class ExtractOIIOTranscode(publish.Extractor): new_repre["custom_tags"].extend(custom_tags) # Add additional tags from output definition to representation + if not new_repre.get("tags"): + new_repre["tags"] = [] for tag in output_def["tags"]: - if not new_repre.get("tags"): - new_repre["tags"] = [] if tag not in new_repre["tags"]: new_repre["tags"].append(tag) if tag == "review": added_review = True + new_repre["tags"].append("newly_added") + instance.data["representations"].append(new_repre) added_representations = True @@ -179,6 +181,12 @@ class ExtractOIIOTranscode(publish.Extractor): for repre in tuple(instance.data["representations"]): tags = repre.get("tags") or [] + # TODO implement better way, for now do not delete new repre + # new repre might have 'delete' tag to removed, but it first must + # be there for review to be created + if "newly_added" in tags: + tags.remove("newly_added") + continue if "delete" in tags and "thumbnail" not in tags: instance.data["representations"].remove(repre) From b5c3e0931e0dc1d13d09cd2b8579e1fc86b0169e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 15 Feb 2023 16:04:59 +0100 Subject: [PATCH 176/227] OP-4642 - added additional command arguments to Settings --- .../schemas/schema_global_publish.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) 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 3956f403f4..5333d514b5 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 @@ -286,6 +286,20 @@ "label": "View", "type": "text" }, + { + "key": "oiiotool_args", + "label": "OIIOtool arguments", + "type": "dict", + "highlight_content": true, + "children": [ + { + "key": "additional_command_args", + "label": "Additional command line arguments", + "type": "list", + "object_type": "text" + } + ] + }, { "type": "schema", "name": "schema_representation_tags" From 3921982365792bb92912777c0cc130462c0e6e14 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 15 Feb 2023 16:08:06 +0100 Subject: [PATCH 177/227] OP-4642 - added additional command arguments for oiiotool Some extension requires special command line arguments (.dpx and binary depth). --- openpype/lib/transcoding.py | 6 ++++++ openpype/plugins/publish/extract_color_transcode.py | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index b6edd863f8..982cee7a46 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1055,6 +1055,7 @@ def convert_colorspace( target_colorspace, view=None, display=None, + additional_command_args=None, logger=None ): """Convert source file from one color space to another. @@ -1074,6 +1075,8 @@ def convert_colorspace( view (str): name for viewer space (ocio valid) both 'view' and 'display' must be filled (if 'target_colorspace') display (str): name for display-referred reference space (ocio valid) + additional_command_args (list): arguments for oiiotool (like binary + depth for .dpx) logger (logging.Logger): Logger used for logging. Raises: ValueError: if misconfigured @@ -1096,6 +1099,9 @@ def convert_colorspace( if not target_colorspace and not all([view, display]): raise ValueError("Both screen and display must be set.") + if additional_command_args: + oiio_cmd.extend(additional_command_args) + if target_colorspace: oiio_cmd.extend(["--colorconvert", source_colorspace, diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 4a03e623fd..3de404125d 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -128,6 +128,9 @@ class ExtractOIIOTranscode(publish.Extractor): if display: new_repre["colorspaceData"]["display"] = display + additional_command_args = (output_def["oiiotool_args"] + ["additional_command_args"]) + files_to_convert = self._translate_to_sequence( files_to_convert) for file_name in files_to_convert: @@ -144,6 +147,7 @@ class ExtractOIIOTranscode(publish.Extractor): target_colorspace, view, display, + additional_command_args, self.log ) From 0834b7564b842832cba47a80a2b8c933d8bce918 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 15 Feb 2023 16:21:25 +0100 Subject: [PATCH 178/227] OP-4642 - refactored newly added representations --- openpype/plugins/publish/extract_color_transcode.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 3de404125d..8c4ef59de9 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -82,6 +82,7 @@ class ExtractOIIOTranscode(publish.Extractor): if not profile: return + new_representations = [] repres = instance.data.get("representations") or [] for idx, repre in enumerate(list(repres)): self.log.debug("repre ({}): `{}`".format(idx + 1, repre["name"])) @@ -174,9 +175,7 @@ class ExtractOIIOTranscode(publish.Extractor): if tag == "review": added_review = True - new_repre["tags"].append("newly_added") - - instance.data["representations"].append(new_repre) + new_representations.append(new_repre) added_representations = True if added_representations: @@ -185,15 +184,11 @@ class ExtractOIIOTranscode(publish.Extractor): for repre in tuple(instance.data["representations"]): tags = repre.get("tags") or [] - # TODO implement better way, for now do not delete new repre - # new repre might have 'delete' tag to removed, but it first must - # be there for review to be created - if "newly_added" in tags: - tags.remove("newly_added") - continue if "delete" in tags and "thumbnail" not in tags: instance.data["representations"].remove(repre) + instance.data["representations"].extend(new_representations) + def _rename_in_representation(self, new_repre, files_to_convert, output_name, output_extension): """Replace old extension with new one everywhere in representation. From 263d3dccc2bb2f2ddc3d18d725a9d16101404cbb Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 15 Feb 2023 16:24:53 +0100 Subject: [PATCH 179/227] OP-4642 - refactored query of representations line 73 returns if no representations. --- openpype/plugins/publish/extract_color_transcode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 8c4ef59de9..de36ea7d5f 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -83,7 +83,7 @@ class ExtractOIIOTranscode(publish.Extractor): return new_representations = [] - repres = instance.data.get("representations") or [] + repres = instance.data["representations"] for idx, repre in enumerate(list(repres)): self.log.debug("repre ({}): `{}`".format(idx + 1, repre["name"])) if not self._repre_is_valid(repre): From cf066d1441d5d356e4be59591aa8fadfc24bcd0b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 10:44:10 +0100 Subject: [PATCH 180/227] OP-4643 - fixed subset filtering Co-authored-by: Toke Jepsen --- openpype/plugins/publish/extract_color_transcode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index de36ea7d5f..71124b527a 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -273,7 +273,7 @@ class ExtractOIIOTranscode(publish.Extractor): "families": family, "task_names": task_name, "task_types": task_type, - "subset": subset + "subsets": subset } profile = filter_profiles(self.profiles, filtering_criteria, logger=self.log) From 984974d7e01a48ebcd704e167c1e3fef42418d82 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 12:12:35 +0100 Subject: [PATCH 181/227] OP-4643 - split command line arguments to separate items Reuse existing method from ExtractReview, put it into transcoding.py --- openpype/lib/transcoding.py | 29 +++++++++++++++++++++- openpype/plugins/publish/extract_review.py | 27 +++----------------- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 982cee7a46..4d2f72fc41 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1100,7 +1100,7 @@ def convert_colorspace( raise ValueError("Both screen and display must be set.") if additional_command_args: - oiio_cmd.extend(additional_command_args) + oiio_cmd.extend(split_cmd_args(additional_command_args)) if target_colorspace: oiio_cmd.extend(["--colorconvert", @@ -1114,3 +1114,30 @@ def convert_colorspace( logger.debug("Conversion command: {}".format(" ".join(oiio_cmd))) run_subprocess(oiio_cmd, logger=logger) + + +def split_cmd_args(in_args): + """Makes sure all entered arguments are separated in individual items. + + Split each argument string with " -" to identify if string contains + one or more arguments. + Args: + in_args (list): of arguments ['-n', '-d uint10'] + Returns + (list): ['-n', '-d', 'unint10'] + """ + splitted_args = [] + for arg in in_args: + sub_args = arg.split(" -") + if len(sub_args) == 1: + if arg and arg not in splitted_args: + splitted_args.append(arg) + continue + + for idx, arg in enumerate(sub_args): + if idx != 0: + arg = "-" + arg + + if arg and arg not in splitted_args: + splitted_args.append(arg) + return splitted_args diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index 0f6dacba18..e80141fc4a 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -22,6 +22,7 @@ from openpype.lib.transcoding import ( should_convert_for_ffmpeg, convert_input_paths_for_ffmpeg, get_transcode_temp_directory, + split_cmd_args ) @@ -670,7 +671,7 @@ class ExtractReview(pyblish.api.InstancePlugin): res_filters = self.rescaling_filters(temp_data, output_def, new_repre) ffmpeg_video_filters.extend(res_filters) - ffmpeg_input_args = self.split_ffmpeg_args(ffmpeg_input_args) + ffmpeg_input_args = split_cmd_args(ffmpeg_input_args) lut_filters = self.lut_filters(new_repre, instance, ffmpeg_input_args) ffmpeg_video_filters.extend(lut_filters) @@ -723,28 +724,6 @@ class ExtractReview(pyblish.api.InstancePlugin): ffmpeg_output_args ) - def split_ffmpeg_args(self, in_args): - """Makes sure all entered arguments are separated in individual items. - - Split each argument string with " -" to identify if string contains - one or more arguments. - """ - splitted_args = [] - for arg in in_args: - sub_args = arg.split(" -") - if len(sub_args) == 1: - if arg and arg not in splitted_args: - splitted_args.append(arg) - continue - - for idx, arg in enumerate(sub_args): - if idx != 0: - arg = "-" + arg - - if arg and arg not in splitted_args: - splitted_args.append(arg) - return splitted_args - def ffmpeg_full_args( self, input_args, video_filters, audio_filters, output_args ): @@ -764,7 +743,7 @@ class ExtractReview(pyblish.api.InstancePlugin): Returns: list: Containing all arguments ready to run in subprocess. """ - output_args = self.split_ffmpeg_args(output_args) + output_args = split_cmd_args(output_args) video_args_dentifiers = ["-vf", "-filter:v"] audio_args_dentifiers = ["-af", "-filter:a"] From 51c54e1aa1bf602fe4cc2ba0ff1110dcfe5f5539 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 13:02:41 +0100 Subject: [PATCH 182/227] OP-4643 - refactor - changed existence check --- openpype/plugins/publish/extract_color_transcode.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 71124b527a..456e40008d 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -161,12 +161,12 @@ class ExtractOIIOTranscode(publish.Extractor): custom_tags = output_def.get("custom_tags") if custom_tags: - if not new_repre.get("custom_tags"): + if new_repre.get("custom_tags") is None: new_repre["custom_tags"] = [] new_repre["custom_tags"].extend(custom_tags) # Add additional tags from output definition to representation - if not new_repre.get("tags"): + if new_repre.get("tags") is None: new_repre["tags"] = [] for tag in output_def["tags"]: if tag not in new_repre["tags"]: From cb551fe83acde2ea43e61706273bf33fec1c37d3 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 13:11:11 +0100 Subject: [PATCH 183/227] Revert "Fix - added missed scopes for Slack bot" This reverts commit 5e0c4a3ab1432e120b8f0c324f899070f1a5f831. --- openpype/modules/slack/manifest.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/modules/slack/manifest.yml b/openpype/modules/slack/manifest.yml index 233c39fbaf..7a65cc5915 100644 --- a/openpype/modules/slack/manifest.yml +++ b/openpype/modules/slack/manifest.yml @@ -19,8 +19,6 @@ oauth_config: - chat:write.public - files:write - channels:read - - users:read - - usergroups:read settings: org_deploy_enabled: false socket_mode_enabled: false From e2ebbae14772b4f24b169c104cdabfcabfab6b66 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 13:10:11 +0100 Subject: [PATCH 184/227] OP-4643 - changed label MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jakub Ježek --- .../schemas/projects_schema/schemas/schema_global_publish.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 5333d514b5..3e9467af61 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 @@ -294,7 +294,7 @@ "children": [ { "key": "additional_command_args", - "label": "Additional command line arguments", + "label": "Arguments", "type": "list", "object_type": "text" } From 4e755e193a6c1e6f2d074d98d2684b3e18cf5282 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 15:06:16 +0100 Subject: [PATCH 185/227] OP-4643 - added documentation --- .../assets/global_oiio_transcode.png | Bin 0 -> 29010 bytes .../project_settings/settings_project_global.md | 15 +++++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 website/docs/project_settings/assets/global_oiio_transcode.png diff --git a/website/docs/project_settings/assets/global_oiio_transcode.png b/website/docs/project_settings/assets/global_oiio_transcode.png new file mode 100644 index 0000000000000000000000000000000000000000..99396d5bb3f16d434a92c6079515128488b82489 GIT binary patch literal 29010 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 literal 0 HcmV?d00001 diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index 37fed93e69..52671d2db6 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -45,6 +45,21 @@ The _input pattern_ matching uses REGEX expression syntax (try [regexr.com](http The **colorspace name** value is a raw string input and no validation is run after saving project settings. We recommend to open the specified `config.ocio` file and copy pasting the exact colorspace names. ::: +### Extract OIIO Transcode +There is profile configurable (see lower) plugin which allows to transcode any incoming representation to one or multiple new representations (configured in `Output Definitions`) with different target colorspaces. +Plugin expects instances with filled dictionary `colorspaceData` on a representation. This data contains information about source colorspace and must be collected for transcoding. +`oiiotool` is used for transcoding, eg. `oiiotool` must be present in `vendor/bin/oiio` or environment variable `OPENPYPE_OIIO_PATHS` must be provided for custom oiio installation. + +Notable parameters: +- **`Delete Original Representation`** - keep or remove original representation. If old representation is kept, but there is new transcoded representation with 'Create review' tag, original representation looses its 'review' tag if present. +- **`Extension`** - target extension, could be empty - original extension is used +- **`Colorspace`** - target colorspace - must be available in used color config +- **`Display & View`** - transcoding into colorspace OR into display and viewer space could be used. (It is disjunctive: Colorspace & nothing in Display and View or opposite) +- **`Arguments`** - special additional command line arguments for `oiiotool` + + +Example here describes use case for creation of new color coded review of png image sequence. Original representation's files are kept intact, review is created from transcoded files, but these files are removed in cleanup process. +![global_oiio_transcode](assets/global_oiio_transcode.png) ## Profile filters From 54e92f02b9f1a31fd9b05005864b6f1e8e5b99ff Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 16:41:42 +0100 Subject: [PATCH 186/227] OP-4643 - updates to documentation Co-authored-by: Toke Jepsen --- website/docs/project_settings/settings_project_global.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index 52671d2db6..cc661a21fa 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -46,7 +46,7 @@ The **colorspace name** value is a raw string input and no validation is run aft ::: ### Extract OIIO Transcode -There is profile configurable (see lower) plugin which allows to transcode any incoming representation to one or multiple new representations (configured in `Output Definitions`) with different target colorspaces. +There is profile configurable plugin which allows to transcode any incoming representation to one or multiple new representations (configured in `Output Definitions`) with different target colorspaces. Plugin expects instances with filled dictionary `colorspaceData` on a representation. This data contains information about source colorspace and must be collected for transcoding. `oiiotool` is used for transcoding, eg. `oiiotool` must be present in `vendor/bin/oiio` or environment variable `OPENPYPE_OIIO_PATHS` must be provided for custom oiio installation. From 665132d9feb19a6d566e2541ccb1207bef1dcc53 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 16:42:06 +0100 Subject: [PATCH 187/227] OP-4643 - updates to documentation Co-authored-by: Toke Jepsen --- website/docs/project_settings/settings_project_global.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index cc661a21fa..8e557a381c 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -52,7 +52,7 @@ Plugin expects instances with filled dictionary `colorspaceData` on a representa Notable parameters: - **`Delete Original Representation`** - keep or remove original representation. If old representation is kept, but there is new transcoded representation with 'Create review' tag, original representation looses its 'review' tag if present. -- **`Extension`** - target extension, could be empty - original extension is used +- **`Extension`** - target extension. If left empty, original extension is used. - **`Colorspace`** - target colorspace - must be available in used color config - **`Display & View`** - transcoding into colorspace OR into display and viewer space could be used. (It is disjunctive: Colorspace & nothing in Display and View or opposite) - **`Arguments`** - special additional command line arguments for `oiiotool` From ceca9a5bb89c8da7915d7c4772cbd23448b49714 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 16:42:29 +0100 Subject: [PATCH 188/227] OP-4643 - updates to documentation Co-authored-by: Toke Jepsen --- website/docs/project_settings/settings_project_global.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index 8e557a381c..166400cb7f 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -53,7 +53,7 @@ Plugin expects instances with filled dictionary `colorspaceData` on a representa Notable parameters: - **`Delete Original Representation`** - keep or remove original representation. If old representation is kept, but there is new transcoded representation with 'Create review' tag, original representation looses its 'review' tag if present. - **`Extension`** - target extension. If left empty, original extension is used. -- **`Colorspace`** - target colorspace - must be available in used color config +- **`Colorspace`** - target colorspace, which must be available in used color config. - **`Display & View`** - transcoding into colorspace OR into display and viewer space could be used. (It is disjunctive: Colorspace & nothing in Display and View or opposite) - **`Arguments`** - special additional command line arguments for `oiiotool` From 0a4cae9db2eaf3beea496e57065240a7b4eec1c1 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 16:42:48 +0100 Subject: [PATCH 189/227] OP-4643 - updates to documentation Co-authored-by: Toke Jepsen --- website/docs/project_settings/settings_project_global.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index 166400cb7f..908191f122 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -54,7 +54,7 @@ Notable parameters: - **`Delete Original Representation`** - keep or remove original representation. If old representation is kept, but there is new transcoded representation with 'Create review' tag, original representation looses its 'review' tag if present. - **`Extension`** - target extension. If left empty, original extension is used. - **`Colorspace`** - target colorspace, which must be available in used color config. -- **`Display & View`** - transcoding into colorspace OR into display and viewer space could be used. (It is disjunctive: Colorspace & nothing in Display and View or opposite) +- **`Display & View`** - transcoding into colorspace or into display and viewer space could be used. Cannot use both `Colorspace` and `Display & View` at the same time. - **`Arguments`** - special additional command line arguments for `oiiotool` From 3a0e9dc78ce48bf9d964eed90b5afea24853e8a7 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 16:43:06 +0100 Subject: [PATCH 190/227] OP-4643 - updates to documentation Co-authored-by: Toke Jepsen --- website/docs/project_settings/settings_project_global.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index 908191f122..0a73868d2d 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -55,7 +55,7 @@ Notable parameters: - **`Extension`** - target extension. If left empty, original extension is used. - **`Colorspace`** - target colorspace, which must be available in used color config. - **`Display & View`** - transcoding into colorspace or into display and viewer space could be used. Cannot use both `Colorspace` and `Display & View` at the same time. -- **`Arguments`** - special additional command line arguments for `oiiotool` +- **`Arguments`** - special additional command line arguments for `oiiotool`. Example here describes use case for creation of new color coded review of png image sequence. Original representation's files are kept intact, review is created from transcoded files, but these files are removed in cleanup process. From 7f8a766c6a294991c379a356e63ab3b2fd9e53a7 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 17:21:20 +0100 Subject: [PATCH 191/227] OP-4643 - updates to documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jakub Ježek --- website/docs/project_settings/settings_project_global.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index 0a73868d2d..9e2ee187cc 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -46,8 +46,8 @@ The **colorspace name** value is a raw string input and no validation is run aft ::: ### Extract OIIO Transcode -There is profile configurable plugin which allows to transcode any incoming representation to one or multiple new representations (configured in `Output Definitions`) with different target colorspaces. -Plugin expects instances with filled dictionary `colorspaceData` on a representation. This data contains information about source colorspace and must be collected for transcoding. +OIIOTools transcoder plugin with configurable output presets. Any incoming representation with `colorspaceData` is convertable to single or multiple representations with different target colorspaces or display and viewer names found in linked **config.ocio** file. + `oiiotool` is used for transcoding, eg. `oiiotool` must be present in `vendor/bin/oiio` or environment variable `OPENPYPE_OIIO_PATHS` must be provided for custom oiio installation. Notable parameters: From 559d54c3a1b061f3234b0491f6abbb8b22aee6c8 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 17:56:21 +0100 Subject: [PATCH 192/227] Revert "OP-4643 - split command line arguments to separate items" This reverts commit deaad39437501f18fc3ba4be8b1fc5f0ee3be65d. --- openpype/lib/transcoding.py | 29 +--------------------- openpype/plugins/publish/extract_review.py | 27 +++++++++++++++++--- 2 files changed, 25 insertions(+), 31 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 4d2f72fc41..982cee7a46 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1100,7 +1100,7 @@ def convert_colorspace( raise ValueError("Both screen and display must be set.") if additional_command_args: - oiio_cmd.extend(split_cmd_args(additional_command_args)) + oiio_cmd.extend(additional_command_args) if target_colorspace: oiio_cmd.extend(["--colorconvert", @@ -1114,30 +1114,3 @@ def convert_colorspace( logger.debug("Conversion command: {}".format(" ".join(oiio_cmd))) run_subprocess(oiio_cmd, logger=logger) - - -def split_cmd_args(in_args): - """Makes sure all entered arguments are separated in individual items. - - Split each argument string with " -" to identify if string contains - one or more arguments. - Args: - in_args (list): of arguments ['-n', '-d uint10'] - Returns - (list): ['-n', '-d', 'unint10'] - """ - splitted_args = [] - for arg in in_args: - sub_args = arg.split(" -") - if len(sub_args) == 1: - if arg and arg not in splitted_args: - splitted_args.append(arg) - continue - - for idx, arg in enumerate(sub_args): - if idx != 0: - arg = "-" + arg - - if arg and arg not in splitted_args: - splitted_args.append(arg) - return splitted_args diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index e80141fc4a..0f6dacba18 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -22,7 +22,6 @@ from openpype.lib.transcoding import ( should_convert_for_ffmpeg, convert_input_paths_for_ffmpeg, get_transcode_temp_directory, - split_cmd_args ) @@ -671,7 +670,7 @@ class ExtractReview(pyblish.api.InstancePlugin): res_filters = self.rescaling_filters(temp_data, output_def, new_repre) ffmpeg_video_filters.extend(res_filters) - ffmpeg_input_args = split_cmd_args(ffmpeg_input_args) + ffmpeg_input_args = self.split_ffmpeg_args(ffmpeg_input_args) lut_filters = self.lut_filters(new_repre, instance, ffmpeg_input_args) ffmpeg_video_filters.extend(lut_filters) @@ -724,6 +723,28 @@ class ExtractReview(pyblish.api.InstancePlugin): ffmpeg_output_args ) + def split_ffmpeg_args(self, in_args): + """Makes sure all entered arguments are separated in individual items. + + Split each argument string with " -" to identify if string contains + one or more arguments. + """ + splitted_args = [] + for arg in in_args: + sub_args = arg.split(" -") + if len(sub_args) == 1: + if arg and arg not in splitted_args: + splitted_args.append(arg) + continue + + for idx, arg in enumerate(sub_args): + if idx != 0: + arg = "-" + arg + + if arg and arg not in splitted_args: + splitted_args.append(arg) + return splitted_args + def ffmpeg_full_args( self, input_args, video_filters, audio_filters, output_args ): @@ -743,7 +764,7 @@ class ExtractReview(pyblish.api.InstancePlugin): Returns: list: Containing all arguments ready to run in subprocess. """ - output_args = split_cmd_args(output_args) + output_args = self.split_ffmpeg_args(output_args) video_args_dentifiers = ["-vf", "-filter:v"] audio_args_dentifiers = ["-af", "-filter:a"] From 5678bdbf065922d1e065782ce419149bdd29cbae Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 18:02:17 +0100 Subject: [PATCH 193/227] OP-4643 - different splitting for oiio It seems that logic in ExtractReview does different thing. --- openpype/lib/transcoding.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 982cee7a46..376297ff32 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1100,7 +1100,7 @@ def convert_colorspace( raise ValueError("Both screen and display must be set.") if additional_command_args: - oiio_cmd.extend(additional_command_args) + oiio_cmd.extend(split_cmd_args(additional_command_args)) if target_colorspace: oiio_cmd.extend(["--colorconvert", @@ -1114,3 +1114,21 @@ def convert_colorspace( logger.debug("Conversion command: {}".format(" ".join(oiio_cmd))) run_subprocess(oiio_cmd, logger=logger) + + +def split_cmd_args(in_args): + """Makes sure all entered arguments are separated in individual items. + + Split each argument string with " -" to identify if string contains + one or more arguments. + Args: + in_args (list): of arguments ['-n', '-d uint10'] + Returns + (list): ['-n', '-d', 'unint10'] + """ + splitted_args = [] + for arg in in_args: + if not arg.strip(): + continue + splitted_args.extend(arg.split(" ")) + return splitted_args From f30b3c52307e4db5e6715a56ba9532c89dcfceb8 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 18:14:57 +0100 Subject: [PATCH 194/227] OP-4643 - allow colorspace to be empty and collected from DCC --- openpype/plugins/publish/extract_color_transcode.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 456e40008d..82b92ec93e 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -118,7 +118,8 @@ class ExtractOIIOTranscode(publish.Extractor): output_name, output_extension) - target_colorspace = output_def["colorspace"] + target_colorspace = (output_def["colorspace"] or + colorspace_data.get("colorspace")) view = output_def["view"] or colorspace_data.get("view") display = (output_def["display"] or colorspace_data.get("display")) From 7ecf6fde48aebc705f13ac965e2a9819239b2c87 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 22 Feb 2023 12:22:53 +0100 Subject: [PATCH 195/227] OP-4643 - fix colorspace from DCC representation["colorspaceData"]["colorspace"] is only input colorspace --- openpype/plugins/publish/extract_color_transcode.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 82b92ec93e..456e40008d 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -118,8 +118,7 @@ class ExtractOIIOTranscode(publish.Extractor): output_name, output_extension) - target_colorspace = (output_def["colorspace"] or - colorspace_data.get("colorspace")) + target_colorspace = output_def["colorspace"] view = output_def["view"] or colorspace_data.get("view") display = (output_def["display"] or colorspace_data.get("display")) From 945f1dfe55ad1f150cf0f71baacaea80857a0e4e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 22 Feb 2023 17:33:20 +0100 Subject: [PATCH 196/227] OP-4643 - added explicit enum for transcoding type As transcoding info (colorspace, display) might be collected from DCC, it must be explicit which should be used. --- openpype/lib/transcoding.py | 2 +- .../plugins/publish/extract_color_transcode.py | 15 +++++++++++---- .../schemas/schema_global_publish.json | 9 +++++++++ 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 376297ff32..c0bda2aa37 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1052,7 +1052,7 @@ def convert_colorspace( output_path, config_path, source_colorspace, - target_colorspace, + target_colorspace=None, view=None, display=None, additional_command_args=None, diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 456e40008d..b0921688e9 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -118,10 +118,17 @@ class ExtractOIIOTranscode(publish.Extractor): output_name, output_extension) - target_colorspace = output_def["colorspace"] - view = output_def["view"] or colorspace_data.get("view") - display = (output_def["display"] or - colorspace_data.get("display")) + transcoding_type = output_def["transcoding_type"] + + target_colorspace = view = display = None + if transcoding_type == "colorspace": + target_colorspace = (output_def["colorspace"] or + colorspace_data.get("colorspace")) + else: + view = output_def["view"] or colorspace_data.get("view") + display = (output_def["display"] or + colorspace_data.get("display")) + # both could be already collected by DCC, # but could be overwritten if view: 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 3e9467af61..76574e8b9b 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 @@ -271,6 +271,15 @@ "label": "Extension", "type": "text" }, + { + "type": "enum", + "key": "transcoding_type", + "label": "Transcoding type", + "enum_items": [ + { "colorspace": "Use Colorspace" }, + { "display": "Use Display&View" } + ] + }, { "key": "colorspace", "label": "Colorspace", From 25fb38bd4c210770c711d725ed1b88f0c1b8731b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 22 Feb 2023 17:34:03 +0100 Subject: [PATCH 197/227] OP-4643 - added explicit enum for transcoding type As transcoding info (colorspace, display) might be collected from DCC, it must be explicit which should be used. --- .../assets/global_oiio_transcode.png | Bin 29010 -> 17936 bytes .../settings_project_global.md | 5 +++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/website/docs/project_settings/assets/global_oiio_transcode.png b/website/docs/project_settings/assets/global_oiio_transcode.png index 99396d5bb3f16d434a92c6079515128488b82489..d818ecfe19f93366e5f4664734d68c7b448d7b4f 100644 GIT binary patch literal 17936 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 DbV~re literal 29010 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 diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index 9e2ee187cc..6e78ee5d45 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -53,8 +53,9 @@ OIIOTools transcoder plugin with configurable output presets. Any incoming repre Notable parameters: - **`Delete Original Representation`** - keep or remove original representation. If old representation is kept, but there is new transcoded representation with 'Create review' tag, original representation looses its 'review' tag if present. - **`Extension`** - target extension. If left empty, original extension is used. -- **`Colorspace`** - target colorspace, which must be available in used color config. -- **`Display & View`** - transcoding into colorspace or into display and viewer space could be used. Cannot use both `Colorspace` and `Display & View` at the same time. +- **`Transcoding type`** - transcoding into colorspace or into display and viewer space could be used. Cannot use both at the same time. +- **`Colorspace`** - target colorspace, which must be available in used color config. (If `Transcoding type` is `Use Colorspace` value in configuration is used OR if empty value collected on instance from DCC). +- **`Display & View`** - display and viewer colorspace. (If `Transcoding type` is `Use Display&View` values in configuration is used OR if empty values collected on instance from DCC). - **`Arguments`** - special additional command line arguments for `oiiotool`. From b8f8fd9a5a5342376b28b18f4a9d394ddf3d6b42 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 22 Feb 2023 17:42:07 +0100 Subject: [PATCH 198/227] OP-4643 - added use case for Maya to documentation --- .../assets/global_oiio_transcode2.png | Bin 0 -> 17960 bytes .../project_settings/settings_project_global.md | 3 +++ 2 files changed, 3 insertions(+) create mode 100644 website/docs/project_settings/assets/global_oiio_transcode2.png diff --git a/website/docs/project_settings/assets/global_oiio_transcode2.png b/website/docs/project_settings/assets/global_oiio_transcode2.png new file mode 100644 index 0000000000000000000000000000000000000000..906f780830a96b4bc6f26d98484dd3f9cc3885f2 GIT binary patch literal 17960 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 F{{cSxQDp!C literal 0 HcmV?d00001 diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index 6e78ee5d45..f58d2c2bf2 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -62,6 +62,9 @@ Notable parameters: Example here describes use case for creation of new color coded review of png image sequence. Original representation's files are kept intact, review is created from transcoded files, but these files are removed in cleanup process. ![global_oiio_transcode](assets/global_oiio_transcode.png) +Another use case is to transcode in Maya only `beauty` render layers and use collected `Display` and `View` colorspaces from DCC. +![global_oiio_transcode_in_Maya](assets/global_oiio_transcode.png) + ## Profile filters Many of the settings are using a concept of **Profile filters** From 539ba60eb4c21e310e716a806683acbc7a0284a5 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 23 Feb 2023 11:05:26 +0100 Subject: [PATCH 199/227] OP-4643 - updates to documentation Co-authored-by: Roy Nieterau --- website/docs/project_settings/settings_project_global.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index f58d2c2bf2..d904080ad1 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -51,7 +51,7 @@ OIIOTools transcoder plugin with configurable output presets. Any incoming repre `oiiotool` is used for transcoding, eg. `oiiotool` must be present in `vendor/bin/oiio` or environment variable `OPENPYPE_OIIO_PATHS` must be provided for custom oiio installation. Notable parameters: -- **`Delete Original Representation`** - keep or remove original representation. If old representation is kept, but there is new transcoded representation with 'Create review' tag, original representation looses its 'review' tag if present. +- **`Delete Original Representation`** - keep or remove original representation. If old representation is kept, but there is new transcoded representation with 'Create review' tag, original representation loses its 'review' tag if present. - **`Extension`** - target extension. If left empty, original extension is used. - **`Transcoding type`** - transcoding into colorspace or into display and viewer space could be used. Cannot use both at the same time. - **`Colorspace`** - target colorspace, which must be available in used color config. (If `Transcoding type` is `Use Colorspace` value in configuration is used OR if empty value collected on instance from DCC). From 6ad3421f7f4c6aa7d3cd21d7e63dca19df3d8e41 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 22 Feb 2023 15:40:55 +0100 Subject: [PATCH 200/227] improving deprecation --- openpype/hosts/nuke/api/lib.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 0325838e78..b13c592fbf 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -214,8 +214,9 @@ def update_node_data(node, knobname, data): knob.setValue(knob_value) +@deprecated class Knobby(object): - """[DEPRICATED] For creating knob which it's type isn't + """[DEPRECATED] For creating knob which it's type isn't mapped in `create_knobs` Args: @@ -248,8 +249,9 @@ class Knobby(object): return " ".join(words) +@deprecated def create_knobs(data, tab=None): - """[DEPRICATED] Create knobs by data + """[DEPRECATED] Create knobs by data Depending on the type of each dict value and creates the correct Knob. @@ -342,8 +344,9 @@ def create_knobs(data, tab=None): return knobs +@deprecated def imprint(node, data, tab=None): - """[DEPRICATED] Store attributes with value on node + """[DEPRECATED] Store attributes with value on node Parse user data into Node knobs. Use `collections.OrderedDict` to ensure knob order. @@ -398,8 +401,9 @@ def imprint(node, data, tab=None): node.addKnob(knob) +@deprecated def add_publish_knob(node): - """[DEPRICATED] Add Publish knob to node + """[DEPRECATED] Add Publish knob to node Arguments: node (nuke.Node): nuke node to be processed @@ -416,8 +420,9 @@ def add_publish_knob(node): return node +@deprecated def set_avalon_knob_data(node, data=None, prefix="avalon:"): - """[DEPRICATED] Sets data into nodes's avalon knob + """[DEPRECATED] Sets data into nodes's avalon knob Arguments: node (nuke.Node): Nuke node to imprint with data, @@ -478,8 +483,9 @@ def set_avalon_knob_data(node, data=None, prefix="avalon:"): return node +@deprecated def get_avalon_knob_data(node, prefix="avalon:", create=True): - """[DEPRICATED] Gets a data from nodes's avalon knob + """[DEPRECATED] Gets a data from nodes's avalon knob Arguments: node (obj): Nuke node to search for data, @@ -521,8 +527,9 @@ def get_avalon_knob_data(node, prefix="avalon:", create=True): return data +@deprecated def fix_data_for_node_create(data): - """[DEPRICATED] Fixing data to be used for nuke knobs + """[DEPRECATED] Fixing data to be used for nuke knobs """ for k, v in data.items(): if isinstance(v, six.text_type): @@ -532,8 +539,9 @@ def fix_data_for_node_create(data): return data +@deprecated def add_write_node_legacy(name, **kwarg): - """[DEPRICATED] Adding nuke write node + """[DEPRECATED] Adding nuke write node Arguments: name (str): nuke node name kwarg (attrs): data for nuke knobs @@ -697,7 +705,7 @@ def get_nuke_imageio_settings(): @deprecated("openpype.hosts.nuke.api.lib.get_nuke_imageio_settings") def get_created_node_imageio_setting_legacy(nodeclass, creator, subset): - '''[DEPRICATED] Get preset data for dataflow (fileType, compression, bitDepth) + '''[DEPRECATED] Get preset data for dataflow (fileType, compression, bitDepth) ''' assert any([creator, nodeclass]), nuke.message( From 2e07aa33fa398148dd51ed103cf4da553c4f2379 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 22 Feb 2023 17:21:17 +0100 Subject: [PATCH 201/227] Nuke: baking with multiple reposition nodes also with settings and defaults --- openpype/hosts/nuke/api/plugin.py | 96 ++++++++++++------- .../defaults/project_settings/nuke.json | 35 +++++++ .../schemas/schema_nuke_publish.json | 47 +++++++++ 3 files changed, 144 insertions(+), 34 deletions(-) diff --git a/openpype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py index d3f8357f7d..5521db99c0 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -558,9 +558,7 @@ class ExporterReview(object): self.path_in = self.instance.data.get("path", None) self.staging_dir = self.instance.data["stagingDir"] self.collection = self.instance.data.get("collection", None) - self.data = dict({ - "representations": list() - }) + self.data = {"representations": []} def get_file_info(self): if self.collection: @@ -626,7 +624,7 @@ class ExporterReview(object): nuke_imageio = opnlib.get_nuke_imageio_settings() # TODO: this is only securing backward compatibility lets remove - # this once all projects's anotomy are updated to newer config + # this once all projects's anatomy are updated to newer config if "baking" in nuke_imageio.keys(): return nuke_imageio["baking"]["viewerProcess"] else: @@ -823,8 +821,41 @@ class ExporterReviewMov(ExporterReview): add_tags = [] self.publish_on_farm = farm read_raw = kwargs["read_raw"] + + # TODO: remove this when `reformat_nodes_config` + # is changed in settings reformat_node_add = kwargs["reformat_node_add"] reformat_node_config = kwargs["reformat_node_config"] + + # TODO: make this required in future + reformat_nodes_config = kwargs.get("reformat_nodes_config", {}) + + # TODO: remove this once deprecated is removed + # make sure only reformat_nodes_config is used in future + if reformat_node_add and reformat_nodes_config.get("enabled"): + self.log.warning( + "`reformat_node_add` is deprecated. " + "Please use only `reformat_nodes_config` instead.") + reformat_nodes_config = None + + # TODO: reformat code when backward compatibility is not needed + # warning if reformat_nodes_config is not set + if not reformat_nodes_config: + self.log.warning( + "Please set `reformat_nodes_config` in settings.") + self.log.warning( + "Using `reformat_node_config` instead.") + reformat_nodes_config = { + "enabled": reformat_node_add, + "reposition_nodes": [ + { + "node_class": "Reformat", + "knobs": reformat_node_config + } + ] + } + + bake_viewer_process = kwargs["bake_viewer_process"] bake_viewer_input_process_node = kwargs[ "bake_viewer_input_process"] @@ -846,7 +877,6 @@ class ExporterReviewMov(ExporterReview): subset = self.instance.data["subset"] self._temp_nodes[subset] = [] - # ---------- start nodes creation # Read node r_node = nuke.createNode("Read") @@ -860,44 +890,39 @@ class ExporterReviewMov(ExporterReview): if read_raw: r_node["raw"].setValue(1) - # connect - self._temp_nodes[subset].append(r_node) - self.previous_node = r_node - self.log.debug("Read... `{}`".format(self._temp_nodes[subset])) + # connect to Read node + self._shift_to_previous_node_and_temp(subset, r_node, "Read... `{}`") # add reformat node - if reformat_node_add: + if reformat_nodes_config["enabled"]: + reposition_nodes = reformat_nodes_config["reposition_nodes"] + for reposition_node in reposition_nodes: + node_class = reposition_node["node_class"] + knobs = reposition_node["knobs"] + node = nuke.createNode(node_class) + set_node_knobs_from_settings(node, knobs) + + # connect in order + self._connect_to_above_nodes( + node, subset, "Reposition node... `{}`" + ) # append reformated tag add_tags.append("reformated") - rf_node = nuke.createNode("Reformat") - set_node_knobs_from_settings(rf_node, reformat_node_config) - - # connect - rf_node.setInput(0, self.previous_node) - self._temp_nodes[subset].append(rf_node) - self.previous_node = rf_node - self.log.debug( - "Reformat... `{}`".format(self._temp_nodes[subset])) - # only create colorspace baking if toggled on if bake_viewer_process: if bake_viewer_input_process_node: # View Process node ipn = get_view_process_node() if ipn is not None: - # connect - ipn.setInput(0, self.previous_node) - self._temp_nodes[subset].append(ipn) - self.previous_node = ipn - self.log.debug( - "ViewProcess... `{}`".format( - self._temp_nodes[subset])) + # connect to ViewProcess node + self._connect_to_above_nodes(ipn, subset, "ViewProcess... `{}`") if not self.viewer_lut_raw: # OCIODisplay dag_node = nuke.createNode("OCIODisplay") + # assign display display, viewer = get_viewer_config_from_string( str(baking_view_profile) ) @@ -907,13 +932,7 @@ class ExporterReviewMov(ExporterReview): # assign viewer dag_node["view"].setValue(viewer) - # connect - dag_node.setInput(0, self.previous_node) - self._temp_nodes[subset].append(dag_node) - self.previous_node = dag_node - self.log.debug("OCIODisplay... `{}`".format( - self._temp_nodes[subset])) - + self._connect_to_above_nodes(dag_node, subset, "OCIODisplay... `{}`") # Write node write_node = nuke.createNode("Write") self.log.debug("Path: {}".format(self.path)) @@ -967,6 +986,15 @@ class ExporterReviewMov(ExporterReview): return self.data + def _shift_to_previous_node_and_temp(self, subset, node, message): + self._temp_nodes[subset].append(node) + self.previous_node = node + self.log.debug(message.format(self._temp_nodes[subset])) + + def _connect_to_above_nodes(self, node, subset, message): + node.setInput(0, self.previous_node) + self._shift_to_previous_node_and_temp(subset, node, message) + @deprecated("openpype.hosts.nuke.api.plugin.NukeWriteCreator") class AbstractWriteRender(OpenPypeCreator): diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json index d475c337d9..2545411e0a 100644 --- a/openpype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -446,6 +446,41 @@ "value": false } ], + "reformat_nodes_config": { + "enabled": false, + "reposition_nodes": [ + { + "node_class": "Reformat", + "knobs": [ + { + "type": "text", + "name": "type", + "value": "to format" + }, + { + "type": "text", + "name": "format", + "value": "HD_1080" + }, + { + "type": "text", + "name": "filter", + "value": "Lanczos6" + }, + { + "type": "bool", + "name": "black_outside", + "value": true + }, + { + "type": "bool", + "name": "pbb", + "value": false + } + ] + } + ] + }, "extension": "mov", "add_custom_tags": [] } diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json index 5b9145e7d9..1c542279fc 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json @@ -271,6 +271,10 @@ { "type": "separator" }, + { + "type": "label", + "label": "Currently we are supporting also multiple reposition nodes.
Older single reformat node is still supported
and if it is activated then preference will
be on it. If you want to use multiple reformat
nodes then you need to disable single reformat
node and enable multiple Reformat nodes here." + }, { "type": "boolean", "key": "reformat_node_add", @@ -287,6 +291,49 @@ } ] }, + { + "key": "reformat_nodes_config", + "type": "dict", + "label": "Reformat Nodes", + "collapsible": true, + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "label", + "label": "Reposition knobs supported only.
You can add multiple reformat nodes
and set their knobs. Order of reformat
nodes is important. First reformat node
will be applied first and last reformat
node will be applied last." + }, + { + "key": "reposition_nodes", + "type": "list", + "label": "Reposition nodes", + "object_type": { + "type": "dict", + "children": [ + { + "key": "node_class", + "label": "Node class", + "type": "text" + }, + { + "type": "schema_template", + "name": "template_nuke_knob_inputs", + "template_data": [ + { + "label": "Node knobs", + "key": "knobs" + } + ] + } + ] + } + } + ] + }, { "type": "separator" }, From fb3bda7c80c908dc052f50092e6d94a62947a3ad Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 23 Feb 2023 10:57:00 +0100 Subject: [PATCH 202/227] little fixes --- openpype/hosts/nuke/api/lib.py | 9 +++------ openpype/hosts/nuke/api/plugin.py | 6 +++--- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index b13c592fbf..73d4986b64 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -214,7 +214,6 @@ def update_node_data(node, knobname, data): knob.setValue(knob_value) -@deprecated class Knobby(object): """[DEPRECATED] For creating knob which it's type isn't mapped in `create_knobs` @@ -249,9 +248,8 @@ class Knobby(object): return " ".join(words) -@deprecated def create_knobs(data, tab=None): - """[DEPRECATED] Create knobs by data + """Create knobs by data Depending on the type of each dict value and creates the correct Knob. @@ -344,9 +342,8 @@ def create_knobs(data, tab=None): return knobs -@deprecated def imprint(node, data, tab=None): - """[DEPRECATED] Store attributes with value on node + """Store attributes with value on node Parse user data into Node knobs. Use `collections.OrderedDict` to ensure knob order. @@ -1249,7 +1246,7 @@ def create_write_node( nodes to be created before write with dependency review (bool)[optional]: adding review knob farm (bool)[optional]: rendering workflow target - kwargs (dict)[optional]: additional key arguments for formating + kwargs (dict)[optional]: additional key arguments for formatting Example: prenodes = { diff --git a/openpype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py index 5521db99c0..160ca820a4 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -842,9 +842,9 @@ class ExporterReviewMov(ExporterReview): # warning if reformat_nodes_config is not set if not reformat_nodes_config: self.log.warning( - "Please set `reformat_nodes_config` in settings.") - self.log.warning( - "Using `reformat_node_config` instead.") + "Please set `reformat_nodes_config` in settings. " + "Using `reformat_node_config` instead." + ) reformat_nodes_config = { "enabled": reformat_node_add, "reposition_nodes": [ From b4541d29fe823416c27b9a07424d431a738d3e8a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Feb 2023 11:47:32 +0100 Subject: [PATCH 203/227] nuke assist kickoff --- .../system_settings/applications.json | 128 ++++++++++++++++++ .../system_schema/schema_applications.json | 8 ++ 2 files changed, 136 insertions(+) diff --git a/openpype/settings/defaults/system_settings/applications.json b/openpype/settings/defaults/system_settings/applications.json index f84d99e36b..5fd9b926fb 100644 --- a/openpype/settings/defaults/system_settings/applications.json +++ b/openpype/settings/defaults/system_settings/applications.json @@ -337,6 +337,134 @@ } } }, + "nukeassist": { + "enabled": true, + "label": "Nuke Assist", + "icon": "{}/app_icons/nuke.png", + "host_name": "nuke", + "environment": { + "NUKE_PATH": [ + "{NUKE_PATH}", + "{OPENPYPE_STUDIO_PLUGINS}/nuke" + ] + }, + "variants": { + "13-2": { + "use_python_2": false, + "executables": { + "windows": [ + "C:\\Program Files\\Nuke13.2v1\\Nuke13.2.exe" + ], + "darwin": [], + "linux": [ + "/usr/local/Nuke13.2v1/Nuke13.2" + ] + }, + "arguments": { + "windows": ["--nukeassist"], + "darwin": ["--nukeassist"], + "linux": ["--nukeassist"] + }, + "environment": {} + }, + "13-0": { + "use_python_2": false, + "executables": { + "windows": [ + "C:\\Program Files\\Nuke13.0v1\\Nuke13.0.exe" + ], + "darwin": [], + "linux": [ + "/usr/local/Nuke13.0v1/Nuke13.0" + ] + }, + "arguments": { + "windows": ["--nukeassist"], + "darwin": ["--nukeassist"], + "linux": ["--nukeassist"] + }, + "environment": {} + }, + "12-2": { + "use_python_2": true, + "executables": { + "windows": [ + "C:\\Program Files\\Nuke12.2v3\\Nuke12.2.exe" + ], + "darwin": [], + "linux": [ + "/usr/local/Nuke12.2v3Nuke12.2" + ] + }, + "arguments": { + "windows": ["--nukeassist"], + "darwin": ["--nukeassist"], + "linux": ["--nukeassist"] + }, + "environment": {} + }, + "12-0": { + "use_python_2": true, + "executables": { + "windows": [ + "C:\\Program Files\\Nuke12.0v1\\Nuke12.0.exe" + ], + "darwin": [], + "linux": [ + "/usr/local/Nuke12.0v1/Nuke12.0" + ] + }, + "arguments": { + "windows": ["--nukeassist"], + "darwin": ["--nukeassist"], + "linux": ["--nukeassist"] + }, + "environment": {} + }, + "11-3": { + "use_python_2": true, + "executables": { + "windows": [ + "C:\\Program Files\\Nuke11.3v1\\Nuke11.3.exe" + ], + "darwin": [], + "linux": [ + "/usr/local/Nuke11.3v5/Nuke11.3" + ] + }, + "arguments": { + "windows": ["--nukeassist"], + "darwin": ["--nukeassist"], + "linux": ["--nukeassist"] + }, + "environment": {} + }, + "11-2": { + "use_python_2": true, + "executables": { + "windows": [ + "C:\\Program Files\\Nuke11.2v2\\Nuke11.2.exe" + ], + "darwin": [], + "linux": [] + }, + "arguments": { + "windows": ["--nukeassist"], + "darwin": ["--nukeassist"], + "linux": ["--nukeassist"] + }, + "environment": {} + }, + "__dynamic_keys_labels__": { + "13-2": "13.2", + "13-0": "13.0", + "12-2": "12.2", + "12-0": "12.0", + "11-3": "11.3", + "11-2": "11.2" + } + } + }, "nukex": { "enabled": true, "label": "Nuke X", diff --git a/openpype/settings/entities/schemas/system_schema/schema_applications.json b/openpype/settings/entities/schemas/system_schema/schema_applications.json index 36c5811496..b17687cf71 100644 --- a/openpype/settings/entities/schemas/system_schema/schema_applications.json +++ b/openpype/settings/entities/schemas/system_schema/schema_applications.json @@ -25,6 +25,14 @@ "nuke_label": "Nuke" } }, + { + "type": "schema_template", + "name": "template_nuke", + "template_data": { + "nuke_type": "nukeassist", + "nuke_label": "Nuke Assist" + } + }, { "type": "schema_template", "name": "template_nuke", From e57c9e8dd6dc9b08d914f43805b3583e5c3bbe1c Mon Sep 17 00:00:00 2001 From: ynput Date: Tue, 21 Feb 2023 14:23:26 +0200 Subject: [PATCH 204/227] adding appgroup to prelaunch hook --- openpype/hooks/pre_foundry_apps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hooks/pre_foundry_apps.py b/openpype/hooks/pre_foundry_apps.py index 85f68c6b60..2092d5025d 100644 --- a/openpype/hooks/pre_foundry_apps.py +++ b/openpype/hooks/pre_foundry_apps.py @@ -13,7 +13,7 @@ class LaunchFoundryAppsWindows(PreLaunchHook): # Should be as last hook because must change launch arguments to string order = 1000 - app_groups = ["nuke", "nukex", "hiero", "nukestudio"] + app_groups = ["nuke", "nukeassist", "nukex", "hiero", "nukestudio"] platforms = ["windows"] def execute(self): From fc6e5ca854fad80a6687befba10422a187724bfd Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Feb 2023 13:39:02 +0100 Subject: [PATCH 205/227] adding nukeassist hook --- openpype/hosts/nuke/hooks/__init__.py | 0 openpype/hosts/nuke/hooks/pre_nukeassist_setup.py | 10 ++++++++++ 2 files changed, 10 insertions(+) create mode 100644 openpype/hosts/nuke/hooks/__init__.py create mode 100644 openpype/hosts/nuke/hooks/pre_nukeassist_setup.py diff --git a/openpype/hosts/nuke/hooks/__init__.py b/openpype/hosts/nuke/hooks/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openpype/hosts/nuke/hooks/pre_nukeassist_setup.py b/openpype/hosts/nuke/hooks/pre_nukeassist_setup.py new file mode 100644 index 0000000000..80696c34e5 --- /dev/null +++ b/openpype/hosts/nuke/hooks/pre_nukeassist_setup.py @@ -0,0 +1,10 @@ +from openpype.lib import PreLaunchHook + +class PrelaunchNukeAssistHook(PreLaunchHook): + """ + Adding flag when nukeassist + """ + app_groups = ["nukeassist"] + + def execute(self): + self.launch_context.env["NUKEASSIST"] = True From a1c9e396640e9d6691af68e21ac1fa3ff78e9bf3 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Feb 2023 13:56:17 +0100 Subject: [PATCH 206/227] adding hook to host --- openpype/hosts/nuke/addon.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openpype/hosts/nuke/addon.py b/openpype/hosts/nuke/addon.py index 9d25afe2b6..6a4b91a76d 100644 --- a/openpype/hosts/nuke/addon.py +++ b/openpype/hosts/nuke/addon.py @@ -63,5 +63,12 @@ class NukeAddon(OpenPypeModule, IHostAddon): path_paths.append(quick_time_path) env["PATH"] = os.pathsep.join(path_paths) + def get_launch_hook_paths(self, app): + if app.host_name != self.host_name: + return [] + return [ + os.path.join(NUKE_ROOT_DIR, "hooks") + ] + def get_workfile_extensions(self): return [".nk"] From 71745e185cae75de715a0962fcf34262d4d7df9d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Feb 2023 14:54:51 +0100 Subject: [PATCH 207/227] adding menu assist variant conditions --- openpype/hosts/nuke/api/pipeline.py | 50 +++++++++++-------- openpype/hosts/nuke/hooks/__init__.py | 0 .../hosts/nuke/hooks/pre_nukeassist_setup.py | 2 +- 3 files changed, 30 insertions(+), 22 deletions(-) delete mode 100644 openpype/hosts/nuke/hooks/__init__.py diff --git a/openpype/hosts/nuke/api/pipeline.py b/openpype/hosts/nuke/api/pipeline.py index d5289010cb..306fa50de9 100644 --- a/openpype/hosts/nuke/api/pipeline.py +++ b/openpype/hosts/nuke/api/pipeline.py @@ -71,7 +71,7 @@ CREATE_PATH = os.path.join(PLUGINS_DIR, "create") INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory") MENU_LABEL = os.environ["AVALON_LABEL"] - +ASSIST = bool(os.getenv("NUKEASSIST")) # registering pyblish gui regarding settings in presets if os.getenv("PYBLISH_GUI", None): @@ -207,6 +207,7 @@ def _show_workfiles(): def _install_menu(): + # uninstall original avalon menu main_window = get_main_window() menubar = nuke.menu("Nuke") @@ -217,7 +218,9 @@ def _install_menu(): ) Context.context_label = label context_action = menu.addCommand(label) - context_action.setEnabled(False) + + if not ASSIST: + context_action.setEnabled(False) menu.addSeparator() menu.addCommand( @@ -226,18 +229,20 @@ def _install_menu(): ) menu.addSeparator() - menu.addCommand( - "Create...", - lambda: host_tools.show_publisher( - tab="create" + if not ASSIST: + menu.addCommand( + "Create...", + lambda: host_tools.show_publisher( + tab="create" + ) ) - ) - menu.addCommand( - "Publish...", - lambda: host_tools.show_publisher( - tab="publish" + menu.addCommand( + "Publish...", + lambda: host_tools.show_publisher( + tab="publish" + ) ) - ) + menu.addCommand( "Load...", lambda: host_tools.show_loader( @@ -285,15 +290,18 @@ def _install_menu(): "Build Workfile from template", lambda: build_workfile_template() ) - menu_template.addSeparator() - menu_template.addCommand( - "Create Place Holder", - lambda: create_placeholder() - ) - menu_template.addCommand( - "Update Place Holder", - lambda: update_placeholder() - ) + + if not ASSIST: + menu_template.addSeparator() + menu_template.addCommand( + "Create Place Holder", + lambda: create_placeholder() + ) + menu_template.addCommand( + "Update Place Holder", + lambda: update_placeholder() + ) + menu.addSeparator() menu.addCommand( "Experimental tools...", diff --git a/openpype/hosts/nuke/hooks/__init__.py b/openpype/hosts/nuke/hooks/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/openpype/hosts/nuke/hooks/pre_nukeassist_setup.py b/openpype/hosts/nuke/hooks/pre_nukeassist_setup.py index 80696c34e5..054bd677a7 100644 --- a/openpype/hosts/nuke/hooks/pre_nukeassist_setup.py +++ b/openpype/hosts/nuke/hooks/pre_nukeassist_setup.py @@ -7,4 +7,4 @@ class PrelaunchNukeAssistHook(PreLaunchHook): app_groups = ["nukeassist"] def execute(self): - self.launch_context.env["NUKEASSIST"] = True + self.launch_context.env["NUKEASSIST"] = "1" From f3431c62792cea3a65c95366d105bd782fe46376 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Feb 2023 15:26:07 +0100 Subject: [PATCH 208/227] moving Assist switch to api level condition for updating nodes --- openpype/hosts/nuke/api/__init__.py | 7 ++++++- openpype/hosts/nuke/api/lib.py | 21 ++++++++++++--------- openpype/hosts/nuke/api/pipeline.py | 2 +- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/nuke/api/__init__.py b/openpype/hosts/nuke/api/__init__.py index 1af5ff365d..7766a94140 100644 --- a/openpype/hosts/nuke/api/__init__.py +++ b/openpype/hosts/nuke/api/__init__.py @@ -1,3 +1,4 @@ +import os from .workio import ( file_extensions, has_unsaved_changes, @@ -50,6 +51,8 @@ from .utils import ( get_colorspace_list ) +ASSIST = bool(os.getenv("NUKEASSIST")) + __all__ = ( "file_extensions", "has_unsaved_changes", @@ -92,5 +95,7 @@ __all__ = ( "create_write_node", "colorspace_exists_on_node", - "get_colorspace_list" + "get_colorspace_list", + + "ASSIST" ) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 73d4986b64..ec5bc58f9f 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -49,7 +49,7 @@ from openpype.pipeline.colorspace import ( ) from openpype.pipeline.workfile import BuildWorkfile -from . import gizmo_menu +from . import gizmo_menu, ASSIST from .workio import ( save_file, @@ -2263,14 +2263,17 @@ class WorkfileSettings(object): node['frame_range'].setValue(range) node['frame_range_lock'].setValue(True) - set_node_data( - self._root_node, - INSTANCE_DATA_KNOB, - { - "handleStart": int(handle_start), - "handleEnd": int(handle_end) - } - ) + if not ASSIST: + set_node_data( + self._root_node, + INSTANCE_DATA_KNOB, + { + "handleStart": int(handle_start), + "handleEnd": int(handle_end) + } + ) + else: + log.warning("NukeAssist mode is not allowing updating custom knobs...") def reset_resolution(self): """Set resolution to project resolution.""" diff --git a/openpype/hosts/nuke/api/pipeline.py b/openpype/hosts/nuke/api/pipeline.py index 306fa50de9..94c4518664 100644 --- a/openpype/hosts/nuke/api/pipeline.py +++ b/openpype/hosts/nuke/api/pipeline.py @@ -60,6 +60,7 @@ from .workio import ( work_root, current_file ) +from . import ASSIST log = Logger.get_logger(__name__) @@ -71,7 +72,6 @@ CREATE_PATH = os.path.join(PLUGINS_DIR, "create") INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory") MENU_LABEL = os.environ["AVALON_LABEL"] -ASSIST = bool(os.getenv("NUKEASSIST")) # registering pyblish gui regarding settings in presets if os.getenv("PYBLISH_GUI", None): From fe163ab19b43bbde9c9ee297cf02ca2d42d998b5 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Feb 2023 16:06:39 +0100 Subject: [PATCH 209/227] moving ASSIST switch to utils --- openpype/hosts/nuke/api/__init__.py | 7 +------ openpype/hosts/nuke/api/lib.py | 3 ++- openpype/hosts/nuke/api/pipeline.py | 2 +- openpype/hosts/nuke/api/utils.py | 1 + 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/nuke/api/__init__.py b/openpype/hosts/nuke/api/__init__.py index 7766a94140..1af5ff365d 100644 --- a/openpype/hosts/nuke/api/__init__.py +++ b/openpype/hosts/nuke/api/__init__.py @@ -1,4 +1,3 @@ -import os from .workio import ( file_extensions, has_unsaved_changes, @@ -51,8 +50,6 @@ from .utils import ( get_colorspace_list ) -ASSIST = bool(os.getenv("NUKEASSIST")) - __all__ = ( "file_extensions", "has_unsaved_changes", @@ -95,7 +92,5 @@ __all__ = ( "create_write_node", "colorspace_exists_on_node", - "get_colorspace_list", - - "ASSIST" + "get_colorspace_list" ) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index ec5bc58f9f..dfc647872b 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -49,7 +49,8 @@ from openpype.pipeline.colorspace import ( ) from openpype.pipeline.workfile import BuildWorkfile -from . import gizmo_menu, ASSIST +from . import gizmo_menu +from .utils import ASSIST from .workio import ( save_file, diff --git a/openpype/hosts/nuke/api/pipeline.py b/openpype/hosts/nuke/api/pipeline.py index 94c4518664..55cb77bafe 100644 --- a/openpype/hosts/nuke/api/pipeline.py +++ b/openpype/hosts/nuke/api/pipeline.py @@ -60,7 +60,7 @@ from .workio import ( work_root, current_file ) -from . import ASSIST +from .utils import ASSIST log = Logger.get_logger(__name__) diff --git a/openpype/hosts/nuke/api/utils.py b/openpype/hosts/nuke/api/utils.py index 6bcb752dd1..261eba8401 100644 --- a/openpype/hosts/nuke/api/utils.py +++ b/openpype/hosts/nuke/api/utils.py @@ -4,6 +4,7 @@ import nuke from openpype import resources from .lib import maintained_selection +ASSIST = bool(os.getenv("NUKEASSIST")) def set_context_favorites(favorites=None): """ Adding favorite folders to nuke's browser From 360a5b3b68de7e3f735078173c897e21196adec4 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Feb 2023 16:38:54 +0100 Subject: [PATCH 210/227] move ASSIST to constant --- openpype/hosts/nuke/api/constants.py | 4 ++++ openpype/hosts/nuke/api/lib.py | 2 +- openpype/hosts/nuke/api/pipeline.py | 2 +- openpype/hosts/nuke/api/utils.py | 1 - 4 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 openpype/hosts/nuke/api/constants.py diff --git a/openpype/hosts/nuke/api/constants.py b/openpype/hosts/nuke/api/constants.py new file mode 100644 index 0000000000..110199720f --- /dev/null +++ b/openpype/hosts/nuke/api/constants.py @@ -0,0 +1,4 @@ +import os + + +ASSIST = bool(os.getenv("NUKEASSIST")) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index dfc647872b..9e36fb147b 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -50,7 +50,7 @@ from openpype.pipeline.colorspace import ( from openpype.pipeline.workfile import BuildWorkfile from . import gizmo_menu -from .utils import ASSIST +from .constants import ASSIST from .workio import ( save_file, diff --git a/openpype/hosts/nuke/api/pipeline.py b/openpype/hosts/nuke/api/pipeline.py index 55cb77bafe..f07d150ba5 100644 --- a/openpype/hosts/nuke/api/pipeline.py +++ b/openpype/hosts/nuke/api/pipeline.py @@ -60,7 +60,7 @@ from .workio import ( work_root, current_file ) -from .utils import ASSIST +from .constants import ASSIST log = Logger.get_logger(__name__) diff --git a/openpype/hosts/nuke/api/utils.py b/openpype/hosts/nuke/api/utils.py index 261eba8401..6bcb752dd1 100644 --- a/openpype/hosts/nuke/api/utils.py +++ b/openpype/hosts/nuke/api/utils.py @@ -4,7 +4,6 @@ import nuke from openpype import resources from .lib import maintained_selection -ASSIST = bool(os.getenv("NUKEASSIST")) def set_context_favorites(favorites=None): """ Adding favorite folders to nuke's browser From 132b616f1f9b4c802f7c985b5eb73948d3ae22a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Wed, 22 Feb 2023 14:41:36 +0100 Subject: [PATCH 211/227] Update openpype/hosts/nuke/hooks/pre_nukeassist_setup.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/nuke/hooks/pre_nukeassist_setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/nuke/hooks/pre_nukeassist_setup.py b/openpype/hosts/nuke/hooks/pre_nukeassist_setup.py index 054bd677a7..3a0f00413a 100644 --- a/openpype/hosts/nuke/hooks/pre_nukeassist_setup.py +++ b/openpype/hosts/nuke/hooks/pre_nukeassist_setup.py @@ -8,3 +8,4 @@ class PrelaunchNukeAssistHook(PreLaunchHook): def execute(self): self.launch_context.env["NUKEASSIST"] = "1" + From b3b488c2a6235d71d1e15eb2d058d22c439f5303 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 22 Feb 2023 14:44:22 +0100 Subject: [PATCH 212/227] removing line --- openpype/hosts/nuke/hooks/pre_nukeassist_setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/nuke/hooks/pre_nukeassist_setup.py b/openpype/hosts/nuke/hooks/pre_nukeassist_setup.py index 3a0f00413a..054bd677a7 100644 --- a/openpype/hosts/nuke/hooks/pre_nukeassist_setup.py +++ b/openpype/hosts/nuke/hooks/pre_nukeassist_setup.py @@ -8,4 +8,3 @@ class PrelaunchNukeAssistHook(PreLaunchHook): def execute(self): self.launch_context.env["NUKEASSIST"] = "1" - From 363551af73a5703a08af4a5881448e9bc61885d3 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 22 Feb 2023 14:52:57 +0100 Subject: [PATCH 213/227] context label is not needed in nukeassist --- openpype/hosts/nuke/api/pipeline.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/nuke/api/pipeline.py b/openpype/hosts/nuke/api/pipeline.py index f07d150ba5..4c0f169ade 100644 --- a/openpype/hosts/nuke/api/pipeline.py +++ b/openpype/hosts/nuke/api/pipeline.py @@ -207,22 +207,25 @@ def _show_workfiles(): def _install_menu(): + """Install Avalon menu into Nuke's main menu bar.""" # uninstall original avalon menu main_window = get_main_window() menubar = nuke.menu("Nuke") menu = menubar.addMenu(MENU_LABEL) - label = "{0}, {1}".format( - os.environ["AVALON_ASSET"], os.environ["AVALON_TASK"] - ) - Context.context_label = label - context_action = menu.addCommand(label) if not ASSIST: + label = "{0}, {1}".format( + os.environ["AVALON_ASSET"], os.environ["AVALON_TASK"] + ) + Context.context_label = label + context_action = menu.addCommand(label) context_action.setEnabled(False) - menu.addSeparator() + # add separator after context label + menu.addSeparator() + menu.addCommand( "Work Files...", _show_workfiles From cb87097f74af6d6c616486867e89a9c0afada6c0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 22 Feb 2023 14:56:27 +0100 Subject: [PATCH 214/227] adding empty lines --- openpype/hosts/nuke/api/pipeline.py | 1 - openpype/hosts/nuke/hooks/pre_nukeassist_setup.py | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/api/pipeline.py b/openpype/hosts/nuke/api/pipeline.py index 4c0f169ade..2496d66c1d 100644 --- a/openpype/hosts/nuke/api/pipeline.py +++ b/openpype/hosts/nuke/api/pipeline.py @@ -214,7 +214,6 @@ def _install_menu(): menubar = nuke.menu("Nuke") menu = menubar.addMenu(MENU_LABEL) - if not ASSIST: label = "{0}, {1}".format( os.environ["AVALON_ASSET"], os.environ["AVALON_TASK"] diff --git a/openpype/hosts/nuke/hooks/pre_nukeassist_setup.py b/openpype/hosts/nuke/hooks/pre_nukeassist_setup.py index 054bd677a7..3948a665c6 100644 --- a/openpype/hosts/nuke/hooks/pre_nukeassist_setup.py +++ b/openpype/hosts/nuke/hooks/pre_nukeassist_setup.py @@ -1,5 +1,6 @@ from openpype.lib import PreLaunchHook + class PrelaunchNukeAssistHook(PreLaunchHook): """ Adding flag when nukeassist From 3218dd021ad1f471215fd9e7b1f2ce1972e42d74 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 22 Feb 2023 14:59:30 +0100 Subject: [PATCH 215/227] hound comments --- openpype/hosts/nuke/api/lib.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py index 9e36fb147b..c08db978d3 100644 --- a/openpype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -2274,7 +2274,10 @@ class WorkfileSettings(object): } ) else: - log.warning("NukeAssist mode is not allowing updating custom knobs...") + log.warning( + "NukeAssist mode is not allowing " + "updating custom knobs..." + ) def reset_resolution(self): """Set resolution to project resolution.""" From 7ec1cb77a46675a2e986a78f6081594d67f996a5 Mon Sep 17 00:00:00 2001 From: Kayla Man <64118225+moonyuet@users.noreply.github.com> Date: Thu, 23 Feb 2023 23:00:16 +0800 Subject: [PATCH 216/227] maya gltf texture convertor and validator (#4261) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondřej Samohel <33513211+antirotor@users.noreply.github.com> --- .../maya/plugins/publish/extract_gltf.py | 3 + .../plugins/publish/validate_glsl_material.py | 207 ++++++++++++++++++ .../plugins/publish/validate_glsl_plugin.py | 31 +++ .../defaults/project_settings/maya.json | 15 ++ .../schemas/schema_maya_publish.json | 32 +++ 5 files changed, 288 insertions(+) create mode 100644 openpype/hosts/maya/plugins/publish/validate_glsl_material.py create mode 100644 openpype/hosts/maya/plugins/publish/validate_glsl_plugin.py diff --git a/openpype/hosts/maya/plugins/publish/extract_gltf.py b/openpype/hosts/maya/plugins/publish/extract_gltf.py index f5ceed5f33..ac258ffb3d 100644 --- a/openpype/hosts/maya/plugins/publish/extract_gltf.py +++ b/openpype/hosts/maya/plugins/publish/extract_gltf.py @@ -22,6 +22,8 @@ class ExtractGLB(publish.Extractor): self.log.info("Extracting GLB to: {}".format(path)) + cmds.loadPlugin("maya2glTF", quiet=True) + nodes = instance[:] self.log.info("Instance: {0}".format(nodes)) @@ -45,6 +47,7 @@ class ExtractGLB(publish.Extractor): "glb": True, "vno": True # visibleNodeOnly } + with lib.maintained_selection(): cmds.select(nodes, hi=True, noExpand=True) extract_gltf(staging_dir, diff --git a/openpype/hosts/maya/plugins/publish/validate_glsl_material.py b/openpype/hosts/maya/plugins/publish/validate_glsl_material.py new file mode 100644 index 0000000000..10c48da404 --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/validate_glsl_material.py @@ -0,0 +1,207 @@ +import os +from maya import cmds + +import pyblish.api +from openpype.pipeline.publish import ( + RepairAction, + ValidateContentsOrder +) +from openpype.pipeline import PublishValidationError + + +class ValidateGLSLMaterial(pyblish.api.InstancePlugin): + """ + Validate if the asset uses GLSL Shader + """ + + order = ValidateContentsOrder + 0.1 + families = ['gltf'] + hosts = ['maya'] + label = 'GLSL Shader for GLTF' + actions = [RepairAction] + optional = True + active = True + + def process(self, instance): + shading_grp = self.get_material_from_shapes(instance) + if not shading_grp: + raise PublishValidationError("No shading group found") + invalid = self.get_texture_shader_invalid(instance) + if invalid: + raise PublishValidationError("Non GLSL Shader found: " + "{0}".format(invalid)) + + def get_material_from_shapes(self, instance): + shapes = cmds.ls(instance, type="mesh", long=True) + for shape in shapes: + shading_grp = cmds.listConnections(shape, + destination=True, + type="shadingEngine") + + return shading_grp or [] + + def get_texture_shader_invalid(self, instance): + + invalid = set() + shading_grp = self.get_material_from_shapes(instance) + for shading_group in shading_grp: + material_name = "{}.surfaceShader".format(shading_group) + material = cmds.listConnections(material_name, + source=True, + destination=False, + type="GLSLShader") + + if not material: + # add material name + material = cmds.listConnections(material_name)[0] + invalid.add(material) + + return list(invalid) + + @classmethod + def repair(cls, instance): + """ + Repair instance by assigning GLSL Shader + to the material + """ + cls.assign_glsl_shader(instance) + return + + @classmethod + def assign_glsl_shader(cls, instance): + """ + Converting StingrayPBS material to GLSL Shaders + for the glb export through Maya2GLTF plugin + """ + + meshes = cmds.ls(instance, type="mesh", long=True) + cls.log.info("meshes: {}".format(meshes)) + # load the glsl shader plugin + cmds.loadPlugin("glslShader", quiet=True) + + for mesh in meshes: + # create glsl shader + glsl = cmds.createNode('GLSLShader') + glsl_shading_grp = cmds.sets(name=glsl + "SG", empty=True, + renderable=True, noSurfaceShader=True) + cmds.connectAttr(glsl + ".outColor", + glsl_shading_grp + ".surfaceShader") + + # load the maya2gltf shader + ogsfx_path = instance.context.data["project_settings"]["maya"]["publish"]["ExtractGLB"]["ogsfx_path"] # noqa + if not os.path.exists(ogsfx_path): + if ogsfx_path: + # if custom ogsfx path is not specified + # the log below is the warning for the user + cls.log.warning("ogsfx shader file " + "not found in {}".format(ogsfx_path)) + + cls.log.info("Find the ogsfx shader file in " + "default maya directory...") + # re-direct to search the ogsfx path in maya_dir + ogsfx_path = os.getenv("MAYA_APP_DIR") + ogsfx_path + if not os.path.exists(ogsfx_path): + raise PublishValidationError("The ogsfx shader file does not " # noqa + "exist: {}".format(ogsfx_path)) # noqa + + cmds.setAttr(glsl + ".shader", ogsfx_path, typ="string") + # list the materials used for the assets + shading_grp = cmds.listConnections(mesh, + destination=True, + type="shadingEngine") + + # get the materials related to the selected assets + for material in shading_grp: + pbs_shader = cmds.listConnections(material, + destination=True, + type="StingrayPBS") + if pbs_shader: + cls.pbs_shader_conversion(pbs_shader, glsl) + # setting up to relink the texture if + # the mesh is with aiStandardSurface + arnold_shader = cmds.listConnections(material, + destination=True, + type="aiStandardSurface") + if arnold_shader: + cls.arnold_shader_conversion(arnold_shader, glsl) + + cmds.sets(mesh, forceElement=str(glsl_shading_grp)) + + @classmethod + def pbs_shader_conversion(cls, main_shader, glsl): + + cls.log.info("StringrayPBS detected " + "-> Can do texture conversion") + + for shader in main_shader: + # get the file textures related to the PBS Shader + albedo = cmds.listConnections(shader + + ".TEX_color_map") + if albedo: + dif_output = albedo[0] + ".outColor" + # get the glsl_shader input + # reconnect the file nodes to maya2gltf shader + glsl_dif = glsl + ".u_BaseColorTexture" + cmds.connectAttr(dif_output, glsl_dif) + + # connect orm map if there is one + orm_packed = cmds.listConnections(shader + + ".TEX_ao_map") + if orm_packed: + orm_output = orm_packed[0] + ".outColor" + + mtl = glsl + ".u_MetallicTexture" + ao = glsl + ".u_OcclusionTexture" + rough = glsl + ".u_RoughnessTexture" + + cmds.connectAttr(orm_output, mtl) + cmds.connectAttr(orm_output, ao) + cmds.connectAttr(orm_output, rough) + + # connect nrm map if there is one + nrm = cmds.listConnections(shader + + ".TEX_normal_map") + if nrm: + nrm_output = nrm[0] + ".outColor" + glsl_nrm = glsl + ".u_NormalTexture" + cmds.connectAttr(nrm_output, glsl_nrm) + + @classmethod + def arnold_shader_conversion(cls, main_shader, glsl): + cls.log.info("aiStandardSurface detected " + "-> Can do texture conversion") + + for shader in main_shader: + # get the file textures related to the PBS Shader + albedo = cmds.listConnections(shader + ".baseColor") + if albedo: + dif_output = albedo[0] + ".outColor" + # get the glsl_shader input + # reconnect the file nodes to maya2gltf shader + glsl_dif = glsl + ".u_BaseColorTexture" + cmds.connectAttr(dif_output, glsl_dif) + + orm_packed = cmds.listConnections(shader + + ".specularRoughness") + if orm_packed: + orm_output = orm_packed[0] + ".outColor" + + mtl = glsl + ".u_MetallicTexture" + ao = glsl + ".u_OcclusionTexture" + rough = glsl + ".u_RoughnessTexture" + + cmds.connectAttr(orm_output, mtl) + cmds.connectAttr(orm_output, ao) + cmds.connectAttr(orm_output, rough) + + # connect nrm map if there is one + bump_node = cmds.listConnections(shader + + ".normalCamera") + if bump_node: + for bump in bump_node: + nrm = cmds.listConnections(bump + + ".bumpValue") + if nrm: + nrm_output = nrm[0] + ".outColor" + glsl_nrm = glsl + ".u_NormalTexture" + cmds.connectAttr(nrm_output, glsl_nrm) diff --git a/openpype/hosts/maya/plugins/publish/validate_glsl_plugin.py b/openpype/hosts/maya/plugins/publish/validate_glsl_plugin.py new file mode 100644 index 0000000000..53c2cf548a --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/validate_glsl_plugin.py @@ -0,0 +1,31 @@ + +from maya import cmds + +import pyblish.api +from openpype.pipeline.publish import ( + RepairAction, + ValidateContentsOrder +) + + +class ValidateGLSLPlugin(pyblish.api.InstancePlugin): + """ + Validate if the asset uses GLSL Shader + """ + + order = ValidateContentsOrder + 0.15 + families = ['gltf'] + hosts = ['maya'] + label = 'maya2glTF plugin' + actions = [RepairAction] + + def process(self, instance): + if not cmds.pluginInfo("maya2glTF", query=True, loaded=True): + raise RuntimeError("maya2glTF is not loaded") + + @classmethod + def repair(cls, instance): + """ + Repair instance by enabling the plugin + """ + return cmds.loadPlugin("maya2glTF", quiet=True) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index b590a56da6..32b141566b 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -407,6 +407,16 @@ "optional": false, "active": true }, + "ValidateGLSLMaterial": { + "enabled": true, + "optional": false, + "active": true + }, + "ValidateGLSLPlugin": { + "enabled": true, + "optional": false, + "active": true + }, "ValidateRenderImageRule": { "enabled": true, "optional": false, @@ -898,6 +908,11 @@ "optional": true, "active": true, "bake_attributes": [] + }, + "ExtractGLB": { + "enabled": true, + "active": true, + "ogsfx_path": "/maya2glTF/PBR/shaders/glTF_PBR.ogsfx" } }, "load": { diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json index 873bb79c95..994e2d0032 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json @@ -408,6 +408,14 @@ "key": "ValidateCurrentRenderLayerIsRenderable", "label": "Validate Current Render Layer Has Renderable Camera" }, + { + "key": "ValidateGLSLMaterial", + "label": "Validate GLSL Material" + }, + { + "key": "ValidateGLSLPlugin", + "label": "Validate GLSL Plugin" + }, { "key": "ValidateRenderImageRule", "label": "Validate Images File Rule (Workspace)" @@ -956,6 +964,30 @@ "is_list": true } ] + }, + { + "type": "dict", + "collapsible": true, + "key": "ExtractGLB", + "label": "Extract GLB", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "active", + "label": "Active" + }, + { + "type": "text", + "key": "ogsfx_path", + "label": "GLSL Shader Directory" + } + ] } ] } From 64a142ef6482b61eb0967806882ccb6c70ecd91c Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 14:03:22 +0100 Subject: [PATCH 217/227] OP-4643 - fix for full file paths --- openpype/plugins/publish/extract_color_transcode.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index b0921688e9..99c8c87e51 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -1,7 +1,6 @@ import os import copy import clique - import pyblish.api from openpype.pipeline import publish From d2f8407111905b621e29b27202ef3e59afa63983 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 27 Jan 2023 11:59:13 +0100 Subject: [PATCH 218/227] OP-4643 - fix files to delete --- .../plugins/publish/extract_color_transcode.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 99c8c87e51..61e29697d6 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -222,6 +222,21 @@ class ExtractOIIOTranscode(publish.Extractor): renamed_files.append(file_name) new_repre["files"] = renamed_files + def _rename_in_representation(self, new_repre, files_to_convert, + output_extension): + """Replace old extension with new one everywhere in representation.""" + if new_repre["name"] == new_repre["ext"]: + new_repre["name"] = output_extension + new_repre["ext"] = output_extension + + renamed_files = [] + for file_name in files_to_convert: + file_name, _ = os.path.splitext(file_name) + file_name = '{}.{}'.format(file_name, + output_extension) + renamed_files.append(file_name) + new_repre["files"] = renamed_files + def _translate_to_sequence(self, files_to_convert): """Returns original list or list with filename formatted in single sequence format. From c038fbf884f872ec717588290b9ba9273f219d36 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 27 Jan 2023 13:18:33 +0100 Subject: [PATCH 219/227] OP-4643 - fix no tags in repre --- openpype/plugins/publish/extract_color_transcode.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 61e29697d6..aca4adc40c 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -175,6 +175,8 @@ class ExtractOIIOTranscode(publish.Extractor): if new_repre.get("tags") is None: new_repre["tags"] = [] for tag in output_def["tags"]: + if not new_repre.get("tags"): + new_repre["tags"] = [] if tag not in new_repre["tags"]: new_repre["tags"].append(tag) From 195e9b436047a797bd9c99ac37ba5080690e3943 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 1 Feb 2023 16:13:59 +0100 Subject: [PATCH 220/227] OP-4643 - name of new representation from output definition key --- .../publish/extract_color_transcode.py | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index aca4adc40c..bd81dd6087 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -225,10 +225,22 @@ class ExtractOIIOTranscode(publish.Extractor): new_repre["files"] = renamed_files def _rename_in_representation(self, new_repre, files_to_convert, - output_extension): - """Replace old extension with new one everywhere in representation.""" - if new_repre["name"] == new_repre["ext"]: - new_repre["name"] = output_extension + output_name, output_extension): + """Replace old extension with new one everywhere in representation. + + Args: + new_repre (dict) + files_to_convert (list): of filenames from repre["files"], + standardized to always list + output_name (str): key of output definition from Settings, + if "" token used, keep original repre name + output_extension (str): extension from output definition + """ + if output_name != "passthrough": + new_repre["name"] = output_name + if not output_extension: + return + new_repre["ext"] = output_extension renamed_files = [] From 4a70ec9c54fbf7caec8ac8bd1f17adecbd9dd6b0 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 8 Feb 2023 12:07:00 +0100 Subject: [PATCH 221/227] Fix - added missed scopes for Slack bot --- openpype/modules/slack/manifest.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/modules/slack/manifest.yml b/openpype/modules/slack/manifest.yml index 7a65cc5915..233c39fbaf 100644 --- a/openpype/modules/slack/manifest.yml +++ b/openpype/modules/slack/manifest.yml @@ -19,6 +19,8 @@ oauth_config: - chat:write.public - files:write - channels:read + - users:read + - usergroups:read settings: org_deploy_enabled: false socket_mode_enabled: false From 7f94f7ef7183a51bdfa1bd40078a9183ee50e1bd Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 14 Feb 2023 15:14:14 +0100 Subject: [PATCH 222/227] OP-4643 - allow new repre to stay One might want to delete outputs with 'delete' tag, but repre must stay there at least until extract_review. More universal new tag might be created for this. --- openpype/plugins/publish/extract_color_transcode.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index bd81dd6087..4892a00fbe 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -175,8 +175,6 @@ class ExtractOIIOTranscode(publish.Extractor): if new_repre.get("tags") is None: new_repre["tags"] = [] for tag in output_def["tags"]: - if not new_repre.get("tags"): - new_repre["tags"] = [] if tag not in new_repre["tags"]: new_repre["tags"].append(tag) @@ -192,6 +190,12 @@ class ExtractOIIOTranscode(publish.Extractor): for repre in tuple(instance.data["representations"]): tags = repre.get("tags") or [] + # TODO implement better way, for now do not delete new repre + # new repre might have 'delete' tag to removed, but it first must + # be there for review to be created + if "newly_added" in tags: + tags.remove("newly_added") + continue if "delete" in tags and "thumbnail" not in tags: instance.data["representations"].remove(repre) From cce048fd3e0f50441fa9f893dfb9efebe43d95a8 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 15 Feb 2023 16:21:25 +0100 Subject: [PATCH 223/227] OP-4642 - refactored newly added representations --- openpype/plugins/publish/extract_color_transcode.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 4892a00fbe..a6fa710425 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -190,12 +190,6 @@ class ExtractOIIOTranscode(publish.Extractor): for repre in tuple(instance.data["representations"]): tags = repre.get("tags") or [] - # TODO implement better way, for now do not delete new repre - # new repre might have 'delete' tag to removed, but it first must - # be there for review to be created - if "newly_added" in tags: - tags.remove("newly_added") - continue if "delete" in tags and "thumbnail" not in tags: instance.data["representations"].remove(repre) From 9eaa0d1ff8e0882a2778fce44f09fba3f2cccecd Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 12:12:35 +0100 Subject: [PATCH 224/227] OP-4643 - split command line arguments to separate items Reuse existing method from ExtractReview, put it into transcoding.py --- openpype/plugins/publish/extract_review.py | 27 +++------------------- 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index 0f6dacba18..e80141fc4a 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -22,6 +22,7 @@ from openpype.lib.transcoding import ( should_convert_for_ffmpeg, convert_input_paths_for_ffmpeg, get_transcode_temp_directory, + split_cmd_args ) @@ -670,7 +671,7 @@ class ExtractReview(pyblish.api.InstancePlugin): res_filters = self.rescaling_filters(temp_data, output_def, new_repre) ffmpeg_video_filters.extend(res_filters) - ffmpeg_input_args = self.split_ffmpeg_args(ffmpeg_input_args) + ffmpeg_input_args = split_cmd_args(ffmpeg_input_args) lut_filters = self.lut_filters(new_repre, instance, ffmpeg_input_args) ffmpeg_video_filters.extend(lut_filters) @@ -723,28 +724,6 @@ class ExtractReview(pyblish.api.InstancePlugin): ffmpeg_output_args ) - def split_ffmpeg_args(self, in_args): - """Makes sure all entered arguments are separated in individual items. - - Split each argument string with " -" to identify if string contains - one or more arguments. - """ - splitted_args = [] - for arg in in_args: - sub_args = arg.split(" -") - if len(sub_args) == 1: - if arg and arg not in splitted_args: - splitted_args.append(arg) - continue - - for idx, arg in enumerate(sub_args): - if idx != 0: - arg = "-" + arg - - if arg and arg not in splitted_args: - splitted_args.append(arg) - return splitted_args - def ffmpeg_full_args( self, input_args, video_filters, audio_filters, output_args ): @@ -764,7 +743,7 @@ class ExtractReview(pyblish.api.InstancePlugin): Returns: list: Containing all arguments ready to run in subprocess. """ - output_args = self.split_ffmpeg_args(output_args) + output_args = split_cmd_args(output_args) video_args_dentifiers = ["-vf", "-filter:v"] audio_args_dentifiers = ["-af", "-filter:a"] From 751586fd415ea6aa10327cca8fb57f2f1657b9d7 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 13:11:11 +0100 Subject: [PATCH 225/227] Revert "Fix - added missed scopes for Slack bot" This reverts commit 5e0c4a3ab1432e120b8f0c324f899070f1a5f831. --- openpype/modules/slack/manifest.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/modules/slack/manifest.yml b/openpype/modules/slack/manifest.yml index 233c39fbaf..7a65cc5915 100644 --- a/openpype/modules/slack/manifest.yml +++ b/openpype/modules/slack/manifest.yml @@ -19,8 +19,6 @@ oauth_config: - chat:write.public - files:write - channels:read - - users:read - - usergroups:read settings: org_deploy_enabled: false socket_mode_enabled: false From 84ccfa60f43c1fa5a575b62c79779d66dfc2951d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 15:06:16 +0100 Subject: [PATCH 226/227] OP-4643 - added documentation --- website/docs/project_settings/settings_project_global.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index d904080ad1..b320b5502f 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -63,7 +63,7 @@ Example here describes use case for creation of new color coded review of png im ![global_oiio_transcode](assets/global_oiio_transcode.png) Another use case is to transcode in Maya only `beauty` render layers and use collected `Display` and `View` colorspaces from DCC. -![global_oiio_transcode_in_Maya](assets/global_oiio_transcode.png) +![global_oiio_transcode_in_Maya](assets/global_oiio_transcode.png)n ## Profile filters From 72a3572d9527093290ebd600b538e2375e690861 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 17:56:21 +0100 Subject: [PATCH 227/227] Revert "OP-4643 - split command line arguments to separate items" This reverts commit deaad39437501f18fc3ba4be8b1fc5f0ee3be65d. --- openpype/lib/transcoding.py | 3 ++- openpype/plugins/publish/extract_review.py | 27 +++++++++++++++++++--- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index c0bda2aa37..e5e21195e5 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1100,7 +1100,7 @@ def convert_colorspace( raise ValueError("Both screen and display must be set.") if additional_command_args: - oiio_cmd.extend(split_cmd_args(additional_command_args)) + oiio_cmd.extend(additional_command_args) if target_colorspace: oiio_cmd.extend(["--colorconvert", @@ -1132,3 +1132,4 @@ def split_cmd_args(in_args): continue splitted_args.extend(arg.split(" ")) return splitted_args + diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py index e80141fc4a..0f6dacba18 100644 --- a/openpype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -22,7 +22,6 @@ from openpype.lib.transcoding import ( should_convert_for_ffmpeg, convert_input_paths_for_ffmpeg, get_transcode_temp_directory, - split_cmd_args ) @@ -671,7 +670,7 @@ class ExtractReview(pyblish.api.InstancePlugin): res_filters = self.rescaling_filters(temp_data, output_def, new_repre) ffmpeg_video_filters.extend(res_filters) - ffmpeg_input_args = split_cmd_args(ffmpeg_input_args) + ffmpeg_input_args = self.split_ffmpeg_args(ffmpeg_input_args) lut_filters = self.lut_filters(new_repre, instance, ffmpeg_input_args) ffmpeg_video_filters.extend(lut_filters) @@ -724,6 +723,28 @@ class ExtractReview(pyblish.api.InstancePlugin): ffmpeg_output_args ) + def split_ffmpeg_args(self, in_args): + """Makes sure all entered arguments are separated in individual items. + + Split each argument string with " -" to identify if string contains + one or more arguments. + """ + splitted_args = [] + for arg in in_args: + sub_args = arg.split(" -") + if len(sub_args) == 1: + if arg and arg not in splitted_args: + splitted_args.append(arg) + continue + + for idx, arg in enumerate(sub_args): + if idx != 0: + arg = "-" + arg + + if arg and arg not in splitted_args: + splitted_args.append(arg) + return splitted_args + def ffmpeg_full_args( self, input_args, video_filters, audio_filters, output_args ): @@ -743,7 +764,7 @@ class ExtractReview(pyblish.api.InstancePlugin): Returns: list: Containing all arguments ready to run in subprocess. """ - output_args = split_cmd_args(output_args) + output_args = self.split_ffmpeg_args(output_args) video_args_dentifiers = ["-vf", "-filter:v"] audio_args_dentifiers = ["-af", "-filter:a"]