From edc10cd85f232d132266b00dae6392ab33945b3f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 11:57:51 +0100 Subject: [PATCH 001/239] 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/239] 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/239] 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/239] 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/239] 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/239] 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/239] 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/239] 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/239] 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/239] 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/239] 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/239] 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/239] 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 58808104280eba59fed182af0849a66e366450f3 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 16 Jan 2023 13:44:57 +0100 Subject: [PATCH 014/239] OP-4642 - refactor weird assignment Co-authored-by: Roy Nieterau --- 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 62cf8f0dee..835b64b685 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -138,8 +138,7 @@ class ExtractColorTranscode(publish.Extractor): new_file_name = '{}.{}'.format(file_name, output_extension) renamed_files.append(new_file_name) - files_to_convert = renamed_files - return files_to_convert + return renamed_files def _get_profile(self, instance): """Returns profile if and how repre should be color transcoded.""" From b35ee739bcfcc158daa9ef2d9261f3bda328d4cc Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 11:57:51 +0100 Subject: [PATCH 015/239] 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 0e078dc157..474e878cb6 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 687d6dbf2811db3b8c6f46ee10726b26c1120772 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 11:58:51 +0100 Subject: [PATCH 016/239] 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 e36cf8004706a7d70133d79ea5e0ddd1f208f4c3 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 12:05:57 +0100 Subject: [PATCH 017/239] 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 cb27e5a4d6b512dfa193b031ab266340975245f0 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 14:03:22 +0100 Subject: [PATCH 018/239] 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 7201c57ddc71cac47f6ea38fdbf7d6c1d2d03577 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 14:04:06 +0100 Subject: [PATCH 019/239] 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 dc796b71b4c50d2bb0240667cf9d0f19d85ad5dc Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 14:15:33 +0100 Subject: [PATCH 020/239] 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 50ff228070729e87dc0a846aa82461fdcc8b08b7 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 14:18:38 +0100 Subject: [PATCH 021/239] 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 7a162f9dba79e1c829b9a01338917033607ec074 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 18:15:44 +0100 Subject: [PATCH 022/239] 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 171af695c3ebfdb5a946af8897002e853f34af81 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 18:17:25 +0100 Subject: [PATCH 023/239] 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 c7b443519e220ca14dc32ee135c6dcc8085c7d88 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 18:19:08 +0100 Subject: [PATCH 024/239] 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 69c04bb01dc7bc220318a3d6f63e4ed568bddff2 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 18:23:01 +0100 Subject: [PATCH 025/239] 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 44e12b05b95db1f0b1a43ad506850b5578705942 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 18:46:14 +0100 Subject: [PATCH 026/239] 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 85fc41bd7427a395fbf1454b12a6ee6fea34e594 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 18:46:35 +0100 Subject: [PATCH 027/239] 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 028/239] 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 53470bb0333cd8662e3bf4433fa78898bb29eb19 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 18 Jan 2023 09:56:29 +0000 Subject: [PATCH 029/239] Initial draft --- openpype/hosts/maya/api/lib_renderproducts.py | 73 ++++++++++++++----- .../maya/plugins/publish/collect_render.py | 4 +- openpype/hosts/maya/startup/userSetup.py | 8 +- .../plugins/publish/submit_publish_job.py | 33 ++++++++- 4 files changed, 92 insertions(+), 26 deletions(-) diff --git a/openpype/hosts/maya/api/lib_renderproducts.py b/openpype/hosts/maya/api/lib_renderproducts.py index c54e3ab3e0..b76b441588 100644 --- a/openpype/hosts/maya/api/lib_renderproducts.py +++ b/openpype/hosts/maya/api/lib_renderproducts.py @@ -127,6 +127,7 @@ class RenderProduct(object): """ productName = attr.ib() ext = attr.ib() # extension + colorspace = attr.ib() # colorspace aov = attr.ib(default=None) # source aov driver = attr.ib(default=None) # source driver multipart = attr.ib(default=False) # multichannel file @@ -344,7 +345,6 @@ class ARenderProducts: separator = file_prefix[matches[0].end(1):matches[1].start(1)] return separator - def _get_layer_data(self): # type: () -> LayerMetadata # ______________________________________________ @@ -553,6 +553,9 @@ class RenderProductsArnold(ARenderProducts): ] for ai_driver in ai_drivers: + colorspace = self._get_colorspace( + ai_driver + ".colorManagement" + ) # todo: check aiAOVDriver.prefix as it could have # a custom path prefix set for this driver @@ -590,12 +593,15 @@ class RenderProductsArnold(ARenderProducts): global_aov = self._get_attr(aov, "globalAov") if global_aov: for camera in cameras: - product = RenderProduct(productName=name, - ext=ext, - aov=aov_name, - driver=ai_driver, - multipart=multipart, - camera=camera) + product = RenderProduct( + productName=name, + ext=ext, + aov=aov_name, + driver=ai_driver, + multipart=multipart, + camera=camera, + colorspace=colorspace + ) products.append(product) all_light_groups = self._get_attr(aov, "lightGroups") @@ -603,13 +609,16 @@ class RenderProductsArnold(ARenderProducts): # All light groups is enabled. A single multipart # Render Product for camera in cameras: - product = RenderProduct(productName=name + "_lgroups", - ext=ext, - aov=aov_name, - driver=ai_driver, - # Always multichannel output - multipart=True, - camera=camera) + product = RenderProduct( + productName=name + "_lgroups", + ext=ext, + aov=aov_name, + driver=ai_driver, + # Always multichannel output + multipart=True, + camera=camera, + colorspace=colorspace + ) products.append(product) else: value = self._get_attr(aov, "lightGroupsList") @@ -625,12 +634,28 @@ class RenderProductsArnold(ARenderProducts): aov=aov_name, driver=ai_driver, ext=ext, - camera=camera + camera=camera, + colorspace=colorspace ) products.append(product) return products + def _get_colorspace(self, attribute): + """Resolve colorspace from Arnold settings.""" + + def _view_transform(): + preferences = lib.get_color_management_preferences() + return preferences["view_transform"] + + resolved_values = { + "Raw": lambda: "Raw", + "Use View Transform": _view_transform, + # Default. Same as Maya Preferences. + "Use Output Transform": lib.get_color_management_output_transform + } + return resolved_values[self._get_attr(attribute)]() + def get_render_products(self): """Get all AOVs. @@ -659,11 +684,19 @@ class RenderProductsArnold(ARenderProducts): ] default_ext = self._get_attr("defaultRenderGlobals.imfPluginKey") - beauty_products = [RenderProduct( - productName="beauty", - ext=default_ext, - driver="defaultArnoldDriver", - camera=camera) for camera in cameras] + colorspace = self._get_colorspace( + "defaultArnoldDriver.colorManagement" + ) + beauty_products = [ + RenderProduct( + productName="beauty", + ext=default_ext, + driver="defaultArnoldDriver", + camera=camera, + colorspace=colorspace + ) for camera in cameras + ] + # AOVs > Legacy > Maya Render View > Mode aovs_enabled = bool( self._get_attr("defaultArnoldRenderOptions.aovMode") diff --git a/openpype/hosts/maya/plugins/publish/collect_render.py b/openpype/hosts/maya/plugins/publish/collect_render.py index b1ad3ca58e..2c89424381 100644 --- a/openpype/hosts/maya/plugins/publish/collect_render.py +++ b/openpype/hosts/maya/plugins/publish/collect_render.py @@ -42,7 +42,6 @@ Provides: import re import os import platform -import json from maya import cmds import maya.app.renderSetup.model.renderSetup as renderSetup @@ -318,6 +317,9 @@ class CollectMayaRender(pyblish.api.ContextPlugin): "aovSeparator": layer_render_products.layer_data.aov_separator, # noqa: E501 "renderSetupIncludeLights": render_instance.data.get( "renderSetupIncludeLights" + ), + "colorspaceConfig": ( + lib.get_color_management_preferences()["config"] ) } diff --git a/openpype/hosts/maya/startup/userSetup.py b/openpype/hosts/maya/startup/userSetup.py index 40cd51f2d8..cb5aa4a898 100644 --- a/openpype/hosts/maya/startup/userSetup.py +++ b/openpype/hosts/maya/startup/userSetup.py @@ -4,10 +4,16 @@ from openpype.pipeline import install_host from openpype.hosts.maya.api import MayaHost from maya import cmds +# MAYA_RESOURCES enviornment variable is referenced in default OCIO path but +# it's not part of the environment. Patching this so it works as expected. +if "MAYA_RESOURCES" not in os.environ: + os.environ["MAYA_RESOURCES"] = os.path.join( + os.environ["MAYA_LOCATION"], "resources" + ).replace("\\", "/") + host = MayaHost() install_host(host) - print("starting OpenPype usersetup") # build a shelf diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 7e39a644a2..8811fa5d34 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -427,7 +427,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): self.log.info( "Finished copying %i files" % len(resource_files)) - def _create_instances_for_aov(self, instance_data, exp_files): + def _create_instances_for_aov( + self, instance_data, exp_files, additional_data + ): """Create instance for each AOV found. This will create new instance for every aov it can detect in expected @@ -528,6 +530,14 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): else: files = os.path.basename(col) + # Copy render product "colorspace" data to representation. + colorspace = "" + products = additional_data["renderProducts"].layer_data.products + for product in products: + if product.productName == aov: + colorspace = product.colorspace + break + rep = { "name": ext, "ext": ext, @@ -537,7 +547,14 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): # If expectedFile are absolute, we need only filenames "stagingDir": staging, "fps": new_instance.get("fps"), - "tags": ["review"] if preview else [] + "tags": ["review"] if preview else [], + "colorspaceData": { + "colorspace": colorspace, + "configData": { + "path": additional_data["colorspaceConfig"], + "template": "" + } + } } # support conversion from tiled to scanline @@ -561,7 +578,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): self.log.debug("instances:{}".format(instances)) return instances - def _get_representations(self, instance, exp_files): + def _get_representations(self, instance, exp_files, additional_data): """Create representations for file sequences. This will return representations of expected files if they are not @@ -897,6 +914,11 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): self.log.info(data.get("expectedFiles")) + additional_data = { + "renderProducts": instance.data["renderProducts"], + "colorspaceConfig": instance.data["colorspaceConfig"] + } + if isinstance(data.get("expectedFiles")[0], dict): # we cannot attach AOVs to other subsets as we consider every # AOV subset of its own. @@ -911,12 +933,15 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): # there are multiple renderable cameras in scene) instances = self._create_instances_for_aov( instance_skeleton_data, - data.get("expectedFiles")) + data.get("expectedFiles"), + additional_data + ) self.log.info("got {} instance{}".format( len(instances), "s" if len(instances) > 1 else "")) else: + #Need to inject colorspace here. representations = self._get_representations( instance_skeleton_data, data.get("expectedFiles") From ab9b2d5970b458447e442d9d031bc3d8e76ddc7e Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 18 Jan 2023 15:13:12 +0000 Subject: [PATCH 030/239] Missing initial commit --- openpype/hosts/maya/api/lib.py | 45 ++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index dd5da275e8..95b4db1b42 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -5,6 +5,7 @@ import sys import platform import uuid import math +import re import json import logging @@ -3446,3 +3447,47 @@ def iter_visible_nodes_in_range(nodes, start, end): def get_attribute_input(attr): connections = cmds.listConnections(attr, plugs=True, destination=False) return connections[0] if connections else None + + +def get_color_management_preferences(): + """Get and resolve OCIO preferences.""" + data = { + # Is color management enabled. + "enabled": cmds.colorManagementPrefs( + query=True, cmEnabled=True + ), + "rendering_space": cmds.colorManagementPrefs( + query=True, renderingSpaceName=True + ), + "output_transform": cmds.colorManagementPrefs( + query=True, outputTransformName=True + ), + "output_transform_enabled": cmds.colorManagementPrefs( + query=True, outputTransformEnabled=True + ), + "view_transform": cmds.colorManagementPrefs( + query=True, viewTransformName=True + ) + } + + path = cmds.colorManagementPrefs( + query=True, configFilePath=True + ) + # Resolve environment variables in config path. "MAYA_RESOURCES" are in the + # path by default. + for group in re.search(r'(<.*>)', path).groups(): + path = path.replace( + group, os.environ[group[1:-1]] + ) + + data["config"] = path + + return data + + +def get_color_management_output_transform(): + preferences = get_color_management_preferences() + colorspace = preferences["rendering_space"] + if preferences["output_transform_enabled"]: + colorspace = preferences["output_transform"] + return colorspace From 3279634dacd18e6d38a29708596c822d276781c6 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 18 Jan 2023 17:57:28 +0000 Subject: [PATCH 031/239] Fix Environment variable substitution in path --- openpype/hosts/maya/api/lib.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 95b4db1b42..b4aa18af65 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -3473,12 +3473,14 @@ def get_color_management_preferences(): path = cmds.colorManagementPrefs( query=True, configFilePath=True ) + # Resolve environment variables in config path. "MAYA_RESOURCES" are in the # path by default. - for group in re.search(r'(<.*>)', path).groups(): - path = path.replace( - group, os.environ[group[1:-1]] - ) + def _subst_with_env_value(match): + key = match.group(1) + return os.environ.get(key, "") + + path = re.sub(r'<([^>]+)>', _subst_with_env_value, path) data["config"] = path From 7a7f87e861abf4aae7fb3a96f660d74fe4866329 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 19 Jan 2023 09:30:11 +0000 Subject: [PATCH 032/239] Improvement Cleaner way to resolve in path. --- openpype/hosts/maya/api/lib.py | 11 ++++------- openpype/hosts/maya/startup/userSetup.py | 6 ------ 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index b4aa18af65..0807db88dc 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -3474,13 +3474,10 @@ def get_color_management_preferences(): query=True, configFilePath=True ) - # Resolve environment variables in config path. "MAYA_RESOURCES" are in the - # path by default. - def _subst_with_env_value(match): - key = match.group(1) - return os.environ.get(key, "") - - path = re.sub(r'<([^>]+)>', _subst_with_env_value, path) + # The OCIO config supports a custom token. + maya_resources_token = "" + maya_resources_path = om.MGlobal.getAbsolutePathToResources() + path = path.replace(maya_resources_token, maya_resources_path) data["config"] = path diff --git a/openpype/hosts/maya/startup/userSetup.py b/openpype/hosts/maya/startup/userSetup.py index cb5aa4a898..1104421cd9 100644 --- a/openpype/hosts/maya/startup/userSetup.py +++ b/openpype/hosts/maya/startup/userSetup.py @@ -4,12 +4,6 @@ from openpype.pipeline import install_host from openpype.hosts.maya.api import MayaHost from maya import cmds -# MAYA_RESOURCES enviornment variable is referenced in default OCIO path but -# it's not part of the environment. Patching this so it works as expected. -if "MAYA_RESOURCES" not in os.environ: - os.environ["MAYA_RESOURCES"] = os.path.join( - os.environ["MAYA_LOCATION"], "resources" - ).replace("\\", "/") host = MayaHost() install_host(host) From c2d0bc8f39f69fc8f2cea36972242e946a6c9deb Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 19 Jan 2023 09:34:55 +0000 Subject: [PATCH 033/239] HOund --- openpype/hosts/maya/api/lib.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 0807db88dc..23f7319d4a 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -5,7 +5,6 @@ import sys import platform import uuid import math -import re import json import logging From a9a86d112093d6b4dd47f6edc5f9ac75ace3b13d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 26 Jan 2023 12:53:02 +0100 Subject: [PATCH 034/239] 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 035/239] 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 036/239] 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 037/239] 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 038/239] 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 039/239] 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 040/239] 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 041/239] 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 042/239] 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 043/239] 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 044/239] 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 045/239] 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 046/239] 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 047/239] 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 048/239] 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 049/239] 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 050/239] 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 3fa7610061298e4a3de96d176b74f5bd3ecca652 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 31 Jan 2023 18:35:50 +0000 Subject: [PATCH 051/239] Working version for Arnold. --- openpype/hosts/maya/api/lib.py | 13 +++++++++++-- openpype/hosts/maya/api/lib_renderproducts.py | 6 +++++- .../hosts/maya/plugins/publish/collect_render.py | 8 ++++---- .../deadline/plugins/publish/submit_publish_job.py | 10 +++++++--- 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index f869dadaad..b31ab2408b 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -14,7 +14,7 @@ from math import ceil from six import string_types from maya import cmds, mel -import maya.api.OpenMaya as om +from maya.api import OpenMaya from openpype.client import ( get_project, @@ -3402,13 +3402,22 @@ def get_color_management_preferences(): ) } + # Split view and display from view_transform. view_transform comes in + # format of "{view} ({display})". + display = data["view_transform"].split("(")[-1].replace(")", "") + data.update({ + "display": display, + "view": data["view_transform"].replace("({})".format(display), "")[:-1] + }) + + # Get config absolute path. path = cmds.colorManagementPrefs( query=True, configFilePath=True ) # The OCIO config supports a custom token. maya_resources_token = "" - maya_resources_path = om.MGlobal.getAbsolutePathToResources() + maya_resources_path = OpenMaya.MGlobal.getAbsolutePathToResources() path = path.replace(maya_resources_token, maya_resources_path) data["config"] = path diff --git a/openpype/hosts/maya/api/lib_renderproducts.py b/openpype/hosts/maya/api/lib_renderproducts.py index 0b585dc8cb..58ccbfd5a2 100644 --- a/openpype/hosts/maya/api/lib_renderproducts.py +++ b/openpype/hosts/maya/api/lib_renderproducts.py @@ -648,8 +648,12 @@ class RenderProductsArnold(ARenderProducts): preferences = lib.get_color_management_preferences() return preferences["view_transform"] + def _raw(): + preferences = lib.get_color_management_preferences() + return preferences["rendering_space"] + resolved_values = { - "Raw": lambda: "Raw", + "Raw": _raw, "Use View Transform": _view_transform, # Default. Same as Maya Preferences. "Use Output Transform": lib.get_color_management_output_transform diff --git a/openpype/hosts/maya/plugins/publish/collect_render.py b/openpype/hosts/maya/plugins/publish/collect_render.py index 2c89424381..d0164f30a8 100644 --- a/openpype/hosts/maya/plugins/publish/collect_render.py +++ b/openpype/hosts/maya/plugins/publish/collect_render.py @@ -264,7 +264,7 @@ class CollectMayaRender(pyblish.api.ContextPlugin): self.log.info(full_exp_files) self.log.info("collecting layer: {}".format(layer_name)) # Get layer specific settings, might be overrides - + colorspace_data = lib.get_color_management_preferences() data = { "subset": expected_layer_name, "attachTo": attach_to, @@ -318,9 +318,9 @@ class CollectMayaRender(pyblish.api.ContextPlugin): "renderSetupIncludeLights": render_instance.data.get( "renderSetupIncludeLights" ), - "colorspaceConfig": ( - lib.get_color_management_preferences()["config"] - ) + "colorspaceConfig": colorspace_data["config"], + "colorspaceDisplay": colorspace_data["display"], + "colorspaceView": colorspace_data["view"] } # Collect Deadline url if Deadline module is enabled diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 8811fa5d34..02aa1043d1 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -550,10 +550,12 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "tags": ["review"] if preview else [], "colorspaceData": { "colorspace": colorspace, - "configData": { + "config": { "path": additional_data["colorspaceConfig"], "template": "" - } + }, + "display": additional_data["display"], + "view": additional_data["view"] } } @@ -916,7 +918,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): additional_data = { "renderProducts": instance.data["renderProducts"], - "colorspaceConfig": instance.data["colorspaceConfig"] + "colorspaceConfig": instance.data["colorspaceConfig"], + "display": instance.data["colorspaceDisplay"], + "view": instance.data["colorspaceView"] } if isinstance(data.get("expectedFiles")[0], dict): From 9f17a4803f4f6fdfb8858a841e20dbe283b119f2 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 1 Feb 2023 11:14:07 +0100 Subject: [PATCH 052/239] 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 053/239] 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 5c4856d72a302db1132b21f8fac87e751e045899 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 1 Feb 2023 12:05:38 +0000 Subject: [PATCH 054/239] BigRoy feedback --- openpype/hosts/maya/api/lib.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index b31ab2408b..d83f18bf30 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -5,6 +5,7 @@ import sys import platform import uuid import math +import re import json import logging @@ -3404,10 +3405,11 @@ def get_color_management_preferences(): # Split view and display from view_transform. view_transform comes in # format of "{view} ({display})". - display = data["view_transform"].split("(")[-1].replace(")", "") + regex = re.compile(r"^(?P.+) \((?P.+)\)$") + match = regex.match(data["view_transform"]) data.update({ - "display": display, - "view": data["view_transform"].replace("({})".format(display), "")[:-1] + "display": match.group("display"), + "view": match.group("view") }) # Get config absolute path. From 89d7edd8dfe0e6917c18870c92628659a8635541 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 1 Feb 2023 12:06:26 +0000 Subject: [PATCH 055/239] Support for Maya Hardware --- openpype/hosts/maya/api/lib_renderproducts.py | 7 ++++++- openpype/hosts/maya/api/lib_rendersettings.py | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/api/lib_renderproducts.py b/openpype/hosts/maya/api/lib_renderproducts.py index 58ccbfd5a2..6a607e7ff3 100644 --- a/openpype/hosts/maya/api/lib_renderproducts.py +++ b/openpype/hosts/maya/api/lib_renderproducts.py @@ -1354,7 +1354,12 @@ class RenderProductsMayaHardware(ARenderProducts): products = [] for cam in self.get_renderable_cameras(): - product = RenderProduct(productName="beauty", ext=ext, camera=cam) + product = RenderProduct( + productName="beauty", + ext=ext, + camera=cam, + colorspace=lib.get_color_management_output_transform() + ) products.append(product) return products diff --git a/openpype/hosts/maya/api/lib_rendersettings.py b/openpype/hosts/maya/api/lib_rendersettings.py index 6190a49401..4710b10128 100644 --- a/openpype/hosts/maya/api/lib_rendersettings.py +++ b/openpype/hosts/maya/api/lib_rendersettings.py @@ -23,7 +23,8 @@ class RenderSettings(object): 'vray': 'vraySettings.fileNamePrefix', 'arnold': 'defaultRenderGlobals.imageFilePrefix', 'renderman': 'rmanGlobals.imageFileFormat', - 'redshift': 'defaultRenderGlobals.imageFilePrefix' + 'redshift': 'defaultRenderGlobals.imageFilePrefix', + 'mayahardware2': 'defaultRenderGlobals.imageFilePrefix' } _image_prefixes = { From 21d275d46ae2e7d8c1cc9fae67cd8512da46dec1 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 1 Feb 2023 12:27:48 +0000 Subject: [PATCH 056/239] Support for Vray --- openpype/hosts/maya/api/lib_renderproducts.py | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/maya/api/lib_renderproducts.py b/openpype/hosts/maya/api/lib_renderproducts.py index 6a607e7ff3..38415c2ae2 100644 --- a/openpype/hosts/maya/api/lib_renderproducts.py +++ b/openpype/hosts/maya/api/lib_renderproducts.py @@ -841,9 +841,13 @@ class RenderProductsVray(ARenderProducts): if not dont_save_rgb: for camera in cameras: products.append( - RenderProduct(productName="", - ext=default_ext, - camera=camera)) + RenderProduct( + productName="", + ext=default_ext, + camera=camera, + colorspace=lib.get_color_management_output_transform() + ) + ) # separate alpha file separate_alpha = self._get_attr("vraySettings.separateAlpha") @@ -895,10 +899,13 @@ class RenderProductsVray(ARenderProducts): aov_name = self._get_vray_aov_name(aov) for camera in cameras: - product = RenderProduct(productName=aov_name, - ext=default_ext, - aov=aov, - camera=camera) + product = RenderProduct( + productName=aov_name, + ext=default_ext, + aov=aov, + camera=camera, + colorspace=lib.get_color_management_output_transform() + ) products.append(product) return products From e7fbe105fdd312ecdf3560e1db5859dbbda39076 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 1 Feb 2023 16:11:45 +0100 Subject: [PATCH 057/239] 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 058/239] 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 059/239] 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 060/239] 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 061/239] 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 062/239] 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 d39dd9ce1de40544cbdf612c6f9e5ff00b126129 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 13 Feb 2023 17:59:40 +0000 Subject: [PATCH 063/239] Correct colorspace when using "View Transform" --- openpype/hosts/maya/api/lib_renderproducts.py | 7 +- openpype/pipeline/colorspace.py | 99 +++++++++---------- openpype/scripts/ocio_wrapper.py | 20 +++- 3 files changed, 70 insertions(+), 56 deletions(-) diff --git a/openpype/hosts/maya/api/lib_renderproducts.py b/openpype/hosts/maya/api/lib_renderproducts.py index 38415c2ae2..4930143fb1 100644 --- a/openpype/hosts/maya/api/lib_renderproducts.py +++ b/openpype/hosts/maya/api/lib_renderproducts.py @@ -46,6 +46,7 @@ import attr from . import lib from . import lib_rendersetup +from openpype.pipeline.colorspace import get_ocio_config_views from maya import cmds, mel @@ -646,7 +647,11 @@ class RenderProductsArnold(ARenderProducts): def _view_transform(): preferences = lib.get_color_management_preferences() - return preferences["view_transform"] + views_data = get_ocio_config_views(preferences["config"]) + view_data = views_data[ + "{}/{}".format(preferences["display"], preferences["view"]) + ] + return view_data["colorspace"] def _raw(): preferences = lib.get_color_management_preferences() diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index cb37b2c4ae..6f68bdc5bf 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -198,41 +198,19 @@ def validate_imageio_colorspace_in_config(config_path, colorspace_name): return True -def get_ocio_config_colorspaces(config_path): - """Get all colorspace data - - Wrapper function for aggregating all names and its families. - Families can be used for building menu and submenus in gui. - - Args: - config_path (str): path leading to config.ocio file - - Returns: - dict: colorspace and family in couple - """ - if sys.version_info[0] == 2: - return get_colorspace_data_subprocess(config_path) - - from ..scripts.ocio_wrapper import _get_colorspace_data - return _get_colorspace_data(config_path) - - -def get_colorspace_data_subprocess(config_path): - """Get colorspace data via subprocess +def get_data_subprocess(config_path, data_type): + """Get data via subprocess Wrapper for Python 2 hosts. Args: config_path (str): path leading to config.ocio file - - Returns: - dict: colorspace and family in couple """ with _make_temp_json_file() as tmp_json_path: # Prepare subprocess arguments args = [ "run", get_ocio_config_script_path(), - "config", "get_colorspace", + "config", data_type, "--in_path", config_path, "--out_path", tmp_json_path @@ -251,6 +229,47 @@ def get_colorspace_data_subprocess(config_path): return json.loads(return_json_data) +def compatible_python(): + """Only 3.9 or higher can directly use PyOpenColorIO in ocio_wrapper""" + compatible = False + if sys.version[0] == 3 and sys.version[1] >= 9: + compatible = True + return compatible + + +def get_ocio_config_colorspaces(config_path): + """Get all colorspace data + + Wrapper function for aggregating all names and its families. + Families can be used for building menu and submenus in gui. + + Args: + config_path (str): path leading to config.ocio file + + Returns: + dict: colorspace and family in couple + """ + if compatible_python(): + from ..scripts.ocio_wrapper import _get_colorspace_data + return _get_colorspace_data(config_path) + else: + return get_colorspace_data_subprocess(config_path) + + +def get_colorspace_data_subprocess(config_path): + """Get colorspace data via subprocess + + Wrapper for Python 2 hosts. + + Args: + config_path (str): path leading to config.ocio file + + Returns: + dict: colorspace and family in couple + """ + return get_data_subprocess(config_path, "get_colorspace") + + def get_ocio_config_views(config_path): """Get all viewer data @@ -263,12 +282,12 @@ def get_ocio_config_views(config_path): Returns: dict: `display/viewer` and viewer data """ - if sys.version_info[0] == 2: + if compatible_python(): + from ..scripts.ocio_wrapper import _get_views_data + return _get_views_data(config_path) + else: return get_views_data_subprocess(config_path) - from ..scripts.ocio_wrapper import _get_views_data - return _get_views_data(config_path) - def get_views_data_subprocess(config_path): """Get viewers data via subprocess @@ -281,27 +300,7 @@ def get_views_data_subprocess(config_path): Returns: dict: `display/viewer` and viewer data """ - with _make_temp_json_file() as tmp_json_path: - # Prepare subprocess arguments - args = [ - "run", get_ocio_config_script_path(), - "config", "get_views", - "--in_path", config_path, - "--out_path", tmp_json_path - - ] - log.info("Executing: {}".format(" ".join(args))) - - process_kwargs = { - "logger": log, - "env": {} - } - - run_openpype_process(*args, **process_kwargs) - - # return all colorspaces - return_json_data = open(tmp_json_path).read() - return json.loads(return_json_data) + return get_data_subprocess(config_path, "get_views") def get_imageio_config( diff --git a/openpype/scripts/ocio_wrapper.py b/openpype/scripts/ocio_wrapper.py index 0685b2e52a..aa9e11c841 100644 --- a/openpype/scripts/ocio_wrapper.py +++ b/openpype/scripts/ocio_wrapper.py @@ -157,11 +157,21 @@ def _get_views_data(config_path): config = ocio.Config().CreateFromFile(str(config_path)) - return { - f"{d}/{v}": {"display": d, "view": v} - for d in config.getDisplays() - for v in config.getViews(d) - } + data = {} + for display in config.getDisplays(): + for view in config.getViews(display): + colorspace = config.getDisplayViewColorSpaceName(display, view) + # Special token. See https://opencolorio.readthedocs.io/en/latest/guides/authoring/authoring.html#shared-views + if colorspace == "": + colorspace = display + + data[f"{display}/{view}"] = { + "display": display, + "view": view, + "colorspace": colorspace + } + + return data if __name__ == '__main__': From e21323c78841902bf66f8670b1890a7cde11773c Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 13 Feb 2023 18:01:38 +0000 Subject: [PATCH 064/239] Hound --- openpype/scripts/ocio_wrapper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/scripts/ocio_wrapper.py b/openpype/scripts/ocio_wrapper.py index aa9e11c841..16558642c6 100644 --- a/openpype/scripts/ocio_wrapper.py +++ b/openpype/scripts/ocio_wrapper.py @@ -161,7 +161,7 @@ def _get_views_data(config_path): for display in config.getDisplays(): for view in config.getViews(display): colorspace = config.getDisplayViewColorSpaceName(display, view) - # Special token. See https://opencolorio.readthedocs.io/en/latest/guides/authoring/authoring.html#shared-views + # Special token. See https://opencolorio.readthedocs.io/en/latest/guides/authoring/authoring.html#shared-views # noqa if colorspace == "": colorspace = display From 714454d7300cf2e067785c888dadddb415f88baf Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 14 Feb 2023 14:54:25 +0100 Subject: [PATCH 065/239] 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 066/239] 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 2621016015b0b2c3e36f36d4c3853851d5154256 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 14 Feb 2023 16:27:43 +0000 Subject: [PATCH 067/239] Template for config. --- .../modules/deadline/plugins/publish/submit_publish_job.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 02aa1043d1..89a4e5d377 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -552,7 +552,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "colorspace": colorspace, "config": { "path": additional_data["colorspaceConfig"], - "template": "" + "template": additional_data["colorspaceTemplate"] }, "display": additional_data["display"], "view": additional_data["view"] @@ -920,7 +920,10 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "renderProducts": instance.data["renderProducts"], "colorspaceConfig": instance.data["colorspaceConfig"], "display": instance.data["colorspaceDisplay"], - "view": instance.data["colorspaceView"] + "view": instance.data["colorspaceView"], + "colorspaceTemplate": instance.data["colorspaceConfig"].replace( + context.data["anatomy"].roots["work"], "{root[work]}" + ) } if isinstance(data.get("expectedFiles")[0], dict): From 0abcbc152390c00aa596b80ccc40c6a64c4861c4 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 15 Feb 2023 16:04:59 +0100 Subject: [PATCH 068/239] 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 069/239] 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 070/239] 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 071/239] 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 7b6fb46cd191ab39ae43b9664365a0422203ad58 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 20 Feb 2023 16:05:24 +0000 Subject: [PATCH 072/239] Fix colorspaceTemplate --- openpype/modules/deadline/plugins/publish/submit_publish_job.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 89a4e5d377..bd5933926c 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -922,7 +922,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "display": instance.data["colorspaceDisplay"], "view": instance.data["colorspaceView"], "colorspaceTemplate": instance.data["colorspaceConfig"].replace( - context.data["anatomy"].roots["work"], "{root[work]}" + str(context.data["anatomy"].roots["work"]), "{root[work]}" ) } From 78933eb56259045a27c1c56f06a31dfb39f38e37 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 21 Feb 2023 10:44:10 +0100 Subject: [PATCH 073/239] 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 074/239] 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 075/239] 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 076/239] 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 077/239] 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 078/239] 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 079/239] 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 080/239] 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 081/239] 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 082/239] 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 083/239] 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 084/239] 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 085/239] 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 086/239] 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 087/239] 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 8f7b6c6a0e9f8b708e618fb5fec9277305786859 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 22 Feb 2023 06:58:20 +0000 Subject: [PATCH 088/239] Hound. --- openpype/modules/deadline/plugins/publish/submit_publish_job.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 425c236b7f..c651782392 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -962,7 +962,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "s" if len(instances) > 1 else "")) else: - #Need to inject colorspace here. representations = self._get_representations( instance_skeleton_data, data.get("expectedFiles") From 2563d302fa0f9b1140b226e7e992b70bae403430 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 22 Feb 2023 12:22:53 +0100 Subject: [PATCH 089/239] 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 090/239] 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 091/239] 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 092/239] 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 a07e76ebb56d9dc325f11b40a5b336f4125725e0 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 23 Feb 2023 11:05:26 +0100 Subject: [PATCH 093/239] 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 2921579164112453343a580f8f358c124eb9c1d7 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 11:57:51 +0100 Subject: [PATCH 094/239] 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 095/239] 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 096/239] 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 097/239] 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 098/239] 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 099/239] 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 100/239] 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 101/239] 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 102/239] 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 103/239] 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 104/239] 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 105/239] 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 106/239] 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 107/239] 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 108/239] 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 109/239] 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 110/239] 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 111/239] 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 112/239] 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 113/239] 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 114/239] 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 115/239] 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 116/239] 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 117/239] 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 118/239] 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 119/239] 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 120/239] 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 121/239] 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 122/239] 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 123/239] 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 124/239] 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 125/239] 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 126/239] 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 127/239] 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 128/239] 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 129/239] 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 130/239] 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 131/239] 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 132/239] 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 133/239] 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 134/239] 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 135/239] 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 136/239] 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 137/239] 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 138/239] 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 139/239] 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 140/239] 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 141/239] 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 142/239] 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 zcmd43cUTnLw>H>_$dRZbasUC5C`dTutR#^vSwO%*hHkK%oDG18lA7E!IX4Xwn8K$NPF zm2^QMVp$OAQr#byfHS1V^FM+Ah+w)Z3ZTNS+l#=D%Qo_w@*q%gIQj7l65#h$=f}n{ z5QysA`9Gp&r(8?mCIu^^^;?!$N6SYzs7#)xFy6$VS3|FOCp6Y#YEfsmQEDK5C2rU_H#OT1o!xk<^8Ww zASTs2_jJ^B9gPlVr$6Q z>DWnLOFk*|im|ueJsgx(+*d*EcN|X;s~d&zO^y}%y3UyZlNPy#r38UenOeZWkJX0| zVi3qSSOP-?oSXXhbEHs45a^+F1P-`t><#`32-HLM8gkT}QJ>?Wo`IKUS=4xoSb#~5@N|KBnN8~tj`wR*t&c7IU4AX4`0os-`1&oE3OMR+ zE%~3!#DqKTcmb~%pkoCwt*~E>J?`OTFY^rP-0MF(Y%ZuOA8Ilc-d=l_bT;HwbQ=WH z&u+u-?aJbS5v6xR<4SnJVxv;7Thz63Y*I9tkhKk-${~S+nlX^h$W$0pMharFv!fpS zN+FKrL6X+5uBq4ScCSsp*;%o7BHjZs`&xh!!ho?GWpRU!{)Z3b_THKdk@}xb*2u8| zvOL~c=zRQIVx-u{ube17^_#7p2!=l4>6VA~&H5qjnk>EHDGupO^nO_-c&vV6?jQw) z-kxFmIb?U`d~He#xiOw;YNfm$TXG*#PX^JZ=0?W4&C9|!S}wm{NH8v8L#HZ)n>2j9 z6hZs$hf~|kx0$Fn1KGC4YO-O8+`<{nn%&FIlZhd0(VskEDqp^3maL7MpXu?!bcMWR zyz;s15-YW(%Ul=RGKR9Zq-lp`>QsUsc=tDLvI{IB4FVI`QpugJP#Zy=m{06gvE!&ObG&} zU)6sgmv_~`+;ixD5r#fP^$)?2CjJ4>F7u>Nl+~G+hH()lET16NyrA)_)g}HhG(4tu zT4Yr6{COpui_y^|d1cuXmUGIoq@$w!svCb)1}?HAp(hN=Dy`_EeW@}%z=Wi;PU|#p z#2DQWY4@MJ^;k=a<`4L1B`pYJxk9+F|C7*)M{mBZskL{4cZ)Ei9Fl-oMYh9}IcKsp zt<&onD45>V)NZu(XJo<(-@p8W;hJ#!|$E~=cid^{- z1QLofNn=m$%VkFWmdlF50rDeDFwB(ws!z#G1KZDAk5t$aEA*19Cjbl1*S)X;a6{bM zNAvo6KJyRI2il2+T8lE4Xx6+f>0S$4Wp@zlZr3MA&C{D5ArhMw?&C=ziU!%=GE>+; ze%FkuZwiMS9|ym-f4NjNU6b=2?3-Y=;FWU;tI2sMSbr%8%ja`>WhP~Hlz%uQ{!l%v z-Ets@!{uBp;&@;P?oIEjEY?QjnMw+#r|TR|l3yQ}CYFx8x&%LU%(&O8bUTk_o}%?u z6tn={Mal&)m~po~)Un<@!rd4($QR9?=jvzhb2(y-(dFt7_j~W#!a6i zV?Yqwe;@b$!!Z8|d8;-SCe)phJlc3QWL$R+cQP$LWY36_1l-D##-OD=)()$kp84#x%00=Iw$(!%kv&pwq;@VCObCNvAl zPM&#J&2}9x>}doRtoHj_g*k6)RtseIa|^>ERB;9k^tJml_=6jTRF0`IhcWK74g`8_ z_$KH{s5uO`w^$OA6Y;F~s7+D!i7@8YpXn4voPxW;-AM!)k^xOTTiflzF$+D=Lq4x|@REKqY&7C*SNm23|7{-;{^-LvHz#q_Gbyn=t%Ah9 zo~C>KsslW0&4teeh?PPqbrw_~Y0O7pC*Jcb@U$irUQ%o+=S$9c5=zrBes5AHF9N9{ zMKiUkJTbK-n9#AX$KKB>Rr|E6#Is;T7Ev3au>W~V(^T#4JGHd^yY`;&>vj*kT)b^X z#rwl=8{~AaJoPgQBu(2!@VQvFQN6$nuHPzWkByF(73FZP$p!_C?L^}ST-znbxdxQS zlBNfDIqRUgP#A4-+p79o?E4Yq2<5ngM%2e?@)%f%T4Ijtjj04g@^;V$zgfb#s*P?o zHtp~)qvj7|ak=a@be_SyE{^@Jl^N2Mnw}wa>I^2N{nUzo8BDfje-wRFBR?1xEY*Sd zG^h>rDM%ZXONM9O81bzgWajNLc#f~EQNn$VyeF=-B{4rf-E{c3K)b+0ixV5|$aYnR zCk6IHb6j~Vu2JUd8cvaAiP6E*`^~!;hV0-P)|&RrVYHn=2{tkz`CjX2=+~(&DOJX7 z_*ILU6UE*<#iBZ|B~7`zMMmn3{EZnOJ`~b`O=xw8K3| zS{e%Vq?(_r8_&s+*crpTr=G6Mfcr18vV-J%5^$=K5TAHizmTzCmMi>&R$bU ze;mzGW42_DH@1vD64k-chW#T$S${ zpEitt;@bCZ`nGO0N`GRCj0v~%r7|No$$83VqK|q+XjC?ukGKj*21q&7|*bVyOT=W(ejO2Tp`0M4abyrOjYym*Y7d;#JTYSv% zy8+qpC@i0eWV^rQz{`#=w__!DZgeLAU=}1#14C3rTwWU%X~uVgVOIqH0;Ps9_@~?i z9K`kiBJ%x*v)Co%bx=zi+L&z?aZ!$BcdVBfy%eMV88OpC^c6p73elU4itsZ~;h5Yg z^Q}7G|6*D%J5G_hxNq2BW^}5ya`?ZRSXcYZ4Y^ z-Y<(>AQrQi$n~Qfw_v6_sAy|>g-&@dSa1)xT}ykUGo!6<$WimCn2>;r5%mncnV8te zvzimCdc{T;(p)NStMqM=HC>fcKr@VS+!k?OMECFx>cwR>wPSl373*ex4&vt&kTf+5FEvet4rR6$zr&7GLaIUCsAbv!DxHDSnWOt1lDbx#AMMi z)1-9_s=pS{GVD+j$XyH6G7RSt{IuSeokb4IgEmt0blCKNU~q-}~+pBFqf zK|jwa;g|@!>1Yscx_!QiYTuW^*`Ly9Y!K2}-kSP8R4|)&WE5jYUm} zQx}Q0*$mwN&~5;m!sS)v=7oH0by9{>C3IzWge@QL=_yju4VF<^e}M~?YNohJJ(*aG ze>LqqpX7bHPcbdCDA06GTtLDQS&^@4*sbZeP6RT^n!v}JOVr|TiAHL!#faXKph@F* zRncxC5vqMDsrdNp&F!Aew>dmYNOqd-H zJv2Cw)m1xs^G1^D7-AeP{oa1zN_3^5bAu_SwL`wMtbwiB;ehI>D#1B0DF?R`!z_L>NVgKzx0s zkmUq#<$JLc-3s2W+aKv7#NQzSf*>BwfWYZ%4qX{5io$10sT$@3=c*Y1`wiW3E$si6 zwvW$VlU$Mx=+b-$0uc&{=7H21wDaLSlI)@S2hyD9(TdVg#5UO3*K>(`k`JIN#`0*f zok!`tE5j*nY0H1a`@FL6ua8zD7$2v#yQu8Bk`;L_x>e{EEl$=<@2MBhy(T(xiHu_)>kY^kif^tkY{Qs!{& z%FJx7gx#!}X2QkF7*4USR2+hnWM3vo^f2rJ?fIi4Du2o{I1HrKa|1{NT0?ek&)goY$UDY`^LH%)gi3FRvF4NAER3-J9se z`2~6_5U=JaB4xTf8BJ9RmiA3U$o3Z8<`#-QyEBgT+B1_z<{( z1aVoeAUIs|L~_0%v(6tH4V6sDZ~wH&hZn56`Q5cft3+>CNMo(6KfY!EO5X z$29~g4>zH2Iy&c-F*5!QJ|UB`KcP~QYi^(|P|dyg^ql@1UVkX>u%mV`9#}7!`AI{-hI@Rv|+=23e{UOveqm z1S0P7hxE^vEUF$9$#pK~yw;Tb7Awcq_O4^Dr#?N`FG>$f&w+oke?O*{8Q&`^9Q<^w zsd|JP8#Df!lD1P$oMM%CovdbeQ(H zZqb>%YVH4ZDdWm#>8oP;16DuZo~iJymCM7tp2%`(R^>Uyp_&O}sY{tgqqcD04^UwlK7mPhW~iuD027A}yS%GWc6(oE%z)3v40C=Y1guZxg~F?c|@L_>Mg ziA+Rae||J?ERLHESs|GdI(x$^-)^eSOTx_0rKsE@`&p;hSl~~tyXvCaJh-_kKPmXv z-Y;%Vw~5N>-k=hFKP08CD$>aSYhjX3)yev~Ffm)Z-+%87tD+K0)63QdrLUDei)p*f zD_dN~;w_+X;?cQEXQmqd=rEbuJ$a~G@(DI${*!mB^e zct0?j*jz0NlSP&JiLYa7$QpW)ZDz;}(DUa2_!HAgFvVr_i$P59Z3?6l6U@~E8m4MV zL90VSc*3OYJ#Dir{Gib8S(~c`rqV5)zh)nlIzASOtI8?VUV8LMz|N`S^{SGY$2!r& zYD$N5=J-#se|J~^f%X-Q$6NdRTZdBo`sQ9L@+^haB60t4&a>8d_)Pn1Youe#5&F27 z@z?7spt2`NP{bkg_UO?xezkvSzgCn3vx{k0jKn1kXJy3BirM4<^hiLLN##U^>`~}g z%xnL_{Cik*y;RLC5ECcMtXHHp6Tr0Vtv4U79Bw^z z(iHx9D0vwpXc`qYHGK^(o^(p2Qt#QUvy!8u#WR9jR4pGSsuR zSrh8sJZ7b7FBRugdWUZ8weV-5?bh31l)-6e6b_cFYJ1;TkoG8`NvJ6FW;XJ@;oS_j zym^}4q}!;U5Nq;nix1J0JW^X-o1d=4kWOX9jo#BMeeNP``;(higrb(WrcDUuAAYw% z0rU{e(+ys99e96rKQa25k|t_}Iwa3UPSReu1Cstc6E>6T ztDsz+T`V8vH9Jq;Ny#Iw7=tuM=@O0?Yt9n|eT}wF&F~n=;&-fzhpe)pyw;gPNN3f10CUkWrJQWD@$w@4|y<%6qTkB25Y!u>6A6JX!bi z!6~a3n(8+AG2Cz6D&$z~xBcAo>1ClTQso{JS1`U`lxmGK?G)KxQujO-ajFzd$TuGU zi*CFpEG}MqOzCwvc3h3whIe7>N%tlvO4YdlPaFj`kHZr~a@pv2onq!Gk$fHg7PPK0 zkPn6NM`vw`_8BMI{(}?)ERgo^Y0&CL7|OViNc(C7td&ILngyxmI^x z=09dZch+**=FgZB;Z{beFY9aMk*g2zYL7eJ!;?461MwNdW{~YYDd%V?gOot{~Y zS<=MI%}_Elag=W0+OqCb3!Wx{h`}%0J#J|*cH$Eo)2vs4q7n^+mt=OtoUa&w z6-&uPDC&8W+Vb%o>^4fTwv>Dvi9qj$0XBro-E~>%_V{wrf;0(}=WX#%>)d&}0hvk` z&jA$^!e@2bdphG03H$o0WN<}UUszK|73Csc8mP1V z^{9=2fxe5TFWXpi zd0$~r7phFnPCbxIxz0Nw1`_0o#4XXH1LL!(L=?6sjcUN4=V5m-gAd2`gfS^kMeUM; z>WVyoTAB&+yr4$7_Fr)f6;o3_IU1HD@P6?f)qY2S7(mPV8 z>4+OpA$z6yZd;z}fNrKQQo#F1cQ5@o2q-|{6V-%U?kHQU5~s|wYDx|U>Lfu_GZW?^$P2VCK= zZX#KIwAR6RQ1$-I?1L`c1pxwG6a0y{=HPPuzO{z(8h>=ahRrkbUD$BsXeeY z4DNbp{gQ;XK%V#keZpvrjtV z$rI`Aj@YtkR~@s17iD;MA`o|4V!cx+zdFG_(Ko`GAG+jq3vG}&Sc2s<>ktIPq+c7# zUBat7`gvNPD$~}`d%>LDS;4mO#uY#1q#H%NM%iF5c6F_ZD1bQ8udSH4#>q9;@c{2{ zO-E)kF)*yWH{K5B;JONWc=er`lb*w$ByWnIL6!$p`3Dtq!&>Q!JC3Cdoy8LFwdx75 zWNK%KGMAFK`gx)Rrew=`GA*BRnP#e#gtj)<(1{Bg6rsi6-?ljsu{oW>-@>0vC%6|o z=tgNh-!qT<>Sam?zYI!!*FX@1DZ6J5s?AksA!qfNuH1OHC>fjJ-%Pg1*-0@w_b#)p zY6C%)B{(Wz6I~-qxn&c@+*-)BUPsoD=LN21)zEEC@Rz`lxYv6S%+|*W>iOT`hjsxu zwHQnSq)eZEAKG;kCQ!JzTcpUPJP*j7`0rfr`0IZNf8jCW=HbI$ZDpv?(!xN@R8e$_L# z7g*IxpeKz1Fa*Ddc)ZY{zC@^4-f~sd&p;>w5;dt>%w0vVde$5GjE>U?g6R&U#V5~5 z^)2n!zGsFpw&~EVbZkwmI77kc0s~ZbB62v3M4Ad@D-OjK zFg%C!@Bh5C&q8|N#5CWZ;k)&Ws}8R-u{W9s1f#)%4Jk!1gO8OT!6v_uIB3D=sY!Oj97X7}KAO}<>`@VA&$A`7>w!s8;NQR|tKdFIcv z%g(}+x%ey>Qrz~X7+)Rh8G{8qO_c)gW9>_z9j7N)@KM?R?#cJg{uJhqJUNqpP5CGf z1-yJCn!xk^u7c3}F=GKS4#c4vMT6Osz#-SNk|KXL45dCW0x2|t(R~M$YIP+VSMs2G zAnjmpkzKJeW-zZlg(NMRAG&Cnf9=yBt=AF)Xdgps|5$xn!@Y<4S@+178}a5XLo$vb zR~Cyt+6GGG{V)jmX-!51>T!u!?6W_R0#*i#toC)X2a#<*4O9v<8t|O#Z=24Blg*1) zSkv1)6*D#U6}Au(4IH1JEL!R&5bpZdXOil1)0SLkWooRYKn0z4jgcBtgH`xlxi!w9 zKF)KImUjybaC5rP9oOFMzy;-d7P+J$RQ)pU_4=MAM_R}`M^E##j%93i zv-yA6+ZK&UVkuOPo_ZuiCwh(jg({JK;jIFM0$ZNa@g1HU0#D8a)I9DB%c49-jaYj! z@vM~5l>%zkY9vJ_bVY&RWs|Wq!bh}!Di>2-2c^=*;JGvlZ!ZqE@KMiIlwl{^Wr%*pFblqKiX zJx|&KR((;YrkdeK&On`cCQfUXk3D|MDnA4+gQ5*pA?nG8boD!zp4dT31s)SFv4syZ zC0Ir$(VJ+dyOO|oxsn5nsUxn^Sv*(hgzdFU-`6!y!lnD_oIL)v)50!n6lnd7V{FdD zA&GWpu=v}X7|yPt9nm&lrcPp`HL9`Qx#9%03^Il;cZ}{k#f#4|8b!fIX~-Q?3U888%Q#6+XNBh z$3plum{F~d10A~yowt)tw%i20=h}PX#P?G<^r9vd0AigKJ)V>t{pMp~gtD$Y&!10DAD(5jUJ2JAM{H6!7EsN;+J-IA5t~KFg z`xn#K8wdwSR&Ewv6Q(tCY04+kgDa>anJ{m4cRq&3XE!s*(RnP{OHV^t_CyCKAs-K%f;m8qa$UwuvGpBvvwr%smTp->QCvY zgf0V`Wq@Gh&cfh_eAWi5c$cDHWxZ#$4vv?fA+ZmWGY>6nA*Z8g1_kuBfSCuK*X|SN zR@?0!*IpNR0;Ax>J@l8&SbYbK@Nu8fN<>9H#i){g8SlfkP&suz76bfYqCyaz3aOtCWrag z>~terzViw^lx=vZa*z-d99!fEU9Om;acGW}odyF`F$}NBYgpvrfm8}ClC3Fh#=Ff% zsG0rXg8K!&%s)sgUMOxV$0p;%z~dFuQ|0EYKoi5}AT3H<3$iMU8Bb;iPtoG9>9?AN zCN>jnA*S0SKjvuO11_ki;bZWyVI(d#%mOi_4(Xjvs3VoC^+1iWbF`K`dzQ{)%E4JU z3Y0;t47XNj>mYIaiH`lV;#gUr14GKod6dlnjY;rEa;99R?bRqp>#Otg)R;?>4wp`B*RuU^RggujdsT?=lvYf z`mMgYY?{^|>mM8PWAX;2rEiXjuI4DdJ*U&v5`%xz}~Lp))l`KpE{Zo<{@Q z0%m@5OWqIsR;waNxAe(s?Z3ONN_(9SE*ni8jG~4gT-$n>td?d(3F-+40)u=72*P{T zUwvnidgQGC<(YEBPno@@3UVXJZJE7{5K%@J58J!^{~dse52iUr*=cJyK>-sHIQvHs2NKt~$%0=We+|od^+)3? zyw4(E_0PxgTdc+~mZ8vh@QQ7Y&o@!ck*KC#@Wk|9#c_t18*}KdMQDNDi9Ps7ESvWtLYo#QgSKI z2mA9^`C_C4d};Yg##W$;zQp#@NW0?%@yQ5ky`9$Ft~5fPUA6;5c^q zYXbRjjgR3&9@w{=phKnKBT5xuvL4Xi@V&-$y71}riPnvlz9QlKE#RTxyw2f;Uz6{i zz5zsa6VKck)z;U<>vQr$g#@Egnpq;UDMbWw3B(%;Lrr--{3l~uop03i;jLqv2Hqwe zs8{YRQ*6T!+cCkaM)_Y7t;E3FdwK7g{bp(HCw&hiHpt4~JxD5k@S@6LvKKC3z87Vz z!qsg~pveaHP(w{STfpd^O}V_dx{1&io&O&UiD_uEt(KBCxlQwFg z!2-@HP`_8<={1K;$+9@mnaD;y@I4~#zY0nEF&#z&;^64lO)MI|}NWtem{6wI!i<=p*+&^TFpf7r| zyyg8+?L_zO=XD~Y0J+F>jr;aA~@2 zjDOzFdYi?p`y5fK|HY+G=u}@dXLr-#1!FXISp~}MqJDHet2A`}rZ1*SqQD5!|2-x9 zoe>%w|1<3CjbPy=nruS5Jab}1jxIN%1Xy*qHK~om&*56hwFPo7?CWk@_G@|a1eS#0 zGC)sgFIFvgoUp3LW$rOIH#{*kNqm=jHt%GySLob8_xcbIe5Fr+n0emOKG@*~>HmIJ zz~)0Pgz-OjaJau~(C#F6lqF3!PXr43p`tAyv5PLAYp=IX*#HcW4U^{Y@3%1Vz}x!t zIMCt>NCf{;Ma+Z4Q%jrwR}S6UX=fVA-qnG;!IU!s$%|_n_*F{0lSGnvjc6Vldb%-mTQ0#Rxhlcwcxu@KW=($tJ-I0;*ZJqUmDJJJOl+WN8rXj2W#F1;sPm3x=j4 zr^*~3TpxUwm$dgBKB2D!Z$9THTRj&)3`Gw;ZVY7zBFrpGV>)ZLM+No|0$=?5WV>_| z(qM?IkBW=6T_SYnPD>2sA?gl?O!j@?M4X0|&1koXi`V0&V8To!Qs}I)Hr{{=u#PM5 z@k5Z=3S)z^2}02;4lJKv!Hr})92laPk*Qkj0?`YFWyeHOEOj@tAv)@v1*~Hf0dWv* zLUH8|PL`vzvlLE$uWM(1I-YPJa#b2*$78oh7@w=ynaEv$)}j|;ZKYVWwIB%3`F?Tp zkHjP3j^uPbcsXDSSdg4-2VB*w?Ib%RxpKi-;9o3D5-MBdp9N8sd+rf1Z>;mtZq zylr5fGLGw=?2^@qFRuXk-yM$>5rML+P}9-~oVCFV2n`Qc>w~M`@4JkO^J=DjG}H-! zPe7rF!PV6~07vFvh!0L7kJ5MRfBHNtX+0(%UgT*t<@8qNT#mp^9Q2qNN$<6L#PFBS zT3-ylzK(mFrrGjuN(#WCMGZ_UKz7@z6^C2 zIuvv|2Q1)=^H~I(FQ(W|I1mef<$v3P%0IQ2@}v}o*oZO=T+sm%=?jIXJOLUxADaF> zj3-ue`>s(+O&1ZUmi1iUDNo1=KxjxDd9JEKrzeXOfjL}s9%lnYAj$y)2x6k@C*9Gl z5iXw#JX)CC#{D?$Cg1n5>ouzJ-v9+<-%G)|A9HIegbK#E#QTeW2c1uA|_Ce#<85;f5MFgmfz0g zKS|h>{Idmu8C&s#w}9rK7gSU|L+m|x?Q7$;Iiyh@p@juQr&jY`*E@@~&erWd?4)rgXk)rsGNY3Rin2z8Yn@giqug!Pr1Uh-vAsJ!P(} z(U|qyhg;=#HmD^F^pdiyMt+5krZh>L&otqHW63vy*3D5-8lz`wM{bbMPPyZkiiWKA9G0P zp^Qj&`|djpAQjG6mK%rNUu;GMXp4EC0ZP_6J6GW_Jke6Z`!BOWA!2YHwY`;inIZkE zk5O=$4~Y`HdcZ)pIx_2+uGQjW9itMJF-o16YoOsxZKNxPqYuHI7+`>EktF{AtZ{VsE2o9U)Ibawf5_x8^o@h!b*+i~=w5lXR?Fk_DY=Q~92?}Hn9 zIR6a?ubwg)#O!^1VBbS~A)pI$$K6iVP4xDM1C|K`LI}VQDe_f?di`}I?hIsK@fj^( zz~lT!#|E6YCI4UC*Z)Qh8G*3%A8zVDTJ`^=)h?dksQ@hoW(s<8<03HnpF0=;YCw7O z7FEY>gZqBRW3H?~ymQlQ@Z;*3`_3_HxTt2k)Tqe+&g>hR7=+9ZN69J8qT z)P~1O2$-%AFX6GGLgOGNx6z~Y?6l{-jI4*_BOol(lkDy?BQ>wz>}{nJ(Dw80uF{kq zmfZwf0dbet)^~Lv!|>1qodY8f^NN9nv**XL3yDA&pvPw!55ujUsDx>zSXB9x8$p1y z)AVD*U$gXDVY^5t)rsZ~;$BqI2*)ALQY=^^3iy1ViBeSLp~i-~thv z?$`SY(bF+YMi;g!|BVUf8S)R;&c|i0x-lx6AEozs1Z2_)GlSRPGNwRswAv)XS)qarjWP>QL6TZkgjl_1I3Hz^z?ti1U$jg!K>ZCs(b}#RKe;= zdO32!!%%HAwz-cw5C-)?)T-m`Euzk&PE)k8%g93echa(a83btkc}_tb?Vh6Fmht}P zA2w2$Jkc*b3UMEe?XapQVVN*Y37Kc`k7=pww0=e12ZE?N#|b&tjgjygh1F_pmK)}2 zZa_r5$ED#8gtA-T+s%cR7iLG>eHwvOO8z{RI+up^WSd58lM9nU6rS2(>R$de3Lh+% zZ@@DIpM7gygO?*ANE}u7-%Mr19vVW_oh+81+fViAdC63_?$}QcxaX5VO)Ix=U^SU< zj^7Y1-D<1e22`-G=-8QdR?Kc|%fShe1Ej;ohcPU3;IV^#%mYCru=_^&_e}BlSoTa? zutszCYj{G~Z72+J>KveIQhbb$U9Hi8kaL!PEhs>bxBoYD9vsLR%cu6ZwYCL_Ra7U0 z_PP71$tpFI76?jz3HTi#t78B}j!@-<^^{U}BmsLD*;^3N z4D9N^@}l1TfDVs9lekG@-KDr2jy}|XfOdYT3|GR}$4LTWf8W$F<6a0#&ARZ@X}7BP zS@=$>x4P%g3Xj##lcKxzywo5dv;dSmsOm9;w3u(*&!9ZVC^FA9O}K~~p|SieV>Vz= z*Tz7pH-2YK8O_NfZc}ZaflO5=MNrg&Y6~#U7=_X3TG_;M*zlWSCm)xs6|3E=dfRh% z5N>UH_o6lN%p?K%7u0~_s+d!X)Mg{^d8&EJd<{r@R<*-pi-ClZu8pH zH-ED*pa8#%_h$UC>Cg{kAUkBmOT}CjW|h#HpT#Vi1*5;dD$=|@P^8(~`B_X4uqYaH zGSco$9sr}jWl)t+i(xnR1}ZH^pukE?iuw92ZVj4a&+%;SMA^YQSUXu7HZ^wGIvGA=5(KPruy)<7 z=;iZh^qjB=AG%*R&Waj6>;$V&buyOVIn!22RkXYV<#qW4s#I;IHyb$wa!FK0(-}cP zUcejWT8f?=JzHN{s6!du#=Xzw|KW zLVI`B_|mZV)NamJc)DlPIel(3$n6W~qDyRipHDA&V|6kzeGn2#cja((v~6&ra6s7{@KoW5Ty z^i|QW^B7gPW{u$TN>??Fh|<#RN1rElruv26m9nDQL0$S?t>C-DULH}kr89Aleuqux zP(?H&cMnuXcM-=>{F$N-`X@$)){$EN;2o$sW{%LUkA6Pm{SfrG>lF0xS?B);D@4Iu z4%@O|C^G$NQ22z%PHS5|uq0B2s~HZPjWXSZjI?gjT7_z7GAvKS3``S6DZ)( zZ^xx3<4>rG8*${a>GFKt$=Tbv3{clVwv@Pw#Ql)NNBY9$eN))9IyXjG2~7ksw-A^qQS#lpwu@2GA#q;OI|Pibg}POA1Pm>$=R8csT?Gi zos#;?AWHK}_8Irn%*eYUkev~-M|LFrPqGrq;P2s7Cz2!8&qb-%)U6B3Oa-W|#@cQl zhr}@6^;}`L4~?lAmuh&mu>Q*VKJ_vfB&Y(QtKnS?2ZvUl&|s8SbH1-3xB77(eOORB!CIrHX*Z0^`y>N314SCX%2XCR^>sT<~DQ+(RrovSmpl@&RhNXb^n4Na^ zvuihriq12njOM>p*b2FPy%4VZ4ST@JH2zsmawdPOZN7q?R#{z7D$*l^Thf!eQXs!) zaVSN@q|>R|8n0Al*@S0(YuqL|Fjc#49v0Rt=w1*I%&B#<;Nv!^sm5LgY-v*v^Z=5&L zzIX&djb57-+ueV9cqIiEUUUn{Rlf=Xp{e7@8vGI{z*zA-Oh2!yT@-2lrw)u?Ll94^ zfv-q(+%y04%SCYkXeSmEpcnz9c1ln*FfB6Dlfi3?3uUQHf5jDf{ zV+{D#L>XO%ICVI)g#fbS%*VPlOhYuzv@=Dw6{ukS$t}$V;EC85{4G)>K;;`5b9vU} zz+6HZ_7J!&=@1xhKG@)r`b(?%sW28pQKz+y_I8wIJ+QwCd?B|24am9EF?I{Q@x$qn zJRhZ$eYvXIqYT_zFO*-B<442ffg5H6PeP&Q`Uk*#UjGtab~AImF{tALQQy`piLf^O z{$nz+mpE@wwDzWrkgerTrEppD#IH@+?HPHv;T{ESpBL%x#WJ+cia$_-K2%yP*8u9u z#jr~N+;a@$4#~klYJtMz=e8wZDEN|KUYm;e4=TUFWjB7P_-NL}20as88 z4(}ILoH@meEYtVwd!b+V>ZM9<2*cWjZBi{&xVP;5dR)J18Ch zZq(F?)LjN|x%|ZP!Bd_|2?N!ZY(?*iE(&reqFm2j&zj{OQaw07QAn6!4EXwl*NY1+ zy|RnF%p{@FqY90IyY9U5e_eQIlHF7hi&i{;ZI{O%8LO7fD~vnA6O}_xNstb$7T7=n z1yJQ#NqPvNLn_oXDwzFM=cfsc)4m7Bx*vxp)|4zz*XJY!px+VchybZ-%duItM}=$WKNvL1`+lD-3sK-&p3sY#e)wv)Uzw}5l5_Pt`^l&E4|+Pf4S4_P z3dpq;E==G)?>Nu4_K)2Mkqm9%AyRz~s~_H!pof#c2fhO{RmS@ONMa5DPbU2xNQk{o zv)X|ueZ9HJjP7@x9{rRWdHxlfheYSAY50%e@_$#H&zaXUi3cwK`g26`Md5tK_)++6 zgj~JLfqfA^LR0qzPy;o`qEoa0q`klif(ry*v-_{Y`r*p?M`6x^<;5DI;ylB5JghN# z`jE$F-F0vB>N)J*`~qtPb|eG59Cu}D0!K_%h|!2Y*9D>Ywd*QO1OxuBsG(p*a;bYi zU4hCR2t=}yB&Y(z0j&y#EXN(uG$w0_@kXXN}XOMnGfApKe<58CbqUvhcra z%#FyK+yF(fo^{@}l7yrI+D&LWk0;Fjbpir$Agn+06rcrgW(IgWcNpR#mq>YkkxLvc z{Zp!1^J1*>OCvi6Z@rNKpE4ULcCi}q96%>crQH@{>k6=4xBXBP9$ttO6g&MOmx4QA zNU_a-D?EQoBY4aHTjAMjP05`3C=a`EN!HuhXBTc-FDkdWTjaC*!iffydJtRa;o|dh z9Bzt11Kl<<=Sr3Q!SmLEat_qxEI>4rvw}5}Z!^2K+*y=sMSG`-sNAm*5 zLLiYLLd^cEzfW&{z0Yt6TD?XOX!kH~0q5Sr@`+XGD&XEwhStE@&aX4b@b@P$uR8+G zC%2%C`O1)fxv^^Jb@4yIAQM7DGoA0oiSphk&;xOQRt%`IXP>l>0y!pGs%xEC#V>b( z32+L)gaYNu^Qnvh4yLA*6;$>%scxn8??)Q8!*Cxobdmt9gjWSO5b~AUyqJ@Az1Ied@2ryMqZ=iTM z(1LXN%W-8Ul&4VD7OtnUGWu7kY8<=lV<$EvF1pFEc9p`y-Z`G_@8Fr=TjsGaug%z8 zKY1hO(Iq4qQ7Y~1j@3V3!CCrnS~!mrruq%4}1rWo6}0HG)QG!cSid#RfvZZ(mf}5UG(n*FGDSr zXaviu?F+DSLsN|?;&`%}oOxv>(p))u=H#SLwe{G03md_3`{qy&r1wBdEg?>({$>QP2ygdm$T1T<{ zv57DSnBrV?KYu0Cka#V-eJrN8XJul2dT;eWdFq;u7+VgK#z?zzv|2GXlLVUQ)i{N>jE`J^^dn4fQe$s)Wp-z=Qa zuRYb4|M3~I%$3dCFRVG*Ly`+8aftfmv$#A15nRl~;5rB56z=jMXba#$mXlgum4xh3 z-ZBGVvC8;KT(W6Tup+007fKxL;ham8aL=ng#dF>?r?;kY250_dL2_KtJUU;U-biEH z1}l(y@A2@wB8Pj1N$il+D?1~u)Fa2NyTVrA31DehS<#5Mp#%*u5^{qF?tcQWdi@VpRJaJbt!$E#MOGU z=|x2}10--(Dp{pxgECTfW%*=C8K_dxBS{WaUMZ(c67}6mvL28-)_ZjB&|Y|JW#H0F zDQ8(3fjABLuu zFkgG)^@bP2Gc#;ya)WmJ#CAM|~!zzXU{LKEP#RK}c>DV7K z$qx-^fjHx~dE|#mvo4jdcsRPry2ooy3N___<_3H4O@1Y^FTd$RM9kO;mJ5eMx6b)O z1VOp6Tv*Ch&_zS*i{5_C*1}uonF@)6xmGq2Uq8%E%mc|wnwAI-6HlJRu&t6x8mpp& zv}7t>El7jOOC|TBBg8BRrrgAIAFF;6bK^>aAa+g-_koKC5)u z*M|_tQPY&4+5q=-gtY*akQ?&Pg{du`d;rx`3piLm z>wNeC3EMr*CsS6Xf>+hOF$L4kLFqcQ7^aTy!-Po*nVA!<7z|*tLf(?wo&#)lDfD(s zL7-Z^W`f|;<}m;J9MHep1jPPQ9Rq?`?Zzgh=Q&Yr=}NA4D*h&_v_Lmhsc^8!y|w#O z^yPJjmjsa&H;GcXYK{a-GRlQVbT9t!gVQ?(7Heaz$orQN{q0X8FfEk8`0X!~G8tSL zl?=6**$dWGHX$C{-g6xvWNGfdZmSh*g;w`n?XlNRuF;GPucE}oaAhW%wUym3I?Q@= zx4A0}VSR3r1#${?&I{v4)%SPs^x{LmCq0N0V2o5In|G_2Nk9vYhF;X#=2X_7yD{!g zl6>vE;}u`V5|r;&7z8~#6Y*!O7JTjNg*@XNg$(li2Y*ZR%2+V~1@-)iZ4b5}fy+43 zE|lXaO)lN`#l?muoT?!cnK1;iAqVa2eLLfJ5o?fjW;(i7d%AvqeSI<|v=%zOd1P&X ztLu?HZ@hCO15lA5udwoHA)t`)45+00a`;d$p{Qc#W(y_c1_nRhD6e)x8c2-AG19!Q zq%07-gO%h_yT&Nxk74zH2nOW&Z{Q0+$tKYRk8RAEX#v)+wMSxBpHdTY3A0)=$caUC-G*@#Gy40#9Buc z(x=2z)m0ifMzsxgpP<^S%Ysuy@CPINFSQunHAL6L%l`0eJc78S13G z*V|7n|8AdK0n3hM0dRwz37y$+CT9R(P-F*=erHL1!RpMD^2uL;1v9=@dZw0V!)7Rc z58D)i{MyTOueVBPw#EF9GT&oeJso)+yTFD#r=r42LCzA<-z$s;@^YljKh$aIxhoXR zdOI=#QQEbDa&e|~Up?7Z zVYht_4P(=ihr8qTOrx!0PEaE$5HdnQh!6xq1PLV^&$-$Nfofhn1%S=X(8%u`jvC2! zWM3)gE6(ZZCzvOYhbM{Glupk0i zauivCR6C$qZCxL9IT()0(f$#pHGHeULsre%Q3urYZJK*m2nm}NS^r=VUaq2-M3r!wZu5AT5t*oTq-TMB)WMwg--udSd34+8UE4umJ0#*XPC zV$BIo>Tw10;c3&wYh46okZuH=3e$LE_{S7E@E}kCk8O&`XykZdg{-!EmKsI&u;jq1H zb<6Q>*qeI|O;}n~a+;qfXU$wGLSK@E`A$zN^+ys`Momh&*a~qR`(Z&PE`nWQgg5WA zU7Y%lCV_FM8I!`A)PlJ~)w)j?VXPCo9FF}OkYX20!mRI9JS=pAgJgiILEezID>?cd z$Q3$(gpvO32ea8%dRcbH36Hk@Mw=N5QD94GY#uK9N_$hrEi-2WFl`GKW~f;GUt=rU z5rQ-SNu6O`LkZcx9P>H}3nvu2{pffON(+X!N(m~Xd22=6M%I?eEXamQ`t zDoNPDWY0N*WSdK9296-4l`FAGVnvY-e?R|ydh<*$1N9JGlkwA=sn}UEpnUPTM zwVu)t{JtU0R4SL#GKmpk%a=$5*q_^N6HeFnY^Z&d_e`(ylE^tvUw93at$YxW?QR!LY;oRLi-jW%+o8*nHB&Rl`K;WE2~G^ErWrW9l3NLbomz8U7_K?6B4{14x}Qi*(%(pvzP znfT9Rd~+vY2_osh6E>NlEXStr;K}I-BH!dOz`go00f)X)gX{+hwFnAqdAMZhde3y# zQY-9t*pJ`&x)+Ku>{C#@qM=QuCO8s>4M_{luno|d1dc!_M3iDt)EN3U2w&fa5qaBC z#Ki%q)DiYrZO$VS>jL^b>hCoq?^_3(E)XlTZv_kL6}B8=WPR?tB&?6Mh;tF7K8A`L zZ{9*ROCndNW>`GYl-A{H^q`M?p3<_e3PYTLe%4|!&wxSvXCvZ90oZV+Sp7|~Xx_qI{x!bp{I&CPbsF+3hGH5x$LRHiu&9*)-j)%LASX!2I8kB50 z>S}FF_;=IHTyrpi2Xd?8YADO7)78ii9AI6$>J$URcX)-@lM~I-zi-a}_dkLR;M!2B zi<3~$NhiSYN$ku{n@n^BjpPf%P}KAq`8oHyikh`pIbM76%r{t~wEUEUASx~0aF$$3 zw#KN(J0kPZh>|ONF06JWg9e|Qh&Ka`P-;Y$$CG6pJQT%T=WKRbyXSO)*6!Rk&gub# zG6ZO5UL3sNdofFWV4HvF9>N;DjjdCeLrq=1){?F9GRj`m?(GW~y%ufEx66n;?gK%+ z*I9;Df@6;|3NZ|0Iou|g6mheCws{h+uj#{O+&!IP5z-D;=z>y|=nXui7Vep8ni_bZG&z~%1;dsIQM@%Wc@vJ;k^msY5gPG%2_x37B(zDv=!a;jB zvvf2bt`HgX4h9{&7%kVT7_HxYzOnnyy!Iqm#Hl@3?v;O-`ni0h(s!;`w3FrY(cf#J zKmu_~1h>NNPADY@TZhMJ5=OEd%rx57DI!9c7@eto>FEoOzS3gv59z4kWHq8{kTb6q z>Ub4yV~7XGE!rLrbT6Ti%MY&l$B1|*-rKZWD)aoQ7HM!-HwW9pt6uajto;_{DC+aG zP8^Gb{+G-N2TOPv=C2Cm3@?t(Qj;Ceu8k5aU{|QEMy7Ii{<(TY!27iE?~$sI1~4i6 zma9=uAJ?7ln>h9yRKR?LUiZkr@kEN9hiZ0?N_rK+b;eKQco?xGviCt-cK<}59!PGy z#|geI(vm-cu=1|ipZ=`a(Ddi9kHuM<$dRb^u(40Lgb2Ig@6Vk9vkDtA<$ugiNZ`9A z20M03_WZ&{n!J<(B5#xV2U)c_8`NBo-}*t#7=t@>b_elDp^rOO`=|zK=WTGB&4KHb z1|OPmXUn&2vHc!wB{E{JcIx&}&CI}<_rkAfDv)+QJIinew}3oTv{Y4eRh;1;W0E9| z@MZN@p_+axNx=m{`rxAtUw`gzfr{qafr$I=?J6YAX3Bo{hCk`#z4jsXyCf>(hRJkvN#uC)+3yXML$SV(E@CScXyE+ zUA32%D4k++)Xa~Fi9KqLzR?7FvhznINk#$xBf%hlHQa1EZ-)paNx~`ht#YSnF9=o* zOTu>r%^>P;(h0c8@*w z{*hVNFz3k0Dvu^;fOHt(fdh%W6I@2l4u0{t)z3ddx4vR=8CBrgwK2aM*BpQ|NnNS3DA?Iwmu_9v92!nSF}nP+?A+6zLYs&N7itrqhS@< ze#B%g5|O0R@)bcEfE5G@vEya&%Z}LsOT-*oI1YWSsutEz@T~sa(5afiOMM)BjXnb{ z3N;Us2MVHiS%~=jA2JKhovqDtD0RVrs@8#J$5gEmd&9i5IE96>a*&sS&dj_o^7P#w z<1(C6;y!aO^W&kRQzl1Bdsvj<3|}#IZ!4~Q;_HR2=%!CW_NbuP_yH4U$%>BU4@MJS z0)-@m<-9FC@<}vcvx$xq(LAD6KSk(H+nv=yxeec*6|c%3Z3aQyN!WW;=qZzW}99 z0T40UGNWBtpF9cNd%%3U^tmFcibKZ^0G(6n@#nNR&fZY9H6*Uss6^ggx(UyBnQb!U zbz*Wze5Pm%^Gy4@{50_tQ}S7;;CHe9$_T7q2$^e=)EwU|Is*Cthj$i(Y!sBg*h_;l7jBACY?(qlHiAoQ%Bz{`osaf!o|iXzTC1HFtUh)+LL{QjSM!Y=?tKf1Ez1b`2vrmaRR_fdJT{h4W?p}E5s zp#S{1%K=Z4a8YZ`+bUAPZT)eZOqC2M2@@Sm(EhtqS=D7a?&y&oxgi`gEA#PR=`B7%a z0lZm3u`6H?T6(Rj(`pPeki)|H8XeXQxrrN@Tt_dtRhL~ za8mV}ShZ2u;zY7jJcnu*FVCoK%pc-thbDmun6-J{tCo?PyVWgM ziOHJ1_QxFJ{}_ggrVtDA*l-*Q9z9TjQ;|7R*PW(hZHB%|2q!RwR1rn?X>8{%s_T*b zA^X~M(I9L2a6(Yksi^kgLj<=~>B8HBgvEQ!ULWe8%F$gCJUVvNJox@X0^u%bxH+iu zWwKEqMF3v-73hz&ehJALT~Iqf(bKsP_-7c(qih`6bGu1>r6s{cNiDA69q7w(j%CH| zLZLGJCA(K2-_R&oCo@ew*fdP{yg3>)gGge8{M=4;lkv6q)(3WW2v^Idt87P%i)v%M zBd!D&`FWZgcX4ZvOSt19^{a8mupwMZ-(}crRL3&{7?^r5<+D941cXVO(cX1bbLd$& zoklhJF7|KMcI5;?bJgB>m7J-> zw2}psn-hMS1{gu5gECBht|7n+Y(P?6O$-Om?p%Vi$DT*-R&O|IB(!Hfkrq{54hR= zen19CRSazYf`sk)DW?N@3G#JY6+mq{fGfPY;xw4B(Ie(k0^PU?6d)xP(^Y#$p6+~x z!5;;VnsEMI{@8cQ1`nFK1LN>Em@S3eV@U|0wl<9`l(3TPIDq9e#M}Pp$fc@3`0)0T z{{vARRf@Y+&xAP^i}hGqKBOeGk-Bp1DAoD=7cD}ls-^9x{_o2oFume<`^zjP?Ry~) z1Y{Dbb-On!c0sBRbEO@5TQRE*x)+k$2s$b$esX-YU9p&`cW)qA9`8W7RITGS2=Q*~ z6ekPHq!G4@k@<1`eGY7VxFGd({n^$hMR17s5aLsRBNO8|cdo+P!oqCJ_CrKewUw}a z{*~5MY=1x+gvO=d;3?lambq5kQJaOLq`|C&mjObc{`uo(0WIKeAR3^`5EK;ytASkb h5$6-oU)b1#ocI28)giYF^kjm-F01{Wp=|W<-vAa3;~fA1 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 143/239] 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 144/239] 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 145/239] 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 146/239] 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 147/239] 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 148/239] 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 149/239] 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 150/239] 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 151/239] 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 152/239] 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 153/239] 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 154/239] 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 155/239] 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 156/239] 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 157/239] 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 158/239] 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 159/239] 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 160/239] 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 161/239] 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 162/239] 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 163/239] 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 164/239] 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 165/239] 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 166/239] 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 167/239] 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 168/239] 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 169/239] 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 170/239] 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 171/239] 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 172/239] 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 173/239] 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 174/239] 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 175/239] 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 176/239] 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 177/239] 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 178/239] 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 179/239] 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 180/239] 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 181/239] 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 182/239] 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 183/239] 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 184/239] 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 185/239] 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 186/239] 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 187/239] 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 188/239] 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 189/239] 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 190/239] 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 191/239] 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 192/239] 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 193/239] 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 194/239] 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 195/239] 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 196/239] 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 197/239] 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 198/239] 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 199/239] 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 200/239] 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 201/239] 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 202/239] 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 203/239] 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 204/239] 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 205/239] 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 zcmd43cUTnLw>H>_$dRZbasUC5C`dTutR#^vSwO%*hHkK%oDG18lA7E!IX4Xwn8K$NPF zm2^QMVp$OAQr#byfHS1V^FM+Ah+w)Z3ZTNS+l#=D%Qo_w@*q%gIQj7l65#h$=f}n{ z5QysA`9Gp&r(8?mCIu^^^;?!$N6SYzs7#)xFy6$VS3|FOCp6Y#YEfsmQEDK5C2rU_H#OT1o!xk<^8Ww zASTs2_jJ^B9gPlVr$6Q z>DWnLOFk*|im|ueJsgx(+*d*EcN|X;s~d&zO^y}%y3UyZlNPy#r38UenOeZWkJX0| zVi3qSSOP-?oSXXhbEHs45a^+F1P-`t><#`32-HLM8gkT}QJ>?Wo`IKUS=4xoSb#~5@N|KBnN8~tj`wR*t&c7IU4AX4`0os-`1&oE3OMR+ zE%~3!#DqKTcmb~%pkoCwt*~E>J?`OTFY^rP-0MF(Y%ZuOA8Ilc-d=l_bT;HwbQ=WH z&u+u-?aJbS5v6xR<4SnJVxv;7Thz63Y*I9tkhKk-${~S+nlX^h$W$0pMharFv!fpS zN+FKrL6X+5uBq4ScCSsp*;%o7BHjZs`&xh!!ho?GWpRU!{)Z3b_THKdk@}xb*2u8| zvOL~c=zRQIVx-u{ube17^_#7p2!=l4>6VA~&H5qjnk>EHDGupO^nO_-c&vV6?jQw) z-kxFmIb?U`d~He#xiOw;YNfm$TXG*#PX^JZ=0?W4&C9|!S}wm{NH8v8L#HZ)n>2j9 z6hZs$hf~|kx0$Fn1KGC4YO-O8+`<{nn%&FIlZhd0(VskEDqp^3maL7MpXu?!bcMWR zyz;s15-YW(%Ul=RGKR9Zq-lp`>QsUsc=tDLvI{IB4FVI`QpugJP#Zy=m{06gvE!&ObG&} zU)6sgmv_~`+;ixD5r#fP^$)?2CjJ4>F7u>Nl+~G+hH()lET16NyrA)_)g}HhG(4tu zT4Yr6{COpui_y^|d1cuXmUGIoq@$w!svCb)1}?HAp(hN=Dy`_EeW@}%z=Wi;PU|#p z#2DQWY4@MJ^;k=a<`4L1B`pYJxk9+F|C7*)M{mBZskL{4cZ)Ei9Fl-oMYh9}IcKsp zt<&onD45>V)NZu(XJo<(-@p8W;hJ#!|$E~=cid^{- z1QLofNn=m$%VkFWmdlF50rDeDFwB(ws!z#G1KZDAk5t$aEA*19Cjbl1*S)X;a6{bM zNAvo6KJyRI2il2+T8lE4Xx6+f>0S$4Wp@zlZr3MA&C{D5ArhMw?&C=ziU!%=GE>+; ze%FkuZwiMS9|ym-f4NjNU6b=2?3-Y=;FWU;tI2sMSbr%8%ja`>WhP~Hlz%uQ{!l%v z-Ets@!{uBp;&@;P?oIEjEY?QjnMw+#r|TR|l3yQ}CYFx8x&%LU%(&O8bUTk_o}%?u z6tn={Mal&)m~po~)Un<@!rd4($QR9?=jvzhb2(y-(dFt7_j~W#!a6i zV?Yqwe;@b$!!Z8|d8;-SCe)phJlc3QWL$R+cQP$LWY36_1l-D##-OD=)()$kp84#x%00=Iw$(!%kv&pwq;@VCObCNvAl zPM&#J&2}9x>}doRtoHj_g*k6)RtseIa|^>ERB;9k^tJml_=6jTRF0`IhcWK74g`8_ z_$KH{s5uO`w^$OA6Y;F~s7+D!i7@8YpXn4voPxW;-AM!)k^xOTTiflzF$+D=Lq4x|@REKqY&7C*SNm23|7{-;{^-LvHz#q_Gbyn=t%Ah9 zo~C>KsslW0&4teeh?PPqbrw_~Y0O7pC*Jcb@U$irUQ%o+=S$9c5=zrBes5AHF9N9{ zMKiUkJTbK-n9#AX$KKB>Rr|E6#Is;T7Ev3au>W~V(^T#4JGHd^yY`;&>vj*kT)b^X z#rwl=8{~AaJoPgQBu(2!@VQvFQN6$nuHPzWkByF(73FZP$p!_C?L^}ST-znbxdxQS zlBNfDIqRUgP#A4-+p79o?E4Yq2<5ngM%2e?@)%f%T4Ijtjj04g@^;V$zgfb#s*P?o zHtp~)qvj7|ak=a@be_SyE{^@Jl^N2Mnw}wa>I^2N{nUzo8BDfje-wRFBR?1xEY*Sd zG^h>rDM%ZXONM9O81bzgWajNLc#f~EQNn$VyeF=-B{4rf-E{c3K)b+0ixV5|$aYnR zCk6IHb6j~Vu2JUd8cvaAiP6E*`^~!;hV0-P)|&RrVYHn=2{tkz`CjX2=+~(&DOJX7 z_*ILU6UE*<#iBZ|B~7`zMMmn3{EZnOJ`~b`O=xw8K3| zS{e%Vq?(_r8_&s+*crpTr=G6Mfcr18vV-J%5^$=K5TAHizmTzCmMi>&R$bU ze;mzGW42_DH@1vD64k-chW#T$S${ zpEitt;@bCZ`nGO0N`GRCj0v~%r7|No$$83VqK|q+XjC?ukGKj*21q&7|*bVyOT=W(ejO2Tp`0M4abyrOjYym*Y7d;#JTYSv% zy8+qpC@i0eWV^rQz{`#=w__!DZgeLAU=}1#14C3rTwWU%X~uVgVOIqH0;Ps9_@~?i z9K`kiBJ%x*v)Co%bx=zi+L&z?aZ!$BcdVBfy%eMV88OpC^c6p73elU4itsZ~;h5Yg z^Q}7G|6*D%J5G_hxNq2BW^}5ya`?ZRSXcYZ4Y^ z-Y<(>AQrQi$n~Qfw_v6_sAy|>g-&@dSa1)xT}ykUGo!6<$WimCn2>;r5%mncnV8te zvzimCdc{T;(p)NStMqM=HC>fcKr@VS+!k?OMECFx>cwR>wPSl373*ex4&vt&kTf+5FEvet4rR6$zr&7GLaIUCsAbv!DxHDSnWOt1lDbx#AMMi z)1-9_s=pS{GVD+j$XyH6G7RSt{IuSeokb4IgEmt0blCKNU~q-}~+pBFqf zK|jwa;g|@!>1Yscx_!QiYTuW^*`Ly9Y!K2}-kSP8R4|)&WE5jYUm} zQx}Q0*$mwN&~5;m!sS)v=7oH0by9{>C3IzWge@QL=_yju4VF<^e}M~?YNohJJ(*aG ze>LqqpX7bHPcbdCDA06GTtLDQS&^@4*sbZeP6RT^n!v}JOVr|TiAHL!#faXKph@F* zRncxC5vqMDsrdNp&F!Aew>dmYNOqd-H zJv2Cw)m1xs^G1^D7-AeP{oa1zN_3^5bAu_SwL`wMtbwiB;ehI>D#1B0DF?R`!z_L>NVgKzx0s zkmUq#<$JLc-3s2W+aKv7#NQzSf*>BwfWYZ%4qX{5io$10sT$@3=c*Y1`wiW3E$si6 zwvW$VlU$Mx=+b-$0uc&{=7H21wDaLSlI)@S2hyD9(TdVg#5UO3*K>(`k`JIN#`0*f zok!`tE5j*nY0H1a`@FL6ua8zD7$2v#yQu8Bk`;L_x>e{EEl$=<@2MBhy(T(xiHu_)>kY^kif^tkY{Qs!{& z%FJx7gx#!}X2QkF7*4USR2+hnWM3vo^f2rJ?fIi4Du2o{I1HrKa|1{NT0?ek&)goY$UDY`^LH%)gi3FRvF4NAER3-J9se z`2~6_5U=JaB4xTf8BJ9RmiA3U$o3Z8<`#-QyEBgT+B1_z<{( z1aVoeAUIs|L~_0%v(6tH4V6sDZ~wH&hZn56`Q5cft3+>CNMo(6KfY!EO5X z$29~g4>zH2Iy&c-F*5!QJ|UB`KcP~QYi^(|P|dyg^ql@1UVkX>u%mV`9#}7!`AI{-hI@Rv|+=23e{UOveqm z1S0P7hxE^vEUF$9$#pK~yw;Tb7Awcq_O4^Dr#?N`FG>$f&w+oke?O*{8Q&`^9Q<^w zsd|JP8#Df!lD1P$oMM%CovdbeQ(H zZqb>%YVH4ZDdWm#>8oP;16DuZo~iJymCM7tp2%`(R^>Uyp_&O}sY{tgqqcD04^UwlK7mPhW~iuD027A}yS%GWc6(oE%z)3v40C=Y1guZxg~F?c|@L_>Mg ziA+Rae||J?ERLHESs|GdI(x$^-)^eSOTx_0rKsE@`&p;hSl~~tyXvCaJh-_kKPmXv z-Y;%Vw~5N>-k=hFKP08CD$>aSYhjX3)yev~Ffm)Z-+%87tD+K0)63QdrLUDei)p*f zD_dN~;w_+X;?cQEXQmqd=rEbuJ$a~G@(DI${*!mB^e zct0?j*jz0NlSP&JiLYa7$QpW)ZDz;}(DUa2_!HAgFvVr_i$P59Z3?6l6U@~E8m4MV zL90VSc*3OYJ#Dir{Gib8S(~c`rqV5)zh)nlIzASOtI8?VUV8LMz|N`S^{SGY$2!r& zYD$N5=J-#se|J~^f%X-Q$6NdRTZdBo`sQ9L@+^haB60t4&a>8d_)Pn1Youe#5&F27 z@z?7spt2`NP{bkg_UO?xezkvSzgCn3vx{k0jKn1kXJy3BirM4<^hiLLN##U^>`~}g z%xnL_{Cik*y;RLC5ECcMtXHHp6Tr0Vtv4U79Bw^z z(iHx9D0vwpXc`qYHGK^(o^(p2Qt#QUvy!8u#WR9jR4pGSsuR zSrh8sJZ7b7FBRugdWUZ8weV-5?bh31l)-6e6b_cFYJ1;TkoG8`NvJ6FW;XJ@;oS_j zym^}4q}!;U5Nq;nix1J0JW^X-o1d=4kWOX9jo#BMeeNP``;(higrb(WrcDUuAAYw% z0rU{e(+ys99e96rKQa25k|t_}Iwa3UPSReu1Cstc6E>6T ztDsz+T`V8vH9Jq;Ny#Iw7=tuM=@O0?Yt9n|eT}wF&F~n=;&-fzhpe)pyw;gPNN3f10CUkWrJQWD@$w@4|y<%6qTkB25Y!u>6A6JX!bi z!6~a3n(8+AG2Cz6D&$z~xBcAo>1ClTQso{JS1`U`lxmGK?G)KxQujO-ajFzd$TuGU zi*CFpEG}MqOzCwvc3h3whIe7>N%tlvO4YdlPaFj`kHZr~a@pv2onq!Gk$fHg7PPK0 zkPn6NM`vw`_8BMI{(}?)ERgo^Y0&CL7|OViNc(C7td&ILngyxmI^x z=09dZch+**=FgZB;Z{beFY9aMk*g2zYL7eJ!;?461MwNdW{~YYDd%V?gOot{~Y zS<=MI%}_Elag=W0+OqCb3!Wx{h`}%0J#J|*cH$Eo)2vs4q7n^+mt=OtoUa&w z6-&uPDC&8W+Vb%o>^4fTwv>Dvi9qj$0XBro-E~>%_V{wrf;0(}=WX#%>)d&}0hvk` z&jA$^!e@2bdphG03H$o0WN<}UUszK|73Csc8mP1V z^{9=2fxe5TFWXpi zd0$~r7phFnPCbxIxz0Nw1`_0o#4XXH1LL!(L=?6sjcUN4=V5m-gAd2`gfS^kMeUM; z>WVyoTAB&+yr4$7_Fr)f6;o3_IU1HD@P6?f)qY2S7(mPV8 z>4+OpA$z6yZd;z}fNrKQQo#F1cQ5@o2q-|{6V-%U?kHQU5~s|wYDx|U>Lfu_GZW?^$P2VCK= zZX#KIwAR6RQ1$-I?1L`c1pxwG6a0y{=HPPuzO{z(8h>=ahRrkbUD$BsXeeY z4DNbp{gQ;XK%V#keZpvrjtV z$rI`Aj@YtkR~@s17iD;MA`o|4V!cx+zdFG_(Ko`GAG+jq3vG}&Sc2s<>ktIPq+c7# zUBat7`gvNPD$~}`d%>LDS;4mO#uY#1q#H%NM%iF5c6F_ZD1bQ8udSH4#>q9;@c{2{ zO-E)kF)*yWH{K5B;JONWc=er`lb*w$ByWnIL6!$p`3Dtq!&>Q!JC3Cdoy8LFwdx75 zWNK%KGMAFK`gx)Rrew=`GA*BRnP#e#gtj)<(1{Bg6rsi6-?ljsu{oW>-@>0vC%6|o z=tgNh-!qT<>Sam?zYI!!*FX@1DZ6J5s?AksA!qfNuH1OHC>fjJ-%Pg1*-0@w_b#)p zY6C%)B{(Wz6I~-qxn&c@+*-)BUPsoD=LN21)zEEC@Rz`lxYv6S%+|*W>iOT`hjsxu zwHQnSq)eZEAKG;kCQ!JzTcpUPJP*j7`0rfr`0IZNf8jCW=HbI$ZDpv?(!xN@R8e$_L# z7g*IxpeKz1Fa*Ddc)ZY{zC@^4-f~sd&p;>w5;dt>%w0vVde$5GjE>U?g6R&U#V5~5 z^)2n!zGsFpw&~EVbZkwmI77kc0s~ZbB62v3M4Ad@D-OjK zFg%C!@Bh5C&q8|N#5CWZ;k)&Ws}8R-u{W9s1f#)%4Jk!1gO8OT!6v_uIB3D=sY!Oj97X7}KAO}<>`@VA&$A`7>w!s8;NQR|tKdFIcv z%g(}+x%ey>Qrz~X7+)Rh8G{8qO_c)gW9>_z9j7N)@KM?R?#cJg{uJhqJUNqpP5CGf z1-yJCn!xk^u7c3}F=GKS4#c4vMT6Osz#-SNk|KXL45dCW0x2|t(R~M$YIP+VSMs2G zAnjmpkzKJeW-zZlg(NMRAG&Cnf9=yBt=AF)Xdgps|5$xn!@Y<4S@+178}a5XLo$vb zR~Cyt+6GGG{V)jmX-!51>T!u!?6W_R0#*i#toC)X2a#<*4O9v<8t|O#Z=24Blg*1) zSkv1)6*D#U6}Au(4IH1JEL!R&5bpZdXOil1)0SLkWooRYKn0z4jgcBtgH`xlxi!w9 zKF)KImUjybaC5rP9oOFMzy;-d7P+J$RQ)pU_4=MAM_R}`M^E##j%93i zv-yA6+ZK&UVkuOPo_ZuiCwh(jg({JK;jIFM0$ZNa@g1HU0#D8a)I9DB%c49-jaYj! z@vM~5l>%zkY9vJ_bVY&RWs|Wq!bh}!Di>2-2c^=*;JGvlZ!ZqE@KMiIlwl{^Wr%*pFblqKiX zJx|&KR((;YrkdeK&On`cCQfUXk3D|MDnA4+gQ5*pA?nG8boD!zp4dT31s)SFv4syZ zC0Ir$(VJ+dyOO|oxsn5nsUxn^Sv*(hgzdFU-`6!y!lnD_oIL)v)50!n6lnd7V{FdD zA&GWpu=v}X7|yPt9nm&lrcPp`HL9`Qx#9%03^Il;cZ}{k#f#4|8b!fIX~-Q?3U888%Q#6+XNBh z$3plum{F~d10A~yowt)tw%i20=h}PX#P?G<^r9vd0AigKJ)V>t{pMp~gtD$Y&!10DAD(5jUJ2JAM{H6!7EsN;+J-IA5t~KFg z`xn#K8wdwSR&Ewv6Q(tCY04+kgDa>anJ{m4cRq&3XE!s*(RnP{OHV^t_CyCKAs-K%f;m8qa$UwuvGpBvvwr%smTp->QCvY zgf0V`Wq@Gh&cfh_eAWi5c$cDHWxZ#$4vv?fA+ZmWGY>6nA*Z8g1_kuBfSCuK*X|SN zR@?0!*IpNR0;Ax>J@l8&SbYbK@Nu8fN<>9H#i){g8SlfkP&suz76bfYqCyaz3aOtCWrag z>~terzViw^lx=vZa*z-d99!fEU9Om;acGW}odyF`F$}NBYgpvrfm8}ClC3Fh#=Ff% zsG0rXg8K!&%s)sgUMOxV$0p;%z~dFuQ|0EYKoi5}AT3H<3$iMU8Bb;iPtoG9>9?AN zCN>jnA*S0SKjvuO11_ki;bZWyVI(d#%mOi_4(Xjvs3VoC^+1iWbF`K`dzQ{)%E4JU z3Y0;t47XNj>mYIaiH`lV;#gUr14GKod6dlnjY;rEa;99R?bRqp>#Otg)R;?>4wp`B*RuU^RggujdsT?=lvYf z`mMgYY?{^|>mM8PWAX;2rEiXjuI4DdJ*U&v5`%xz}~Lp))l`KpE{Zo<{@Q z0%m@5OWqIsR;waNxAe(s?Z3ONN_(9SE*ni8jG~4gT-$n>td?d(3F-+40)u=72*P{T zUwvnidgQGC<(YEBPno@@3UVXJZJE7{5K%@J58J!^{~dse52iUr*=cJyK>-sHIQvHs2NKt~$%0=We+|od^+)3? zyw4(E_0PxgTdc+~mZ8vh@QQ7Y&o@!ck*KC#@Wk|9#c_t18*}KdMQDNDi9Ps7ESvWtLYo#QgSKI z2mA9^`C_C4d};Yg##W$;zQp#@NW0?%@yQ5ky`9$Ft~5fPUA6;5c^q zYXbRjjgR3&9@w{=phKnKBT5xuvL4Xi@V&-$y71}riPnvlz9QlKE#RTxyw2f;Uz6{i zz5zsa6VKck)z;U<>vQr$g#@Egnpq;UDMbWw3B(%;Lrr--{3l~uop03i;jLqv2Hqwe zs8{YRQ*6T!+cCkaM)_Y7t;E3FdwK7g{bp(HCw&hiHpt4~JxD5k@S@6LvKKC3z87Vz z!qsg~pveaHP(w{STfpd^O}V_dx{1&io&O&UiD_uEt(KBCxlQwFg z!2-@HP`_8<={1K;$+9@mnaD;y@I4~#zY0nEF&#z&;^64lO)MI|}NWtem{6wI!i<=p*+&^TFpf7r| zyyg8+?L_zO=XD~Y0J+F>jr;aA~@2 zjDOzFdYi?p`y5fK|HY+G=u}@dXLr-#1!FXISp~}MqJDHet2A`}rZ1*SqQD5!|2-x9 zoe>%w|1<3CjbPy=nruS5Jab}1jxIN%1Xy*qHK~om&*56hwFPo7?CWk@_G@|a1eS#0 zGC)sgFIFvgoUp3LW$rOIH#{*kNqm=jHt%GySLob8_xcbIe5Fr+n0emOKG@*~>HmIJ zz~)0Pgz-OjaJau~(C#F6lqF3!PXr43p`tAyv5PLAYp=IX*#HcW4U^{Y@3%1Vz}x!t zIMCt>NCf{;Ma+Z4Q%jrwR}S6UX=fVA-qnG;!IU!s$%|_n_*F{0lSGnvjc6Vldb%-mTQ0#Rxhlcwcxu@KW=($tJ-I0;*ZJqUmDJJJOl+WN8rXj2W#F1;sPm3x=j4 zr^*~3TpxUwm$dgBKB2D!Z$9THTRj&)3`Gw;ZVY7zBFrpGV>)ZLM+No|0$=?5WV>_| z(qM?IkBW=6T_SYnPD>2sA?gl?O!j@?M4X0|&1koXi`V0&V8To!Qs}I)Hr{{=u#PM5 z@k5Z=3S)z^2}02;4lJKv!Hr})92laPk*Qkj0?`YFWyeHOEOj@tAv)@v1*~Hf0dWv* zLUH8|PL`vzvlLE$uWM(1I-YPJa#b2*$78oh7@w=ynaEv$)}j|;ZKYVWwIB%3`F?Tp zkHjP3j^uPbcsXDSSdg4-2VB*w?Ib%RxpKi-;9o3D5-MBdp9N8sd+rf1Z>;mtZq zylr5fGLGw=?2^@qFRuXk-yM$>5rML+P}9-~oVCFV2n`Qc>w~M`@4JkO^J=DjG}H-! zPe7rF!PV6~07vFvh!0L7kJ5MRfBHNtX+0(%UgT*t<@8qNT#mp^9Q2qNN$<6L#PFBS zT3-ylzK(mFrrGjuN(#WCMGZ_UKz7@z6^C2 zIuvv|2Q1)=^H~I(FQ(W|I1mef<$v3P%0IQ2@}v}o*oZO=T+sm%=?jIXJOLUxADaF> zj3-ue`>s(+O&1ZUmi1iUDNo1=KxjxDd9JEKrzeXOfjL}s9%lnYAj$y)2x6k@C*9Gl z5iXw#JX)CC#{D?$Cg1n5>ouzJ-v9+<-%G)|A9HIegbK#E#QTeW2c1uA|_Ce#<85;f5MFgmfz0g zKS|h>{Idmu8C&s#w}9rK7gSU|L+m|x?Q7$;Iiyh@p@juQr&jY`*E@@~&erWd?4)rgXk)rsGNY3Rin2z8Yn@giqug!Pr1Uh-vAsJ!P(} z(U|qyhg;=#HmD^F^pdiyMt+5krZh>L&otqHW63vy*3D5-8lz`wM{bbMPPyZkiiWKA9G0P zp^Qj&`|djpAQjG6mK%rNUu;GMXp4EC0ZP_6J6GW_Jke6Z`!BOWA!2YHwY`;inIZkE zk5O=$4~Y`HdcZ)pIx_2+uGQjW9itMJF-o16YoOsxZKNxPqYuHI7+`>EktF{AtZ{VsE2o9U)Ibawf5_x8^o@h!b*+i~=w5lXR?Fk_DY=Q~92?}Hn9 zIR6a?ubwg)#O!^1VBbS~A)pI$$K6iVP4xDM1C|K`LI}VQDe_f?di`}I?hIsK@fj^( zz~lT!#|E6YCI4UC*Z)Qh8G*3%A8zVDTJ`^=)h?dksQ@hoW(s<8<03HnpF0=;YCw7O z7FEY>gZqBRW3H?~ymQlQ@Z;*3`_3_HxTt2k)Tqe+&g>hR7=+9ZN69J8qT z)P~1O2$-%AFX6GGLgOGNx6z~Y?6l{-jI4*_BOol(lkDy?BQ>wz>}{nJ(Dw80uF{kq zmfZwf0dbet)^~Lv!|>1qodY8f^NN9nv**XL3yDA&pvPw!55ujUsDx>zSXB9x8$p1y z)AVD*U$gXDVY^5t)rsZ~;$BqI2*)ALQY=^^3iy1ViBeSLp~i-~thv z?$`SY(bF+YMi;g!|BVUf8S)R;&c|i0x-lx6AEozs1Z2_)GlSRPGNwRswAv)XS)qarjWP>QL6TZkgjl_1I3Hz^z?ti1U$jg!K>ZCs(b}#RKe;= zdO32!!%%HAwz-cw5C-)?)T-m`Euzk&PE)k8%g93echa(a83btkc}_tb?Vh6Fmht}P zA2w2$Jkc*b3UMEe?XapQVVN*Y37Kc`k7=pww0=e12ZE?N#|b&tjgjygh1F_pmK)}2 zZa_r5$ED#8gtA-T+s%cR7iLG>eHwvOO8z{RI+up^WSd58lM9nU6rS2(>R$de3Lh+% zZ@@DIpM7gygO?*ANE}u7-%Mr19vVW_oh+81+fViAdC63_?$}QcxaX5VO)Ix=U^SU< zj^7Y1-D<1e22`-G=-8QdR?Kc|%fShe1Ej;ohcPU3;IV^#%mYCru=_^&_e}BlSoTa? zutszCYj{G~Z72+J>KveIQhbb$U9Hi8kaL!PEhs>bxBoYD9vsLR%cu6ZwYCL_Ra7U0 z_PP71$tpFI76?jz3HTi#t78B}j!@-<^^{U}BmsLD*;^3N z4D9N^@}l1TfDVs9lekG@-KDr2jy}|XfOdYT3|GR}$4LTWf8W$F<6a0#&ARZ@X}7BP zS@=$>x4P%g3Xj##lcKxzywo5dv;dSmsOm9;w3u(*&!9ZVC^FA9O}K~~p|SieV>Vz= z*Tz7pH-2YK8O_NfZc}ZaflO5=MNrg&Y6~#U7=_X3TG_;M*zlWSCm)xs6|3E=dfRh% z5N>UH_o6lN%p?K%7u0~_s+d!X)Mg{^d8&EJd<{r@R<*-pi-ClZu8pH zH-ED*pa8#%_h$UC>Cg{kAUkBmOT}CjW|h#HpT#Vi1*5;dD$=|@P^8(~`B_X4uqYaH zGSco$9sr}jWl)t+i(xnR1}ZH^pukE?iuw92ZVj4a&+%;SMA^YQSUXu7HZ^wGIvGA=5(KPruy)<7 z=;iZh^qjB=AG%*R&Waj6>;$V&buyOVIn!22RkXYV<#qW4s#I;IHyb$wa!FK0(-}cP zUcejWT8f?=JzHN{s6!du#=Xzw|KW zLVI`B_|mZV)NamJc)DlPIel(3$n6W~qDyRipHDA&V|6kzeGn2#cja((v~6&ra6s7{@KoW5Ty z^i|QW^B7gPW{u$TN>??Fh|<#RN1rElruv26m9nDQL0$S?t>C-DULH}kr89Aleuqux zP(?H&cMnuXcM-=>{F$N-`X@$)){$EN;2o$sW{%LUkA6Pm{SfrG>lF0xS?B);D@4Iu z4%@O|C^G$NQ22z%PHS5|uq0B2s~HZPjWXSZjI?gjT7_z7GAvKS3``S6DZ)( zZ^xx3<4>rG8*${a>GFKt$=Tbv3{clVwv@Pw#Ql)NNBY9$eN))9IyXjG2~7ksw-A^qQS#lpwu@2GA#q;OI|Pibg}POA1Pm>$=R8csT?Gi zos#;?AWHK}_8Irn%*eYUkev~-M|LFrPqGrq;P2s7Cz2!8&qb-%)U6B3Oa-W|#@cQl zhr}@6^;}`L4~?lAmuh&mu>Q*VKJ_vfB&Y(QtKnS?2ZvUl&|s8SbH1-3xB77(eOORB!CIrHX*Z0^`y>N314SCX%2XCR^>sT<~DQ+(RrovSmpl@&RhNXb^n4Na^ zvuihriq12njOM>p*b2FPy%4VZ4ST@JH2zsmawdPOZN7q?R#{z7D$*l^Thf!eQXs!) zaVSN@q|>R|8n0Al*@S0(YuqL|Fjc#49v0Rt=w1*I%&B#<;Nv!^sm5LgY-v*v^Z=5&L zzIX&djb57-+ueV9cqIiEUUUn{Rlf=Xp{e7@8vGI{z*zA-Oh2!yT@-2lrw)u?Ll94^ zfv-q(+%y04%SCYkXeSmEpcnz9c1ln*FfB6Dlfi3?3uUQHf5jDf{ zV+{D#L>XO%ICVI)g#fbS%*VPlOhYuzv@=Dw6{ukS$t}$V;EC85{4G)>K;;`5b9vU} zz+6HZ_7J!&=@1xhKG@)r`b(?%sW28pQKz+y_I8wIJ+QwCd?B|24am9EF?I{Q@x$qn zJRhZ$eYvXIqYT_zFO*-B<442ffg5H6PeP&Q`Uk*#UjGtab~AImF{tALQQy`piLf^O z{$nz+mpE@wwDzWrkgerTrEppD#IH@+?HPHv;T{ESpBL%x#WJ+cia$_-K2%yP*8u9u z#jr~N+;a@$4#~klYJtMz=e8wZDEN|KUYm;e4=TUFWjB7P_-NL}20as88 z4(}ILoH@meEYtVwd!b+V>ZM9<2*cWjZBi{&xVP;5dR)J18Ch zZq(F?)LjN|x%|ZP!Bd_|2?N!ZY(?*iE(&reqFm2j&zj{OQaw07QAn6!4EXwl*NY1+ zy|RnF%p{@FqY90IyY9U5e_eQIlHF7hi&i{;ZI{O%8LO7fD~vnA6O}_xNstb$7T7=n z1yJQ#NqPvNLn_oXDwzFM=cfsc)4m7Bx*vxp)|4zz*XJY!px+VchybZ-%duItM}=$WKNvL1`+lD-3sK-&p3sY#e)wv)Uzw}5l5_Pt`^l&E4|+Pf4S4_P z3dpq;E==G)?>Nu4_K)2Mkqm9%AyRz~s~_H!pof#c2fhO{RmS@ONMa5DPbU2xNQk{o zv)X|ueZ9HJjP7@x9{rRWdHxlfheYSAY50%e@_$#H&zaXUi3cwK`g26`Md5tK_)++6 zgj~JLfqfA^LR0qzPy;o`qEoa0q`klif(ry*v-_{Y`r*p?M`6x^<;5DI;ylB5JghN# z`jE$F-F0vB>N)J*`~qtPb|eG59Cu}D0!K_%h|!2Y*9D>Ywd*QO1OxuBsG(p*a;bYi zU4hCR2t=}yB&Y(z0j&y#EXN(uG$w0_@kXXN}XOMnGfApKe<58CbqUvhcra z%#FyK+yF(fo^{@}l7yrI+D&LWk0;Fjbpir$Agn+06rcrgW(IgWcNpR#mq>YkkxLvc z{Zp!1^J1*>OCvi6Z@rNKpE4ULcCi}q96%>crQH@{>k6=4xBXBP9$ttO6g&MOmx4QA zNU_a-D?EQoBY4aHTjAMjP05`3C=a`EN!HuhXBTc-FDkdWTjaC*!iffydJtRa;o|dh z9Bzt11Kl<<=Sr3Q!SmLEat_qxEI>4rvw}5}Z!^2K+*y=sMSG`-sNAm*5 zLLiYLLd^cEzfW&{z0Yt6TD?XOX!kH~0q5Sr@`+XGD&XEwhStE@&aX4b@b@P$uR8+G zC%2%C`O1)fxv^^Jb@4yIAQM7DGoA0oiSphk&;xOQRt%`IXP>l>0y!pGs%xEC#V>b( z32+L)gaYNu^Qnvh4yLA*6;$>%scxn8??)Q8!*Cxobdmt9gjWSO5b~AUyqJ@Az1Ied@2ryMqZ=iTM z(1LXN%W-8Ul&4VD7OtnUGWu7kY8<=lV<$EvF1pFEc9p`y-Z`G_@8Fr=TjsGaug%z8 zKY1hO(Iq4qQ7Y~1j@3V3!CCrnS~!mrruq%4}1rWo6}0HG)QG!cSid#RfvZZ(mf}5UG(n*FGDSr zXaviu?F+DSLsN|?;&`%}oOxv>(p))u=H#SLwe{G03md_3`{qy&r1wBdEg?>({$>QP2ygdm$T1T<{ zv57DSnBrV?KYu0Cka#V-eJrN8XJul2dT;eWdFq;u7+VgK#z?zzv|2GXlLVUQ)i{N>jE`J^^dn4fQe$s)Wp-z=Qa zuRYb4|M3~I%$3dCFRVG*Ly`+8aftfmv$#A15nRl~;5rB56z=jMXba#$mXlgum4xh3 z-ZBGVvC8;KT(W6Tup+007fKxL;ham8aL=ng#dF>?r?;kY250_dL2_KtJUU;U-biEH z1}l(y@A2@wB8Pj1N$il+D?1~u)Fa2NyTVrA31DehS<#5Mp#%*u5^{qF?tcQWdi@VpRJaJbt!$E#MOGU z=|x2}10--(Dp{pxgECTfW%*=C8K_dxBS{WaUMZ(c67}6mvL28-)_ZjB&|Y|JW#H0F zDQ8(3fjABLuu zFkgG)^@bP2Gc#;ya)WmJ#CAM|~!zzXU{LKEP#RK}c>DV7K z$qx-^fjHx~dE|#mvo4jdcsRPry2ooy3N___<_3H4O@1Y^FTd$RM9kO;mJ5eMx6b)O z1VOp6Tv*Ch&_zS*i{5_C*1}uonF@)6xmGq2Uq8%E%mc|wnwAI-6HlJRu&t6x8mpp& zv}7t>El7jOOC|TBBg8BRrrgAIAFF;6bK^>aAa+g-_koKC5)u z*M|_tQPY&4+5q=-gtY*akQ?&Pg{du`d;rx`3piLm z>wNeC3EMr*CsS6Xf>+hOF$L4kLFqcQ7^aTy!-Po*nVA!<7z|*tLf(?wo&#)lDfD(s zL7-Z^W`f|;<}m;J9MHep1jPPQ9Rq?`?Zzgh=Q&Yr=}NA4D*h&_v_Lmhsc^8!y|w#O z^yPJjmjsa&H;GcXYK{a-GRlQVbT9t!gVQ?(7Heaz$orQN{q0X8FfEk8`0X!~G8tSL zl?=6**$dWGHX$C{-g6xvWNGfdZmSh*g;w`n?XlNRuF;GPucE}oaAhW%wUym3I?Q@= zx4A0}VSR3r1#${?&I{v4)%SPs^x{LmCq0N0V2o5In|G_2Nk9vYhF;X#=2X_7yD{!g zl6>vE;}u`V5|r;&7z8~#6Y*!O7JTjNg*@XNg$(li2Y*ZR%2+V~1@-)iZ4b5}fy+43 zE|lXaO)lN`#l?muoT?!cnK1;iAqVa2eLLfJ5o?fjW;(i7d%AvqeSI<|v=%zOd1P&X ztLu?HZ@hCO15lA5udwoHA)t`)45+00a`;d$p{Qc#W(y_c1_nRhD6e)x8c2-AG19!Q zq%07-gO%h_yT&Nxk74zH2nOW&Z{Q0+$tKYRk8RAEX#v)+wMSxBpHdTY3A0)=$caUC-G*@#Gy40#9Buc z(x=2z)m0ifMzsxgpP<^S%Ysuy@CPINFSQunHAL6L%l`0eJc78S13G z*V|7n|8AdK0n3hM0dRwz37y$+CT9R(P-F*=erHL1!RpMD^2uL;1v9=@dZw0V!)7Rc z58D)i{MyTOueVBPw#EF9GT&oeJso)+yTFD#r=r42LCzA<-z$s;@^YljKh$aIxhoXR zdOI=#QQEbDa&e|~Up?7Z zVYht_4P(=ihr8qTOrx!0PEaE$5HdnQh!6xq1PLV^&$-$Nfofhn1%S=X(8%u`jvC2! zWM3)gE6(ZZCzvOYhbM{Glupk0i zauivCR6C$qZCxL9IT()0(f$#pHGHeULsre%Q3urYZJK*m2nm}NS^r=VUaq2-M3r!wZu5AT5t*oTq-TMB)WMwg--udSd34+8UE4umJ0#*XPC zV$BIo>Tw10;c3&wYh46okZuH=3e$LE_{S7E@E}kCk8O&`XykZdg{-!EmKsI&u;jq1H zb<6Q>*qeI|O;}n~a+;qfXU$wGLSK@E`A$zN^+ys`Momh&*a~qR`(Z&PE`nWQgg5WA zU7Y%lCV_FM8I!`A)PlJ~)w)j?VXPCo9FF}OkYX20!mRI9JS=pAgJgiILEezID>?cd z$Q3$(gpvO32ea8%dRcbH36Hk@Mw=N5QD94GY#uK9N_$hrEi-2WFl`GKW~f;GUt=rU z5rQ-SNu6O`LkZcx9P>H}3nvu2{pffON(+X!N(m~Xd22=6M%I?eEXamQ`t zDoNPDWY0N*WSdK9296-4l`FAGVnvY-e?R|ydh<*$1N9JGlkwA=sn}UEpnUPTM zwVu)t{JtU0R4SL#GKmpk%a=$5*q_^N6HeFnY^Z&d_e`(ylE^tvUw93at$YxW?QR!LY;oRLi-jW%+o8*nHB&Rl`K;WE2~G^ErWrW9l3NLbomz8U7_K?6B4{14x}Qi*(%(pvzP znfT9Rd~+vY2_osh6E>NlEXStr;K}I-BH!dOz`go00f)X)gX{+hwFnAqdAMZhde3y# zQY-9t*pJ`&x)+Ku>{C#@qM=QuCO8s>4M_{luno|d1dc!_M3iDt)EN3U2w&fa5qaBC z#Ki%q)DiYrZO$VS>jL^b>hCoq?^_3(E)XlTZv_kL6}B8=WPR?tB&?6Mh;tF7K8A`L zZ{9*ROCndNW>`GYl-A{H^q`M?p3<_e3PYTLe%4|!&wxSvXCvZ90oZV+Sp7|~Xx_qI{x!bp{I&CPbsF+3hGH5x$LRHiu&9*)-j)%LASX!2I8kB50 z>S}FF_;=IHTyrpi2Xd?8YADO7)78ii9AI6$>J$URcX)-@lM~I-zi-a}_dkLR;M!2B zi<3~$NhiSYN$ku{n@n^BjpPf%P}KAq`8oHyikh`pIbM76%r{t~wEUEUASx~0aF$$3 zw#KN(J0kPZh>|ONF06JWg9e|Qh&Ka`P-;Y$$CG6pJQT%T=WKRbyXSO)*6!Rk&gub# zG6ZO5UL3sNdofFWV4HvF9>N;DjjdCeLrq=1){?F9GRj`m?(GW~y%ufEx66n;?gK%+ z*I9;Df@6;|3NZ|0Iou|g6mheCws{h+uj#{O+&!IP5z-D;=z>y|=nXui7Vep8ni_bZG&z~%1;dsIQM@%Wc@vJ;k^msY5gPG%2_x37B(zDv=!a;jB zvvf2bt`HgX4h9{&7%kVT7_HxYzOnnyy!Iqm#Hl@3?v;O-`ni0h(s!;`w3FrY(cf#J zKmu_~1h>NNPADY@TZhMJ5=OEd%rx57DI!9c7@eto>FEoOzS3gv59z4kWHq8{kTb6q z>Ub4yV~7XGE!rLrbT6Ti%MY&l$B1|*-rKZWD)aoQ7HM!-HwW9pt6uajto;_{DC+aG zP8^Gb{+G-N2TOPv=C2Cm3@?t(Qj;Ceu8k5aU{|QEMy7Ii{<(TY!27iE?~$sI1~4i6 zma9=uAJ?7ln>h9yRKR?LUiZkr@kEN9hiZ0?N_rK+b;eKQco?xGviCt-cK<}59!PGy z#|geI(vm-cu=1|ipZ=`a(Ddi9kHuM<$dRb^u(40Lgb2Ig@6Vk9vkDtA<$ugiNZ`9A z20M03_WZ&{n!J<(B5#xV2U)c_8`NBo-}*t#7=t@>b_elDp^rOO`=|zK=WTGB&4KHb z1|OPmXUn&2vHc!wB{E{JcIx&}&CI}<_rkAfDv)+QJIinew}3oTv{Y4eRh;1;W0E9| z@MZN@p_+axNx=m{`rxAtUw`gzfr{qafr$I=?J6YAX3Bo{hCk`#z4jsXyCf>(hRJkvN#uC)+3yXML$SV(E@CScXyE+ zUA32%D4k++)Xa~Fi9KqLzR?7FvhznINk#$xBf%hlHQa1EZ-)paNx~`ht#YSnF9=o* zOTu>r%^>P;(h0c8@*w z{*hVNFz3k0Dvu^;fOHt(fdh%W6I@2l4u0{t)z3ddx4vR=8CBrgwK2aM*BpQ|NnNS3DA?Iwmu_9v92!nSF}nP+?A+6zLYs&N7itrqhS@< ze#B%g5|O0R@)bcEfE5G@vEya&%Z}LsOT-*oI1YWSsutEz@T~sa(5afiOMM)BjXnb{ z3N;Us2MVHiS%~=jA2JKhovqDtD0RVrs@8#J$5gEmd&9i5IE96>a*&sS&dj_o^7P#w z<1(C6;y!aO^W&kRQzl1Bdsvj<3|}#IZ!4~Q;_HR2=%!CW_NbuP_yH4U$%>BU4@MJS z0)-@m<-9FC@<}vcvx$xq(LAD6KSk(H+nv=yxeec*6|c%3Z3aQyN!WW;=qZzW}99 z0T40UGNWBtpF9cNd%%3U^tmFcibKZ^0G(6n@#nNR&fZY9H6*Uss6^ggx(UyBnQb!U zbz*Wze5Pm%^Gy4@{50_tQ}S7;;CHe9$_T7q2$^e=)EwU|Is*Cthj$i(Y!sBg*h_;l7jBACY?(qlHiAoQ%Bz{`osaf!o|iXzTC1HFtUh)+LL{QjSM!Y=?tKf1Ez1b`2vrmaRR_fdJT{h4W?p}E5s zp#S{1%K=Z4a8YZ`+bUAPZT)eZOqC2M2@@Sm(EhtqS=D7a?&y&oxgi`gEA#PR=`B7%a z0lZm3u`6H?T6(Rj(`pPeki)|H8XeXQxrrN@Tt_dtRhL~ za8mV}ShZ2u;zY7jJcnu*FVCoK%pc-thbDmun6-J{tCo?PyVWgM ziOHJ1_QxFJ{}_ggrVtDA*l-*Q9z9TjQ;|7R*PW(hZHB%|2q!RwR1rn?X>8{%s_T*b zA^X~M(I9L2a6(Yksi^kgLj<=~>B8HBgvEQ!ULWe8%F$gCJUVvNJox@X0^u%bxH+iu zWwKEqMF3v-73hz&ehJALT~Iqf(bKsP_-7c(qih`6bGu1>r6s{cNiDA69q7w(j%CH| zLZLGJCA(K2-_R&oCo@ew*fdP{yg3>)gGge8{M=4;lkv6q)(3WW2v^Idt87P%i)v%M zBd!D&`FWZgcX4ZvOSt19^{a8mupwMZ-(}crRL3&{7?^r5<+D941cXVO(cX1bbLd$& zoklhJF7|KMcI5;?bJgB>m7J-> zw2}psn-hMS1{gu5gECBht|7n+Y(P?6O$-Om?p%Vi$DT*-R&O|IB(!Hfkrq{54hR= zen19CRSazYf`sk)DW?N@3G#JY6+mq{fGfPY;xw4B(Ie(k0^PU?6d)xP(^Y#$p6+~x z!5;;VnsEMI{@8cQ1`nFK1LN>Em@S3eV@U|0wl<9`l(3TPIDq9e#M}Pp$fc@3`0)0T z{{vARRf@Y+&xAP^i}hGqKBOeGk-Bp1DAoD=7cD}ls-^9x{_o2oFume<`^zjP?Ry~) z1Y{Dbb-On!c0sBRbEO@5TQRE*x)+k$2s$b$esX-YU9p&`cW)qA9`8W7RITGS2=Q*~ z6ekPHq!G4@k@<1`eGY7VxFGd({n^$hMR17s5aLsRBNO8|cdo+P!oqCJ_CrKewUw}a z{*~5MY=1x+gvO=d;3?lambq5kQJaOLq`|C&mjObc{`uo(0WIKeAR3^`5EK;ytASkb h5$6-oU)b1#ocI28)giYF^kjm-F01{Wp=|W<-vAa3;~fA1 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 206/239] 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 207/239] 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 208/239] 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 209/239] 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 210/239] 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 211/239] 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 212/239] 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 213/239] 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 214/239] 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 215/239] 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 216/239] 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 217/239] 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 zcmd43cUTnLw>H>_$dRZbasUC5C`dTutR#^vSwO%*hHkK%oDG18lA7E!IX4Xwn8K$NPF zm2^QMVp$OAQr#byfHS1V^FM+Ah+w)Z3ZTNS+l#=D%Qo_w@*q%gIQj7l65#h$=f}n{ z5QysA`9Gp&r(8?mCIu^^^;?!$N6SYzs7#)xFy6$VS3|FOCp6Y#YEfsmQEDK5C2rU_H#OT1o!xk<^8Ww zASTs2_jJ^B9gPlVr$6Q z>DWnLOFk*|im|ueJsgx(+*d*EcN|X;s~d&zO^y}%y3UyZlNPy#r38UenOeZWkJX0| zVi3qSSOP-?oSXXhbEHs45a^+F1P-`t><#`32-HLM8gkT}QJ>?Wo`IKUS=4xoSb#~5@N|KBnN8~tj`wR*t&c7IU4AX4`0os-`1&oE3OMR+ zE%~3!#DqKTcmb~%pkoCwt*~E>J?`OTFY^rP-0MF(Y%ZuOA8Ilc-d=l_bT;HwbQ=WH z&u+u-?aJbS5v6xR<4SnJVxv;7Thz63Y*I9tkhKk-${~S+nlX^h$W$0pMharFv!fpS zN+FKrL6X+5uBq4ScCSsp*;%o7BHjZs`&xh!!ho?GWpRU!{)Z3b_THKdk@}xb*2u8| zvOL~c=zRQIVx-u{ube17^_#7p2!=l4>6VA~&H5qjnk>EHDGupO^nO_-c&vV6?jQw) z-kxFmIb?U`d~He#xiOw;YNfm$TXG*#PX^JZ=0?W4&C9|!S}wm{NH8v8L#HZ)n>2j9 z6hZs$hf~|kx0$Fn1KGC4YO-O8+`<{nn%&FIlZhd0(VskEDqp^3maL7MpXu?!bcMWR zyz;s15-YW(%Ul=RGKR9Zq-lp`>QsUsc=tDLvI{IB4FVI`QpugJP#Zy=m{06gvE!&ObG&} zU)6sgmv_~`+;ixD5r#fP^$)?2CjJ4>F7u>Nl+~G+hH()lET16NyrA)_)g}HhG(4tu zT4Yr6{COpui_y^|d1cuXmUGIoq@$w!svCb)1}?HAp(hN=Dy`_EeW@}%z=Wi;PU|#p z#2DQWY4@MJ^;k=a<`4L1B`pYJxk9+F|C7*)M{mBZskL{4cZ)Ei9Fl-oMYh9}IcKsp zt<&onD45>V)NZu(XJo<(-@p8W;hJ#!|$E~=cid^{- z1QLofNn=m$%VkFWmdlF50rDeDFwB(ws!z#G1KZDAk5t$aEA*19Cjbl1*S)X;a6{bM zNAvo6KJyRI2il2+T8lE4Xx6+f>0S$4Wp@zlZr3MA&C{D5ArhMw?&C=ziU!%=GE>+; ze%FkuZwiMS9|ym-f4NjNU6b=2?3-Y=;FWU;tI2sMSbr%8%ja`>WhP~Hlz%uQ{!l%v z-Ets@!{uBp;&@;P?oIEjEY?QjnMw+#r|TR|l3yQ}CYFx8x&%LU%(&O8bUTk_o}%?u z6tn={Mal&)m~po~)Un<@!rd4($QR9?=jvzhb2(y-(dFt7_j~W#!a6i zV?Yqwe;@b$!!Z8|d8;-SCe)phJlc3QWL$R+cQP$LWY36_1l-D##-OD=)()$kp84#x%00=Iw$(!%kv&pwq;@VCObCNvAl zPM&#J&2}9x>}doRtoHj_g*k6)RtseIa|^>ERB;9k^tJml_=6jTRF0`IhcWK74g`8_ z_$KH{s5uO`w^$OA6Y;F~s7+D!i7@8YpXn4voPxW;-AM!)k^xOTTiflzF$+D=Lq4x|@REKqY&7C*SNm23|7{-;{^-LvHz#q_Gbyn=t%Ah9 zo~C>KsslW0&4teeh?PPqbrw_~Y0O7pC*Jcb@U$irUQ%o+=S$9c5=zrBes5AHF9N9{ zMKiUkJTbK-n9#AX$KKB>Rr|E6#Is;T7Ev3au>W~V(^T#4JGHd^yY`;&>vj*kT)b^X z#rwl=8{~AaJoPgQBu(2!@VQvFQN6$nuHPzWkByF(73FZP$p!_C?L^}ST-znbxdxQS zlBNfDIqRUgP#A4-+p79o?E4Yq2<5ngM%2e?@)%f%T4Ijtjj04g@^;V$zgfb#s*P?o zHtp~)qvj7|ak=a@be_SyE{^@Jl^N2Mnw}wa>I^2N{nUzo8BDfje-wRFBR?1xEY*Sd zG^h>rDM%ZXONM9O81bzgWajNLc#f~EQNn$VyeF=-B{4rf-E{c3K)b+0ixV5|$aYnR zCk6IHb6j~Vu2JUd8cvaAiP6E*`^~!;hV0-P)|&RrVYHn=2{tkz`CjX2=+~(&DOJX7 z_*ILU6UE*<#iBZ|B~7`zMMmn3{EZnOJ`~b`O=xw8K3| zS{e%Vq?(_r8_&s+*crpTr=G6Mfcr18vV-J%5^$=K5TAHizmTzCmMi>&R$bU ze;mzGW42_DH@1vD64k-chW#T$S${ zpEitt;@bCZ`nGO0N`GRCj0v~%r7|No$$83VqK|q+XjC?ukGKj*21q&7|*bVyOT=W(ejO2Tp`0M4abyrOjYym*Y7d;#JTYSv% zy8+qpC@i0eWV^rQz{`#=w__!DZgeLAU=}1#14C3rTwWU%X~uVgVOIqH0;Ps9_@~?i z9K`kiBJ%x*v)Co%bx=zi+L&z?aZ!$BcdVBfy%eMV88OpC^c6p73elU4itsZ~;h5Yg z^Q}7G|6*D%J5G_hxNq2BW^}5ya`?ZRSXcYZ4Y^ z-Y<(>AQrQi$n~Qfw_v6_sAy|>g-&@dSa1)xT}ykUGo!6<$WimCn2>;r5%mncnV8te zvzimCdc{T;(p)NStMqM=HC>fcKr@VS+!k?OMECFx>cwR>wPSl373*ex4&vt&kTf+5FEvet4rR6$zr&7GLaIUCsAbv!DxHDSnWOt1lDbx#AMMi z)1-9_s=pS{GVD+j$XyH6G7RSt{IuSeokb4IgEmt0blCKNU~q-}~+pBFqf zK|jwa;g|@!>1Yscx_!QiYTuW^*`Ly9Y!K2}-kSP8R4|)&WE5jYUm} zQx}Q0*$mwN&~5;m!sS)v=7oH0by9{>C3IzWge@QL=_yju4VF<^e}M~?YNohJJ(*aG ze>LqqpX7bHPcbdCDA06GTtLDQS&^@4*sbZeP6RT^n!v}JOVr|TiAHL!#faXKph@F* zRncxC5vqMDsrdNp&F!Aew>dmYNOqd-H zJv2Cw)m1xs^G1^D7-AeP{oa1zN_3^5bAu_SwL`wMtbwiB;ehI>D#1B0DF?R`!z_L>NVgKzx0s zkmUq#<$JLc-3s2W+aKv7#NQzSf*>BwfWYZ%4qX{5io$10sT$@3=c*Y1`wiW3E$si6 zwvW$VlU$Mx=+b-$0uc&{=7H21wDaLSlI)@S2hyD9(TdVg#5UO3*K>(`k`JIN#`0*f zok!`tE5j*nY0H1a`@FL6ua8zD7$2v#yQu8Bk`;L_x>e{EEl$=<@2MBhy(T(xiHu_)>kY^kif^tkY{Qs!{& z%FJx7gx#!}X2QkF7*4USR2+hnWM3vo^f2rJ?fIi4Du2o{I1HrKa|1{NT0?ek&)goY$UDY`^LH%)gi3FRvF4NAER3-J9se z`2~6_5U=JaB4xTf8BJ9RmiA3U$o3Z8<`#-QyEBgT+B1_z<{( z1aVoeAUIs|L~_0%v(6tH4V6sDZ~wH&hZn56`Q5cft3+>CNMo(6KfY!EO5X z$29~g4>zH2Iy&c-F*5!QJ|UB`KcP~QYi^(|P|dyg^ql@1UVkX>u%mV`9#}7!`AI{-hI@Rv|+=23e{UOveqm z1S0P7hxE^vEUF$9$#pK~yw;Tb7Awcq_O4^Dr#?N`FG>$f&w+oke?O*{8Q&`^9Q<^w zsd|JP8#Df!lD1P$oMM%CovdbeQ(H zZqb>%YVH4ZDdWm#>8oP;16DuZo~iJymCM7tp2%`(R^>Uyp_&O}sY{tgqqcD04^UwlK7mPhW~iuD027A}yS%GWc6(oE%z)3v40C=Y1guZxg~F?c|@L_>Mg ziA+Rae||J?ERLHESs|GdI(x$^-)^eSOTx_0rKsE@`&p;hSl~~tyXvCaJh-_kKPmXv z-Y;%Vw~5N>-k=hFKP08CD$>aSYhjX3)yev~Ffm)Z-+%87tD+K0)63QdrLUDei)p*f zD_dN~;w_+X;?cQEXQmqd=rEbuJ$a~G@(DI${*!mB^e zct0?j*jz0NlSP&JiLYa7$QpW)ZDz;}(DUa2_!HAgFvVr_i$P59Z3?6l6U@~E8m4MV zL90VSc*3OYJ#Dir{Gib8S(~c`rqV5)zh)nlIzASOtI8?VUV8LMz|N`S^{SGY$2!r& zYD$N5=J-#se|J~^f%X-Q$6NdRTZdBo`sQ9L@+^haB60t4&a>8d_)Pn1Youe#5&F27 z@z?7spt2`NP{bkg_UO?xezkvSzgCn3vx{k0jKn1kXJy3BirM4<^hiLLN##U^>`~}g z%xnL_{Cik*y;RLC5ECcMtXHHp6Tr0Vtv4U79Bw^z z(iHx9D0vwpXc`qYHGK^(o^(p2Qt#QUvy!8u#WR9jR4pGSsuR zSrh8sJZ7b7FBRugdWUZ8weV-5?bh31l)-6e6b_cFYJ1;TkoG8`NvJ6FW;XJ@;oS_j zym^}4q}!;U5Nq;nix1J0JW^X-o1d=4kWOX9jo#BMeeNP``;(higrb(WrcDUuAAYw% z0rU{e(+ys99e96rKQa25k|t_}Iwa3UPSReu1Cstc6E>6T ztDsz+T`V8vH9Jq;Ny#Iw7=tuM=@O0?Yt9n|eT}wF&F~n=;&-fzhpe)pyw;gPNN3f10CUkWrJQWD@$w@4|y<%6qTkB25Y!u>6A6JX!bi z!6~a3n(8+AG2Cz6D&$z~xBcAo>1ClTQso{JS1`U`lxmGK?G)KxQujO-ajFzd$TuGU zi*CFpEG}MqOzCwvc3h3whIe7>N%tlvO4YdlPaFj`kHZr~a@pv2onq!Gk$fHg7PPK0 zkPn6NM`vw`_8BMI{(}?)ERgo^Y0&CL7|OViNc(C7td&ILngyxmI^x z=09dZch+**=FgZB;Z{beFY9aMk*g2zYL7eJ!;?461MwNdW{~YYDd%V?gOot{~Y zS<=MI%}_Elag=W0+OqCb3!Wx{h`}%0J#J|*cH$Eo)2vs4q7n^+mt=OtoUa&w z6-&uPDC&8W+Vb%o>^4fTwv>Dvi9qj$0XBro-E~>%_V{wrf;0(}=WX#%>)d&}0hvk` z&jA$^!e@2bdphG03H$o0WN<}UUszK|73Csc8mP1V z^{9=2fxe5TFWXpi zd0$~r7phFnPCbxIxz0Nw1`_0o#4XXH1LL!(L=?6sjcUN4=V5m-gAd2`gfS^kMeUM; z>WVyoTAB&+yr4$7_Fr)f6;o3_IU1HD@P6?f)qY2S7(mPV8 z>4+OpA$z6yZd;z}fNrKQQo#F1cQ5@o2q-|{6V-%U?kHQU5~s|wYDx|U>Lfu_GZW?^$P2VCK= zZX#KIwAR6RQ1$-I?1L`c1pxwG6a0y{=HPPuzO{z(8h>=ahRrkbUD$BsXeeY z4DNbp{gQ;XK%V#keZpvrjtV z$rI`Aj@YtkR~@s17iD;MA`o|4V!cx+zdFG_(Ko`GAG+jq3vG}&Sc2s<>ktIPq+c7# zUBat7`gvNPD$~}`d%>LDS;4mO#uY#1q#H%NM%iF5c6F_ZD1bQ8udSH4#>q9;@c{2{ zO-E)kF)*yWH{K5B;JONWc=er`lb*w$ByWnIL6!$p`3Dtq!&>Q!JC3Cdoy8LFwdx75 zWNK%KGMAFK`gx)Rrew=`GA*BRnP#e#gtj)<(1{Bg6rsi6-?ljsu{oW>-@>0vC%6|o z=tgNh-!qT<>Sam?zYI!!*FX@1DZ6J5s?AksA!qfNuH1OHC>fjJ-%Pg1*-0@w_b#)p zY6C%)B{(Wz6I~-qxn&c@+*-)BUPsoD=LN21)zEEC@Rz`lxYv6S%+|*W>iOT`hjsxu zwHQnSq)eZEAKG;kCQ!JzTcpUPJP*j7`0rfr`0IZNf8jCW=HbI$ZDpv?(!xN@R8e$_L# z7g*IxpeKz1Fa*Ddc)ZY{zC@^4-f~sd&p;>w5;dt>%w0vVde$5GjE>U?g6R&U#V5~5 z^)2n!zGsFpw&~EVbZkwmI77kc0s~ZbB62v3M4Ad@D-OjK zFg%C!@Bh5C&q8|N#5CWZ;k)&Ws}8R-u{W9s1f#)%4Jk!1gO8OT!6v_uIB3D=sY!Oj97X7}KAO}<>`@VA&$A`7>w!s8;NQR|tKdFIcv z%g(}+x%ey>Qrz~X7+)Rh8G{8qO_c)gW9>_z9j7N)@KM?R?#cJg{uJhqJUNqpP5CGf z1-yJCn!xk^u7c3}F=GKS4#c4vMT6Osz#-SNk|KXL45dCW0x2|t(R~M$YIP+VSMs2G zAnjmpkzKJeW-zZlg(NMRAG&Cnf9=yBt=AF)Xdgps|5$xn!@Y<4S@+178}a5XLo$vb zR~Cyt+6GGG{V)jmX-!51>T!u!?6W_R0#*i#toC)X2a#<*4O9v<8t|O#Z=24Blg*1) zSkv1)6*D#U6}Au(4IH1JEL!R&5bpZdXOil1)0SLkWooRYKn0z4jgcBtgH`xlxi!w9 zKF)KImUjybaC5rP9oOFMzy;-d7P+J$RQ)pU_4=MAM_R}`M^E##j%93i zv-yA6+ZK&UVkuOPo_ZuiCwh(jg({JK;jIFM0$ZNa@g1HU0#D8a)I9DB%c49-jaYj! z@vM~5l>%zkY9vJ_bVY&RWs|Wq!bh}!Di>2-2c^=*;JGvlZ!ZqE@KMiIlwl{^Wr%*pFblqKiX zJx|&KR((;YrkdeK&On`cCQfUXk3D|MDnA4+gQ5*pA?nG8boD!zp4dT31s)SFv4syZ zC0Ir$(VJ+dyOO|oxsn5nsUxn^Sv*(hgzdFU-`6!y!lnD_oIL)v)50!n6lnd7V{FdD zA&GWpu=v}X7|yPt9nm&lrcPp`HL9`Qx#9%03^Il;cZ}{k#f#4|8b!fIX~-Q?3U888%Q#6+XNBh z$3plum{F~d10A~yowt)tw%i20=h}PX#P?G<^r9vd0AigKJ)V>t{pMp~gtD$Y&!10DAD(5jUJ2JAM{H6!7EsN;+J-IA5t~KFg z`xn#K8wdwSR&Ewv6Q(tCY04+kgDa>anJ{m4cRq&3XE!s*(RnP{OHV^t_CyCKAs-K%f;m8qa$UwuvGpBvvwr%smTp->QCvY zgf0V`Wq@Gh&cfh_eAWi5c$cDHWxZ#$4vv?fA+ZmWGY>6nA*Z8g1_kuBfSCuK*X|SN zR@?0!*IpNR0;Ax>J@l8&SbYbK@Nu8fN<>9H#i){g8SlfkP&suz76bfYqCyaz3aOtCWrag z>~terzViw^lx=vZa*z-d99!fEU9Om;acGW}odyF`F$}NBYgpvrfm8}ClC3Fh#=Ff% zsG0rXg8K!&%s)sgUMOxV$0p;%z~dFuQ|0EYKoi5}AT3H<3$iMU8Bb;iPtoG9>9?AN zCN>jnA*S0SKjvuO11_ki;bZWyVI(d#%mOi_4(Xjvs3VoC^+1iWbF`K`dzQ{)%E4JU z3Y0;t47XNj>mYIaiH`lV;#gUr14GKod6dlnjY;rEa;99R?bRqp>#Otg)R;?>4wp`B*RuU^RggujdsT?=lvYf z`mMgYY?{^|>mM8PWAX;2rEiXjuI4DdJ*U&v5`%xz}~Lp))l`KpE{Zo<{@Q z0%m@5OWqIsR;waNxAe(s?Z3ONN_(9SE*ni8jG~4gT-$n>td?d(3F-+40)u=72*P{T zUwvnidgQGC<(YEBPno@@3UVXJZJE7{5K%@J58J!^{~dse52iUr*=cJyK>-sHIQvHs2NKt~$%0=We+|od^+)3? zyw4(E_0PxgTdc+~mZ8vh@QQ7Y&o@!ck*KC#@Wk|9#c_t18*}KdMQDNDi9Ps7ESvWtLYo#QgSKI z2mA9^`C_C4d};Yg##W$;zQp#@NW0?%@yQ5ky`9$Ft~5fPUA6;5c^q zYXbRjjgR3&9@w{=phKnKBT5xuvL4Xi@V&-$y71}riPnvlz9QlKE#RTxyw2f;Uz6{i zz5zsa6VKck)z;U<>vQr$g#@Egnpq;UDMbWw3B(%;Lrr--{3l~uop03i;jLqv2Hqwe zs8{YRQ*6T!+cCkaM)_Y7t;E3FdwK7g{bp(HCw&hiHpt4~JxD5k@S@6LvKKC3z87Vz z!qsg~pveaHP(w{STfpd^O}V_dx{1&io&O&UiD_uEt(KBCxlQwFg z!2-@HP`_8<={1K;$+9@mnaD;y@I4~#zY0nEF&#z&;^64lO)MI|}NWtem{6wI!i<=p*+&^TFpf7r| zyyg8+?L_zO=XD~Y0J+F>jr;aA~@2 zjDOzFdYi?p`y5fK|HY+G=u}@dXLr-#1!FXISp~}MqJDHet2A`}rZ1*SqQD5!|2-x9 zoe>%w|1<3CjbPy=nruS5Jab}1jxIN%1Xy*qHK~om&*56hwFPo7?CWk@_G@|a1eS#0 zGC)sgFIFvgoUp3LW$rOIH#{*kNqm=jHt%GySLob8_xcbIe5Fr+n0emOKG@*~>HmIJ zz~)0Pgz-OjaJau~(C#F6lqF3!PXr43p`tAyv5PLAYp=IX*#HcW4U^{Y@3%1Vz}x!t zIMCt>NCf{;Ma+Z4Q%jrwR}S6UX=fVA-qnG;!IU!s$%|_n_*F{0lSGnvjc6Vldb%-mTQ0#Rxhlcwcxu@KW=($tJ-I0;*ZJqUmDJJJOl+WN8rXj2W#F1;sPm3x=j4 zr^*~3TpxUwm$dgBKB2D!Z$9THTRj&)3`Gw;ZVY7zBFrpGV>)ZLM+No|0$=?5WV>_| z(qM?IkBW=6T_SYnPD>2sA?gl?O!j@?M4X0|&1koXi`V0&V8To!Qs}I)Hr{{=u#PM5 z@k5Z=3S)z^2}02;4lJKv!Hr})92laPk*Qkj0?`YFWyeHOEOj@tAv)@v1*~Hf0dWv* zLUH8|PL`vzvlLE$uWM(1I-YPJa#b2*$78oh7@w=ynaEv$)}j|;ZKYVWwIB%3`F?Tp zkHjP3j^uPbcsXDSSdg4-2VB*w?Ib%RxpKi-;9o3D5-MBdp9N8sd+rf1Z>;mtZq zylr5fGLGw=?2^@qFRuXk-yM$>5rML+P}9-~oVCFV2n`Qc>w~M`@4JkO^J=DjG}H-! zPe7rF!PV6~07vFvh!0L7kJ5MRfBHNtX+0(%UgT*t<@8qNT#mp^9Q2qNN$<6L#PFBS zT3-ylzK(mFrrGjuN(#WCMGZ_UKz7@z6^C2 zIuvv|2Q1)=^H~I(FQ(W|I1mef<$v3P%0IQ2@}v}o*oZO=T+sm%=?jIXJOLUxADaF> zj3-ue`>s(+O&1ZUmi1iUDNo1=KxjxDd9JEKrzeXOfjL}s9%lnYAj$y)2x6k@C*9Gl z5iXw#JX)CC#{D?$Cg1n5>ouzJ-v9+<-%G)|A9HIegbK#E#QTeW2c1uA|_Ce#<85;f5MFgmfz0g zKS|h>{Idmu8C&s#w}9rK7gSU|L+m|x?Q7$;Iiyh@p@juQr&jY`*E@@~&erWd?4)rgXk)rsGNY3Rin2z8Yn@giqug!Pr1Uh-vAsJ!P(} z(U|qyhg;=#HmD^F^pdiyMt+5krZh>L&otqHW63vy*3D5-8lz`wM{bbMPPyZkiiWKA9G0P zp^Qj&`|djpAQjG6mK%rNUu;GMXp4EC0ZP_6J6GW_Jke6Z`!BOWA!2YHwY`;inIZkE zk5O=$4~Y`HdcZ)pIx_2+uGQjW9itMJF-o16YoOsxZKNxPqYuHI7+`>EktF{AtZ{VsE2o9U)Ibawf5_x8^o@h!b*+i~=w5lXR?Fk_DY=Q~92?}Hn9 zIR6a?ubwg)#O!^1VBbS~A)pI$$K6iVP4xDM1C|K`LI}VQDe_f?di`}I?hIsK@fj^( zz~lT!#|E6YCI4UC*Z)Qh8G*3%A8zVDTJ`^=)h?dksQ@hoW(s<8<03HnpF0=;YCw7O z7FEY>gZqBRW3H?~ymQlQ@Z;*3`_3_HxTt2k)Tqe+&g>hR7=+9ZN69J8qT z)P~1O2$-%AFX6GGLgOGNx6z~Y?6l{-jI4*_BOol(lkDy?BQ>wz>}{nJ(Dw80uF{kq zmfZwf0dbet)^~Lv!|>1qodY8f^NN9nv**XL3yDA&pvPw!55ujUsDx>zSXB9x8$p1y z)AVD*U$gXDVY^5t)rsZ~;$BqI2*)ALQY=^^3iy1ViBeSLp~i-~thv z?$`SY(bF+YMi;g!|BVUf8S)R;&c|i0x-lx6AEozs1Z2_)GlSRPGNwRswAv)XS)qarjWP>QL6TZkgjl_1I3Hz^z?ti1U$jg!K>ZCs(b}#RKe;= zdO32!!%%HAwz-cw5C-)?)T-m`Euzk&PE)k8%g93echa(a83btkc}_tb?Vh6Fmht}P zA2w2$Jkc*b3UMEe?XapQVVN*Y37Kc`k7=pww0=e12ZE?N#|b&tjgjygh1F_pmK)}2 zZa_r5$ED#8gtA-T+s%cR7iLG>eHwvOO8z{RI+up^WSd58lM9nU6rS2(>R$de3Lh+% zZ@@DIpM7gygO?*ANE}u7-%Mr19vVW_oh+81+fViAdC63_?$}QcxaX5VO)Ix=U^SU< zj^7Y1-D<1e22`-G=-8QdR?Kc|%fShe1Ej;ohcPU3;IV^#%mYCru=_^&_e}BlSoTa? zutszCYj{G~Z72+J>KveIQhbb$U9Hi8kaL!PEhs>bxBoYD9vsLR%cu6ZwYCL_Ra7U0 z_PP71$tpFI76?jz3HTi#t78B}j!@-<^^{U}BmsLD*;^3N z4D9N^@}l1TfDVs9lekG@-KDr2jy}|XfOdYT3|GR}$4LTWf8W$F<6a0#&ARZ@X}7BP zS@=$>x4P%g3Xj##lcKxzywo5dv;dSmsOm9;w3u(*&!9ZVC^FA9O}K~~p|SieV>Vz= z*Tz7pH-2YK8O_NfZc}ZaflO5=MNrg&Y6~#U7=_X3TG_;M*zlWSCm)xs6|3E=dfRh% z5N>UH_o6lN%p?K%7u0~_s+d!X)Mg{^d8&EJd<{r@R<*-pi-ClZu8pH zH-ED*pa8#%_h$UC>Cg{kAUkBmOT}CjW|h#HpT#Vi1*5;dD$=|@P^8(~`B_X4uqYaH zGSco$9sr}jWl)t+i(xnR1}ZH^pukE?iuw92ZVj4a&+%;SMA^YQSUXu7HZ^wGIvGA=5(KPruy)<7 z=;iZh^qjB=AG%*R&Waj6>;$V&buyOVIn!22RkXYV<#qW4s#I;IHyb$wa!FK0(-}cP zUcejWT8f?=JzHN{s6!du#=Xzw|KW zLVI`B_|mZV)NamJc)DlPIel(3$n6W~qDyRipHDA&V|6kzeGn2#cja((v~6&ra6s7{@KoW5Ty z^i|QW^B7gPW{u$TN>??Fh|<#RN1rElruv26m9nDQL0$S?t>C-DULH}kr89Aleuqux zP(?H&cMnuXcM-=>{F$N-`X@$)){$EN;2o$sW{%LUkA6Pm{SfrG>lF0xS?B);D@4Iu z4%@O|C^G$NQ22z%PHS5|uq0B2s~HZPjWXSZjI?gjT7_z7GAvKS3``S6DZ)( zZ^xx3<4>rG8*${a>GFKt$=Tbv3{clVwv@Pw#Ql)NNBY9$eN))9IyXjG2~7ksw-A^qQS#lpwu@2GA#q;OI|Pibg}POA1Pm>$=R8csT?Gi zos#;?AWHK}_8Irn%*eYUkev~-M|LFrPqGrq;P2s7Cz2!8&qb-%)U6B3Oa-W|#@cQl zhr}@6^;}`L4~?lAmuh&mu>Q*VKJ_vfB&Y(QtKnS?2ZvUl&|s8SbH1-3xB77(eOORB!CIrHX*Z0^`y>N314SCX%2XCR^>sT<~DQ+(RrovSmpl@&RhNXb^n4Na^ zvuihriq12njOM>p*b2FPy%4VZ4ST@JH2zsmawdPOZN7q?R#{z7D$*l^Thf!eQXs!) zaVSN@q|>R|8n0Al*@S0(YuqL|Fjc#49v0Rt=w1*I%&B#<;Nv!^sm5LgY-v*v^Z=5&L zzIX&djb57-+ueV9cqIiEUUUn{Rlf=Xp{e7@8vGI{z*zA-Oh2!yT@-2lrw)u?Ll94^ zfv-q(+%y04%SCYkXeSmEpcnz9c1ln*FfB6Dlfi3?3uUQHf5jDf{ zV+{D#L>XO%ICVI)g#fbS%*VPlOhYuzv@=Dw6{ukS$t}$V;EC85{4G)>K;;`5b9vU} zz+6HZ_7J!&=@1xhKG@)r`b(?%sW28pQKz+y_I8wIJ+QwCd?B|24am9EF?I{Q@x$qn zJRhZ$eYvXIqYT_zFO*-B<442ffg5H6PeP&Q`Uk*#UjGtab~AImF{tALQQy`piLf^O z{$nz+mpE@wwDzWrkgerTrEppD#IH@+?HPHv;T{ESpBL%x#WJ+cia$_-K2%yP*8u9u z#jr~N+;a@$4#~klYJtMz=e8wZDEN|KUYm;e4=TUFWjB7P_-NL}20as88 z4(}ILoH@meEYtVwd!b+V>ZM9<2*cWjZBi{&xVP;5dR)J18Ch zZq(F?)LjN|x%|ZP!Bd_|2?N!ZY(?*iE(&reqFm2j&zj{OQaw07QAn6!4EXwl*NY1+ zy|RnF%p{@FqY90IyY9U5e_eQIlHF7hi&i{;ZI{O%8LO7fD~vnA6O}_xNstb$7T7=n z1yJQ#NqPvNLn_oXDwzFM=cfsc)4m7Bx*vxp)|4zz*XJY!px+VchybZ-%duItM}=$WKNvL1`+lD-3sK-&p3sY#e)wv)Uzw}5l5_Pt`^l&E4|+Pf4S4_P z3dpq;E==G)?>Nu4_K)2Mkqm9%AyRz~s~_H!pof#c2fhO{RmS@ONMa5DPbU2xNQk{o zv)X|ueZ9HJjP7@x9{rRWdHxlfheYSAY50%e@_$#H&zaXUi3cwK`g26`Md5tK_)++6 zgj~JLfqfA^LR0qzPy;o`qEoa0q`klif(ry*v-_{Y`r*p?M`6x^<;5DI;ylB5JghN# z`jE$F-F0vB>N)J*`~qtPb|eG59Cu}D0!K_%h|!2Y*9D>Ywd*QO1OxuBsG(p*a;bYi zU4hCR2t=}yB&Y(z0j&y#EXN(uG$w0_@kXXN}XOMnGfApKe<58CbqUvhcra z%#FyK+yF(fo^{@}l7yrI+D&LWk0;Fjbpir$Agn+06rcrgW(IgWcNpR#mq>YkkxLvc z{Zp!1^J1*>OCvi6Z@rNKpE4ULcCi}q96%>crQH@{>k6=4xBXBP9$ttO6g&MOmx4QA zNU_a-D?EQoBY4aHTjAMjP05`3C=a`EN!HuhXBTc-FDkdWTjaC*!iffydJtRa;o|dh z9Bzt11Kl<<=Sr3Q!SmLEat_qxEI>4rvw}5}Z!^2K+*y=sMSG`-sNAm*5 zLLiYLLd^cEzfW&{z0Yt6TD?XOX!kH~0q5Sr@`+XGD&XEwhStE@&aX4b@b@P$uR8+G zC%2%C`O1)fxv^^Jb@4yIAQM7DGoA0oiSphk&;xOQRt%`IXP>l>0y!pGs%xEC#V>b( z32+L)gaYNu^Qnvh4yLA*6;$>%scxn8??)Q8!*Cxobdmt9gjWSO5b~AUyqJ@Az1Ied@2ryMqZ=iTM z(1LXN%W-8Ul&4VD7OtnUGWu7kY8<=lV<$EvF1pFEc9p`y-Z`G_@8Fr=TjsGaug%z8 zKY1hO(Iq4qQ7Y~1j@3V3!CCrnS~!mrruq%4}1rWo6}0HG)QG!cSid#RfvZZ(mf}5UG(n*FGDSr zXaviu?F+DSLsN|?;&`%}oOxv>(p))u=H#SLwe{G03md_3`{qy&r1wBdEg?>({$>QP2ygdm$T1T<{ zv57DSnBrV?KYu0Cka#V-eJrN8XJul2dT;eWdFq;u7+VgK#z?zzv|2GXlLVUQ)i{N>jE`J^^dn4fQe$s)Wp-z=Qa zuRYb4|M3~I%$3dCFRVG*Ly`+8aftfmv$#A15nRl~;5rB56z=jMXba#$mXlgum4xh3 z-ZBGVvC8;KT(W6Tup+007fKxL;ham8aL=ng#dF>?r?;kY250_dL2_KtJUU;U-biEH z1}l(y@A2@wB8Pj1N$il+D?1~u)Fa2NyTVrA31DehS<#5Mp#%*u5^{qF?tcQWdi@VpRJaJbt!$E#MOGU z=|x2}10--(Dp{pxgECTfW%*=C8K_dxBS{WaUMZ(c67}6mvL28-)_ZjB&|Y|JW#H0F zDQ8(3fjABLuu zFkgG)^@bP2Gc#;ya)WmJ#CAM|~!zzXU{LKEP#RK}c>DV7K z$qx-^fjHx~dE|#mvo4jdcsRPry2ooy3N___<_3H4O@1Y^FTd$RM9kO;mJ5eMx6b)O z1VOp6Tv*Ch&_zS*i{5_C*1}uonF@)6xmGq2Uq8%E%mc|wnwAI-6HlJRu&t6x8mpp& zv}7t>El7jOOC|TBBg8BRrrgAIAFF;6bK^>aAa+g-_koKC5)u z*M|_tQPY&4+5q=-gtY*akQ?&Pg{du`d;rx`3piLm z>wNeC3EMr*CsS6Xf>+hOF$L4kLFqcQ7^aTy!-Po*nVA!<7z|*tLf(?wo&#)lDfD(s zL7-Z^W`f|;<}m;J9MHep1jPPQ9Rq?`?Zzgh=Q&Yr=}NA4D*h&_v_Lmhsc^8!y|w#O z^yPJjmjsa&H;GcXYK{a-GRlQVbT9t!gVQ?(7Heaz$orQN{q0X8FfEk8`0X!~G8tSL zl?=6**$dWGHX$C{-g6xvWNGfdZmSh*g;w`n?XlNRuF;GPucE}oaAhW%wUym3I?Q@= zx4A0}VSR3r1#${?&I{v4)%SPs^x{LmCq0N0V2o5In|G_2Nk9vYhF;X#=2X_7yD{!g zl6>vE;}u`V5|r;&7z8~#6Y*!O7JTjNg*@XNg$(li2Y*ZR%2+V~1@-)iZ4b5}fy+43 zE|lXaO)lN`#l?muoT?!cnK1;iAqVa2eLLfJ5o?fjW;(i7d%AvqeSI<|v=%zOd1P&X ztLu?HZ@hCO15lA5udwoHA)t`)45+00a`;d$p{Qc#W(y_c1_nRhD6e)x8c2-AG19!Q zq%07-gO%h_yT&Nxk74zH2nOW&Z{Q0+$tKYRk8RAEX#v)+wMSxBpHdTY3A0)=$caUC-G*@#Gy40#9Buc z(x=2z)m0ifMzsxgpP<^S%Ysuy@CPINFSQunHAL6L%l`0eJc78S13G z*V|7n|8AdK0n3hM0dRwz37y$+CT9R(P-F*=erHL1!RpMD^2uL;1v9=@dZw0V!)7Rc z58D)i{MyTOueVBPw#EF9GT&oeJso)+yTFD#r=r42LCzA<-z$s;@^YljKh$aIxhoXR zdOI=#QQEbDa&e|~Up?7Z zVYht_4P(=ihr8qTOrx!0PEaE$5HdnQh!6xq1PLV^&$-$Nfofhn1%S=X(8%u`jvC2! zWM3)gE6(ZZCzvOYhbM{Glupk0i zauivCR6C$qZCxL9IT()0(f$#pHGHeULsre%Q3urYZJK*m2nm}NS^r=VUaq2-M3r!wZu5AT5t*oTq-TMB)WMwg--udSd34+8UE4umJ0#*XPC zV$BIo>Tw10;c3&wYh46okZuH=3e$LE_{S7E@E}kCk8O&`XykZdg{-!EmKsI&u;jq1H zb<6Q>*qeI|O;}n~a+;qfXU$wGLSK@E`A$zN^+ys`Momh&*a~qR`(Z&PE`nWQgg5WA zU7Y%lCV_FM8I!`A)PlJ~)w)j?VXPCo9FF}OkYX20!mRI9JS=pAgJgiILEezID>?cd z$Q3$(gpvO32ea8%dRcbH36Hk@Mw=N5QD94GY#uK9N_$hrEi-2WFl`GKW~f;GUt=rU z5rQ-SNu6O`LkZcx9P>H}3nvu2{pffON(+X!N(m~Xd22=6M%I?eEXamQ`t zDoNPDWY0N*WSdK9296-4l`FAGVnvY-e?R|ydh<*$1N9JGlkwA=sn}UEpnUPTM zwVu)t{JtU0R4SL#GKmpk%a=$5*q_^N6HeFnY^Z&d_e`(ylE^tvUw93at$YxW?QR!LY;oRLi-jW%+o8*nHB&Rl`K;WE2~G^ErWrW9l3NLbomz8U7_K?6B4{14x}Qi*(%(pvzP znfT9Rd~+vY2_osh6E>NlEXStr;K}I-BH!dOz`go00f)X)gX{+hwFnAqdAMZhde3y# zQY-9t*pJ`&x)+Ku>{C#@qM=QuCO8s>4M_{luno|d1dc!_M3iDt)EN3U2w&fa5qaBC z#Ki%q)DiYrZO$VS>jL^b>hCoq?^_3(E)XlTZv_kL6}B8=WPR?tB&?6Mh;tF7K8A`L zZ{9*ROCndNW>`GYl-A{H^q`M?p3<_e3PYTLe%4|!&wxSvXCvZ90oZV+Sp7|~Xx_qI{x!bp{I&CPbsF+3hGH5x$LRHiu&9*)-j)%LASX!2I8kB50 z>S}FF_;=IHTyrpi2Xd?8YADO7)78ii9AI6$>J$URcX)-@lM~I-zi-a}_dkLR;M!2B zi<3~$NhiSYN$ku{n@n^BjpPf%P}KAq`8oHyikh`pIbM76%r{t~wEUEUASx~0aF$$3 zw#KN(J0kPZh>|ONF06JWg9e|Qh&Ka`P-;Y$$CG6pJQT%T=WKRbyXSO)*6!Rk&gub# zG6ZO5UL3sNdofFWV4HvF9>N;DjjdCeLrq=1){?F9GRj`m?(GW~y%ufEx66n;?gK%+ z*I9;Df@6;|3NZ|0Iou|g6mheCws{h+uj#{O+&!IP5z-D;=z>y|=nXui7Vep8ni_bZG&z~%1;dsIQM@%Wc@vJ;k^msY5gPG%2_x37B(zDv=!a;jB zvvf2bt`HgX4h9{&7%kVT7_HxYzOnnyy!Iqm#Hl@3?v;O-`ni0h(s!;`w3FrY(cf#J zKmu_~1h>NNPADY@TZhMJ5=OEd%rx57DI!9c7@eto>FEoOzS3gv59z4kWHq8{kTb6q z>Ub4yV~7XGE!rLrbT6Ti%MY&l$B1|*-rKZWD)aoQ7HM!-HwW9pt6uajto;_{DC+aG zP8^Gb{+G-N2TOPv=C2Cm3@?t(Qj;Ceu8k5aU{|QEMy7Ii{<(TY!27iE?~$sI1~4i6 zma9=uAJ?7ln>h9yRKR?LUiZkr@kEN9hiZ0?N_rK+b;eKQco?xGviCt-cK<}59!PGy z#|geI(vm-cu=1|ipZ=`a(Ddi9kHuM<$dRb^u(40Lgb2Ig@6Vk9vkDtA<$ugiNZ`9A z20M03_WZ&{n!J<(B5#xV2U)c_8`NBo-}*t#7=t@>b_elDp^rOO`=|zK=WTGB&4KHb z1|OPmXUn&2vHc!wB{E{JcIx&}&CI}<_rkAfDv)+QJIinew}3oTv{Y4eRh;1;W0E9| z@MZN@p_+axNx=m{`rxAtUw`gzfr{qafr$I=?J6YAX3Bo{hCk`#z4jsXyCf>(hRJkvN#uC)+3yXML$SV(E@CScXyE+ zUA32%D4k++)Xa~Fi9KqLzR?7FvhznINk#$xBf%hlHQa1EZ-)paNx~`ht#YSnF9=o* zOTu>r%^>P;(h0c8@*w z{*hVNFz3k0Dvu^;fOHt(fdh%W6I@2l4u0{t)z3ddx4vR=8CBrgwK2aM*BpQ|NnNS3DA?Iwmu_9v92!nSF}nP+?A+6zLYs&N7itrqhS@< ze#B%g5|O0R@)bcEfE5G@vEya&%Z}LsOT-*oI1YWSsutEz@T~sa(5afiOMM)BjXnb{ z3N;Us2MVHiS%~=jA2JKhovqDtD0RVrs@8#J$5gEmd&9i5IE96>a*&sS&dj_o^7P#w z<1(C6;y!aO^W&kRQzl1Bdsvj<3|}#IZ!4~Q;_HR2=%!CW_NbuP_yH4U$%>BU4@MJS z0)-@m<-9FC@<}vcvx$xq(LAD6KSk(H+nv=yxeec*6|c%3Z3aQyN!WW;=qZzW}99 z0T40UGNWBtpF9cNd%%3U^tmFcibKZ^0G(6n@#nNR&fZY9H6*Uss6^ggx(UyBnQb!U zbz*Wze5Pm%^Gy4@{50_tQ}S7;;CHe9$_T7q2$^e=)EwU|Is*Cthj$i(Y!sBg*h_;l7jBACY?(qlHiAoQ%Bz{`osaf!o|iXzTC1HFtUh)+LL{QjSM!Y=?tKf1Ez1b`2vrmaRR_fdJT{h4W?p}E5s zp#S{1%K=Z4a8YZ`+bUAPZT)eZOqC2M2@@Sm(EhtqS=D7a?&y&oxgi`gEA#PR=`B7%a z0lZm3u`6H?T6(Rj(`pPeki)|H8XeXQxrrN@Tt_dtRhL~ za8mV}ShZ2u;zY7jJcnu*FVCoK%pc-thbDmun6-J{tCo?PyVWgM ziOHJ1_QxFJ{}_ggrVtDA*l-*Q9z9TjQ;|7R*PW(hZHB%|2q!RwR1rn?X>8{%s_T*b zA^X~M(I9L2a6(Yksi^kgLj<=~>B8HBgvEQ!ULWe8%F$gCJUVvNJox@X0^u%bxH+iu zWwKEqMF3v-73hz&ehJALT~Iqf(bKsP_-7c(qih`6bGu1>r6s{cNiDA69q7w(j%CH| zLZLGJCA(K2-_R&oCo@ew*fdP{yg3>)gGge8{M=4;lkv6q)(3WW2v^Idt87P%i)v%M zBd!D&`FWZgcX4ZvOSt19^{a8mupwMZ-(}crRL3&{7?^r5<+D941cXVO(cX1bbLd$& zoklhJF7|KMcI5;?bJgB>m7J-> zw2}psn-hMS1{gu5gECBht|7n+Y(P?6O$-Om?p%Vi$DT*-R&O|IB(!Hfkrq{54hR= zen19CRSazYf`sk)DW?N@3G#JY6+mq{fGfPY;xw4B(Ie(k0^PU?6d)xP(^Y#$p6+~x z!5;;VnsEMI{@8cQ1`nFK1LN>Em@S3eV@U|0wl<9`l(3TPIDq9e#M}Pp$fc@3`0)0T z{{vARRf@Y+&xAP^i}hGqKBOeGk-Bp1DAoD=7cD}ls-^9x{_o2oFume<`^zjP?Ry~) z1Y{Dbb-On!c0sBRbEO@5TQRE*x)+k$2s$b$esX-YU9p&`cW)qA9`8W7RITGS2=Q*~ z6ekPHq!G4@k@<1`eGY7VxFGd({n^$hMR17s5aLsRBNO8|cdo+P!oqCJ_CrKewUw}a z{*~5MY=1x+gvO=d;3?lambq5kQJaOLq`|C&mjObc{`uo(0WIKeAR3^`5EK;ytASkb h5$6-oU)b1#ocI28)giYF^kjm-F01{Wp=|W<-vAa3;~fA1 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 218/239] 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 219/239] 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 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 220/239] 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 221/239] 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 222/239] 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 223/239] 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 224/239] 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 225/239] 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 226/239] 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 227/239] 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 228/239] 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 229/239] 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 230/239] 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 231/239] 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"] From 59c3510a0219fe726e52816e4d1ff20015455794 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 22 Feb 2023 11:26:06 +0100 Subject: [PATCH 232/239] don't add opacity to layers info --- openpype/hosts/tvpaint/api/lib.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/tvpaint/api/lib.py b/openpype/hosts/tvpaint/api/lib.py index 312a211d49..a0cf761870 100644 --- a/openpype/hosts/tvpaint/api/lib.py +++ b/openpype/hosts/tvpaint/api/lib.py @@ -50,7 +50,8 @@ def parse_layers_data(data): "group_id": int(group_id), "visible": visible == "ON", "position": int(position), - "opacity": int(opacity), + # Opacity from 'tv_layerinfo' is always set to '0' so it's unusable + # "opacity": int(opacity), "name": name, "type": layer_type, "frame_start": int(frame_start), From 0f8a9c58a758fda407d777232112b7723d0e4332 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 22 Feb 2023 11:26:22 +0100 Subject: [PATCH 233/239] added is_current information to layers info --- openpype/hosts/tvpaint/api/lib.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/tvpaint/api/lib.py b/openpype/hosts/tvpaint/api/lib.py index a0cf761870..49846d7f29 100644 --- a/openpype/hosts/tvpaint/api/lib.py +++ b/openpype/hosts/tvpaint/api/lib.py @@ -43,7 +43,7 @@ def parse_layers_data(data): layer_id, group_id, visible, position, opacity, name, layer_type, frame_start, frame_end, prelighttable, postlighttable, - selected, editable, sencil_state + selected, editable, sencil_state, is_current ) = layer_raw.split("|") layer = { "layer_id": int(layer_id), @@ -60,7 +60,8 @@ def parse_layers_data(data): "postlighttable": postlighttable == "1", "selected": selected == "1", "editable": editable == "1", - "sencil_state": sencil_state + "sencil_state": sencil_state, + "is_current": is_current == "1" } layers.append(layer) return layers @@ -88,15 +89,17 @@ def get_layers_data_george_script(output_filepath, layer_ids=None): " selected editable sencilState" ), # Check if layer ID match `tv_LayerCurrentID` + "is_current=0", "IF CMP(current_layer_id, layer_id)==1", # - mark layer as selected if layer id match to current layer id + "is_current=1", "selected=1", "END", # Prepare line with data separated by "|" ( "line = layer_id'|'group_id'|'visible'|'position'|'opacity'|'" "name'|'type'|'startFrame'|'endFrame'|'prelighttable'|'" - "postlighttable'|'selected'|'editable'|'sencilState" + "postlighttable'|'selected'|'editable'|'sencilState'|'is_current" ), # Write data to output file "tv_writetextfile \"strict\" \"append\" '\"'output_path'\"' line", From 80151ca8800ca3fdcbcdebc092f27f6277201aff Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 22 Feb 2023 11:39:50 +0100 Subject: [PATCH 234/239] disable review on render layer/pass by default --- openpype/settings/defaults/project_settings/tvpaint.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/settings/defaults/project_settings/tvpaint.json b/openpype/settings/defaults/project_settings/tvpaint.json index e06a67a254..9173a8c3d5 100644 --- a/openpype/settings/defaults/project_settings/tvpaint.json +++ b/openpype/settings/defaults/project_settings/tvpaint.json @@ -31,13 +31,13 @@ "default_variants": [] }, "create_render_layer": { - "mark_for_review": true, + "mark_for_review": false, "default_pass_name": "beauty", "default_variant": "Main", "default_variants": [] }, "create_render_pass": { - "mark_for_review": true, + "mark_for_review": false, "default_variant": "Main", "default_variants": [] }, From abc36c8357e0773e35b5c312663d80ac3afbbfb1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 22 Feb 2023 12:08:08 +0100 Subject: [PATCH 235/239] fix detailed descriptions --- openpype/hosts/tvpaint/plugins/create/create_render.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render.py b/openpype/hosts/tvpaint/plugins/create/create_render.py index 7e85977b11..d996c06818 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render.py @@ -66,7 +66,7 @@ RENDER_LAYER_DETAILED_DESCRIPTIONS = ( Be aware Render Layer is not TVPaint layer. All TVPaint layers in the scene with the color group id are rendered in the -beauty pass. To create sub passes use Render Layer creator which is +beauty pass. To create sub passes use Render Pass creator which is dependent on existence of render layer instance. The group can represent an asset (tree) or different part of scene that consist @@ -82,8 +82,8 @@ could be Render Layer which has 'Arm', 'Head' and 'Body' as Render Passes. RENDER_PASS_DETAILED_DESCRIPTIONS = ( """Render Pass is sub part of Render Layer. -Render Pass can consist of one or more TVPaint layers. Render Layers must -belong to a Render Layer. Marker TVPaint layers will change it's group color +Render Pass can consist of one or more TVPaint layers. Render Pass must +belong to a Render Layer. Marked TVPaint layers will change it's group color to match group color of Render Layer. """ ) From 79a40902e0a16518097240dd8bffd19c90fecbdd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 23 Feb 2023 14:49:02 +0100 Subject: [PATCH 236/239] Better error message --- openpype/hosts/tvpaint/plugins/create/create_render.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render.py b/openpype/hosts/tvpaint/plugins/create/create_render.py index d996c06818..563d16f847 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render.py @@ -461,7 +461,10 @@ class CreateRenderPass(TVPaintCreator): "render_layer_instance_id" ) if not render_layer_instance_id: - raise CreatorError("Missing RenderLayer instance") + raise CreatorError(( + "You cannot create a Render Pass without a Render Layer." + " Please select one first" + )) render_layer_instance = self.create_context.instances_by_id.get( render_layer_instance_id From ba792f648b6aeea6299a5137afd23de4dc677a3f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 23 Feb 2023 14:52:30 +0100 Subject: [PATCH 237/239] use 'list_instances' instead of existing instances --- .../hosts/tvpaint/plugins/create/create_render.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render.py b/openpype/hosts/tvpaint/plugins/create/create_render.py index 563d16f847..6a3788cc08 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render.py @@ -601,13 +601,16 @@ class CreateRenderPass(TVPaintCreator): ] def get_pre_create_attr_defs(self): + # Find available Render Layers + # - instances are created after creators reset + current_instances = self.host.list_instances() render_layers = [ { - "value": instance.id, - "label": instance.label + "value": instance["instance_id"], + "label": instance["subset"] } - for instance in self.create_context.instances - if instance.creator_identifier == CreateRenderlayer.identifier + for instance in current_instances + if instance["creator_identifier"] == CreateRenderlayer.identifier ] if not render_layers: render_layers.append({"value": None, "label": "N/A"}) From 8218e18edf352d377c901197ecf9408988e974e2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 23 Feb 2023 14:52:53 +0100 Subject: [PATCH 238/239] added label with hint for artist --- openpype/hosts/tvpaint/plugins/create/create_render.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render.py b/openpype/hosts/tvpaint/plugins/create/create_render.py index 6a3788cc08..d356a07687 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render.py @@ -41,6 +41,7 @@ from openpype.client import get_asset_by_name from openpype.lib import ( prepare_template_data, AbstractAttrDef, + UILabelDef, UISeparatorDef, EnumDef, TextDef, @@ -621,6 +622,9 @@ class CreateRenderPass(TVPaintCreator): label="Render Layer", items=render_layers ), + UILabelDef( + "NOTE: Try to hit refresh if you don't see a Render Layer" + ), BoolDef( "mark_for_review", label="Review", From 227f1402fe03590b4350124db0a3894335967c04 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 23 Feb 2023 14:57:34 +0100 Subject: [PATCH 239/239] change how instance attributes are filled --- .../tvpaint/plugins/create/create_render.py | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render.py b/openpype/hosts/tvpaint/plugins/create/create_render.py index d356a07687..9711024c79 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render.py @@ -633,7 +633,34 @@ class CreateRenderPass(TVPaintCreator): ] def get_instance_attr_defs(self): - return self.get_pre_create_attr_defs() + # Find available Render Layers + current_instances = self.create_context.instances + render_layers = [ + { + "value": instance.id, + "label": instance.label + } + for instance in current_instances + if instance.creator_identifier == CreateRenderlayer.identifier + ] + if not render_layers: + render_layers.append({"value": None, "label": "N/A"}) + + return [ + EnumDef( + "render_layer_instance_id", + label="Render Layer", + items=render_layers + ), + UILabelDef( + "NOTE: Try to hit refresh if you don't see a Render Layer" + ), + BoolDef( + "mark_for_review", + label="Review", + default=self.mark_for_review + ) + ] class TVPaintAutoDetectRenderCreator(TVPaintCreator):