From 95743f29a3e67baa6d39f1975fd001575c8abdfb Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 30 Jan 2025 14:35:23 +0100 Subject: [PATCH 001/108] Add ACES 1.3 Studio OCIO config option - Introduced new OCIO config for ACES 1.3 Studio. - Updated paths with relevant labels and descriptions. - Ensured compatibility with OCIO v2 requirements. --- server/settings/main.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/server/settings/main.py b/server/settings/main.py index 249bab85fd..c2f5c63f42 100644 --- a/server/settings/main.py +++ b/server/settings/main.py @@ -71,6 +71,12 @@ def _fallback_ocio_config_profile_types(): def _ocio_built_in_paths(): return [ + { + "value": "{BUILTIN_OCIO_ROOT}/aces_1.3/studio-config-v1.0.0_aces-v1.3_ocio-v2.0.ocio", + "label": "ACES 1.3 Studio (OCIO v2)", + "description": ( + "Aces 1.3 Studio OCIO config file. Requires OCIO v2.") + }, { "value": "{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio", "label": "ACES 1.2", From 8ff258983a54a7f665a12460e05eecfaf7a4426c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 30 Jan 2025 15:01:11 +0100 Subject: [PATCH 002/108] Update OCIO config path for clarity Refined the OCIO built-in paths to improve readability. Added a comment to ignore line length warning for better code style adherence. --- server/settings/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/settings/main.py b/server/settings/main.py index c2f5c63f42..261bd7fc04 100644 --- a/server/settings/main.py +++ b/server/settings/main.py @@ -72,7 +72,7 @@ def _fallback_ocio_config_profile_types(): def _ocio_built_in_paths(): return [ { - "value": "{BUILTIN_OCIO_ROOT}/aces_1.3/studio-config-v1.0.0_aces-v1.3_ocio-v2.0.ocio", + "value": "{BUILTIN_OCIO_ROOT}/aces_1.3/studio-config-v1.0.0_aces-v1.3_ocio-v2.0.ocio", # noqa: E501 "label": "ACES 1.3 Studio (OCIO v2)", "description": ( "Aces 1.3 Studio OCIO config file. Requires OCIO v2.") From d0e591067156b381c3ced96ed6703b013aeced7d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 12 Mar 2025 17:47:21 +0100 Subject: [PATCH 003/108] Remove long deprecated `LoaderPlugin.fname` property Make sure to remove all usages of `self.fname` from any `LoaderPlugin` in your addons --- client/ayon_core/pipeline/load/plugins.py | 13 ------------- client/ayon_core/pipeline/load/utils.py | 6 ------ 2 files changed, 19 deletions(-) diff --git a/client/ayon_core/pipeline/load/plugins.py b/client/ayon_core/pipeline/load/plugins.py index 1fb906fd65..6075916369 100644 --- a/client/ayon_core/pipeline/load/plugins.py +++ b/client/ayon_core/pipeline/load/plugins.py @@ -229,19 +229,6 @@ class LoaderPlugin(list): """ return cls.options or [] - @property - def fname(self): - """Backwards compatibility with deprecation warning""" - - self.log.warning(( - "DEPRECATION WARNING: Source - Loader plugin {}." - " The 'fname' property on the Loader plugin will be removed in" - " future versions of OpenPype. Planned version to drop the support" - " is 3.16.6 or 3.17.0." - ).format(self.__class__.__name__)) - if hasattr(self, "_fname"): - return self._fname - @classmethod def get_representation_name_aliases(cls, representation_name: str): """Return representation names to which switching is allowed from diff --git a/client/ayon_core/pipeline/load/utils.py b/client/ayon_core/pipeline/load/utils.py index de8e1676e7..b130161190 100644 --- a/client/ayon_core/pipeline/load/utils.py +++ b/client/ayon_core/pipeline/load/utils.py @@ -316,12 +316,6 @@ def load_with_repre_context( ) loader = Loader() - - # Backwards compatibility: Originally the loader's __init__ required the - # representation context to set `fname` attribute to the filename to load - # Deprecated - to be removed in OpenPype 3.16.6 or 3.17.0. - loader._fname = get_representation_path_from_context(repre_context) - return loader.load(repre_context, name, namespace, options) From ffc76e639a5d516140950da3c9a2c3a9d305c773 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 12 Mar 2025 21:50:06 +0100 Subject: [PATCH 004/108] Remove deprecated `convert_for_ffmpeg` --- client/ayon_core/lib/__init__.py | 2 - client/ayon_core/lib/transcoding.py | 135 +--------------------------- 2 files changed, 2 insertions(+), 135 deletions(-) diff --git a/client/ayon_core/lib/__init__.py b/client/ayon_core/lib/__init__.py index 03ed574081..0c64b88d11 100644 --- a/client/ayon_core/lib/__init__.py +++ b/client/ayon_core/lib/__init__.py @@ -97,7 +97,6 @@ from .profiles_filtering import ( from .transcoding import ( get_transcode_temp_directory, should_convert_for_ffmpeg, - convert_for_ffmpeg, convert_input_paths_for_ffmpeg, get_ffprobe_data, get_ffprobe_streams, @@ -196,7 +195,6 @@ __all__ = [ "get_transcode_temp_directory", "should_convert_for_ffmpeg", - "convert_for_ffmpeg", "convert_input_paths_for_ffmpeg", "get_ffprobe_data", "get_ffprobe_streams", diff --git a/client/ayon_core/lib/transcoding.py b/client/ayon_core/lib/transcoding.py index 1fda014bd8..c4030b3f97 100644 --- a/client/ayon_core/lib/transcoding.py +++ b/client/ayon_core/lib/transcoding.py @@ -526,137 +526,6 @@ def should_convert_for_ffmpeg(src_filepath): return False -# Deprecated since 2022 4 20 -# - Reason - Doesn't convert sequences right way: Can't handle gaps, reuse -# first frame for all frames and changes filenames when input -# is sequence. -# - use 'convert_input_paths_for_ffmpeg' instead -def convert_for_ffmpeg( - first_input_path, - output_dir, - input_frame_start=None, - input_frame_end=None, - logger=None -): - """Convert source file to format supported in ffmpeg. - - Currently can convert only exrs. - - Args: - first_input_path (str): Path to first file of a sequence or a single - file path for non-sequential input. - output_dir (str): Path to directory where output will be rendered. - Must not be same as input's directory. - input_frame_start (int): Frame start of input. - input_frame_end (int): Frame end of input. - logger (logging.Logger): Logger used for logging. - - Raises: - ValueError: If input filepath has extension not supported by function. - Currently is supported only ".exr" extension. - """ - if logger is None: - logger = logging.getLogger(__name__) - - logger.warning(( - "DEPRECATED: 'ayon_core.lib.transcoding.convert_for_ffmpeg' is" - " deprecated function of conversion for FFMpeg. Please replace usage" - " with 'ayon_core.lib.transcoding.convert_input_paths_for_ffmpeg'" - )) - - ext = os.path.splitext(first_input_path)[1].lower() - if ext != ".exr": - raise ValueError(( - "Function 'convert_for_ffmpeg' currently support only" - " \".exr\" extension. Got \"{}\"." - ).format(ext)) - - is_sequence = False - if input_frame_start is not None and input_frame_end is not None: - is_sequence = int(input_frame_end) != int(input_frame_start) - - input_info = get_oiio_info_for_input(first_input_path, logger=logger) - - # Change compression only if source compression is "dwaa" or "dwab" - # - they're not supported in ffmpeg - compression = input_info["attribs"].get("compression") - if compression in ("dwaa", "dwab"): - compression = "none" - - # Prepare subprocess arguments - oiio_cmd = get_oiio_tool_args( - "oiiotool", - # Don't add any additional attributes - "--nosoftwareattrib", - ) - # Add input compression if available - if compression: - oiio_cmd.extend(["--compression", compression]) - - # Collect channels to export - input_arg, channels_arg = get_oiio_input_and_channel_args(input_info) - - oiio_cmd.extend([ - input_arg, first_input_path, - # Tell oiiotool which channels should be put to top stack (and output) - "--ch", channels_arg, - # Use first subimage - "--subimage", "0" - ]) - - # Add frame definitions to arguments - if is_sequence: - oiio_cmd.extend([ - "--frames", "{}-{}".format(input_frame_start, input_frame_end) - ]) - - for attr_name, attr_value in input_info["attribs"].items(): - if not isinstance(attr_value, str): - continue - - # Remove attributes that have string value longer than allowed length - # for ffmpeg or when contain prohibited symbols - erase_reason = "Missing reason" - erase_attribute = False - if len(attr_value) > MAX_FFMPEG_STRING_LEN: - erase_reason = "has too long value ({} chars).".format( - len(attr_value) - ) - erase_attribute = True - - if not erase_attribute: - for char in NOT_ALLOWED_FFMPEG_CHARS: - if char in attr_value: - erase_attribute = True - erase_reason = ( - "contains unsupported character \"{}\"." - ).format(char) - break - - if erase_attribute: - # Set attribute to empty string - logger.info(( - "Removed attribute \"{}\" from metadata because {}." - ).format(attr_name, erase_reason)) - oiio_cmd.extend(["--eraseattrib", attr_name]) - - # Add last argument - path to output - if is_sequence: - ext = os.path.splitext(first_input_path)[1] - base_filename = "tmp.%{:0>2}d{}".format( - len(str(input_frame_end)), ext - ) - else: - base_filename = os.path.basename(first_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) - - def convert_input_paths_for_ffmpeg( input_paths, output_dir, @@ -664,7 +533,7 @@ def convert_input_paths_for_ffmpeg( ): """Convert source file to format supported in ffmpeg. - Currently can convert only exrs. The input filepaths should be files + Can currently convert only EXRs. The input filepaths should be files with same type. Information about input is loaded only from first found file. @@ -692,7 +561,7 @@ def convert_input_paths_for_ffmpeg( if ext != ".exr": raise ValueError(( - "Function 'convert_for_ffmpeg' currently support only" + "Function 'convert_input_paths_for_ffmpeg' currently only supports" " \".exr\" extension. Got \"{}\"." ).format(ext)) From 5540e7923a5f3dc2cc6179deccc783e9c33888c0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 13 Mar 2025 10:59:38 +0100 Subject: [PATCH 005/108] Update client/ayon_core/lib/transcoding.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/lib/transcoding.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/lib/transcoding.py b/client/ayon_core/lib/transcoding.py index c4030b3f97..3e77f39a8f 100644 --- a/client/ayon_core/lib/transcoding.py +++ b/client/ayon_core/lib/transcoding.py @@ -561,9 +561,9 @@ def convert_input_paths_for_ffmpeg( if ext != ".exr": raise ValueError(( - "Function 'convert_input_paths_for_ffmpeg' currently only supports" - " \".exr\" extension. Got \"{}\"." - ).format(ext)) + "Function 'convert_input_paths_for_ffmpeg' currently supports" + f" only \".exr\" extension. Got \"{ext}\"." + )) input_info = get_oiio_info_for_input(first_input_path, logger=logger) From 8424ad39078b3610894b0e53142a9b01ea6c5811 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 13 Mar 2025 11:00:59 +0100 Subject: [PATCH 006/108] Remove redundant brackets --- client/ayon_core/lib/transcoding.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/lib/transcoding.py b/client/ayon_core/lib/transcoding.py index 3e77f39a8f..8c84e1c4dc 100644 --- a/client/ayon_core/lib/transcoding.py +++ b/client/ayon_core/lib/transcoding.py @@ -560,10 +560,10 @@ def convert_input_paths_for_ffmpeg( ext = os.path.splitext(first_input_path)[1].lower() if ext != ".exr": - raise ValueError(( + raise ValueError( "Function 'convert_input_paths_for_ffmpeg' currently supports" f" only \".exr\" extension. Got \"{ext}\"." - )) + ) input_info = get_oiio_info_for_input(first_input_path, logger=logger) From d20942892f0f6b00c5b5abdf867351b25c2f247b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 31 Mar 2025 14:45:31 +0200 Subject: [PATCH 007/108] Used new enum for template names from Anatomy --- server/settings/publish_plugins.py | 13 +++++++++++-- server/settings/tools.py | 13 +++++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/server/settings/publish_plugins.py b/server/settings/publish_plugins.py index 39a9c028f9..c32d8d360c 100644 --- a/server/settings/publish_plugins.py +++ b/server/settings/publish_plugins.py @@ -7,6 +7,7 @@ from ayon_server.settings import ( normalize_name, ensure_unique_names, task_types_enum, + anatomy_template_items_enum ) from ayon_server.types import ColorRGBA_uint8 @@ -889,7 +890,11 @@ class IntegrateANTemplateNameProfileModel(BaseSettingsModel): default_factory=list, title="Task names" ) - template_name: str = SettingsField("", title="Template name") + template_name: str = SettingsField( + "", + title="Template name", + enum_resolver=anatomy_template_items_enum(category="publish") + ) class IntegrateHeroTemplateNameProfileModel(BaseSettingsModel): @@ -910,7 +915,11 @@ class IntegrateHeroTemplateNameProfileModel(BaseSettingsModel): default_factory=list, title="Task names" ) - template_name: str = SettingsField("", title="Template name") + template_name: str = SettingsField( + "", + title="Template name", + enum_resolver=anatomy_template_items_enum(category="publish") + ) class IntegrateHeroVersionModel(BaseSettingsModel): diff --git a/server/settings/tools.py b/server/settings/tools.py index 32c72e7a98..d1e6cb50eb 100644 --- a/server/settings/tools.py +++ b/server/settings/tools.py @@ -5,6 +5,7 @@ from ayon_server.settings import ( normalize_name, ensure_unique_names, task_types_enum, + anatomy_template_items_enum ) @@ -283,7 +284,11 @@ class PublishTemplateNameProfile(BaseSettingsModel): task_names: list[str] = SettingsField( default_factory=list, title="Task names" ) - template_name: str = SettingsField("", title="Template name") + template_name: str = SettingsField( + "", + title="Template name", + enum_resolver=anatomy_template_items_enum(category="publish") + ) class CustomStagingDirProfileModel(BaseSettingsModel): @@ -306,7 +311,11 @@ class CustomStagingDirProfileModel(BaseSettingsModel): custom_staging_dir_persistent: bool = SettingsField( False, title="Custom Staging Folder Persistent" ) - template_name: str = SettingsField("", title="Template Name") + template_name: str = SettingsField( + "", + title="Template name", + enum_resolver=anatomy_template_items_enum + ) class PublishToolModel(BaseSettingsModel): From ff8d4f5ddaa0c974a4a9a96553778f2b18139c85 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 1 Apr 2025 11:54:56 +0200 Subject: [PATCH 008/108] Added missed argument --- server/settings/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/settings/tools.py b/server/settings/tools.py index d1e6cb50eb..28ceeedbe4 100644 --- a/server/settings/tools.py +++ b/server/settings/tools.py @@ -314,7 +314,7 @@ class CustomStagingDirProfileModel(BaseSettingsModel): template_name: str = SettingsField( "", title="Template name", - enum_resolver=anatomy_template_items_enum + enum_resolver=anatomy_template_items_enum(category="publish") ) From 7dad20d9c98c71c37db2bbdba22b32249c0001e4 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 1 Apr 2025 12:07:10 +0200 Subject: [PATCH 009/108] Bump up required server version --- package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.py b/package.py index 9af45719a7..51851c0f5f 100644 --- a/package.py +++ b/package.py @@ -6,7 +6,7 @@ client_dir = "ayon_core" plugin_for = ["ayon_server"] -ayon_server_version = ">=1.0.3,<2.0.0" +ayon_server_version = ">=1.7.5,<2.0.0" ayon_launcher_version = ">=1.0.2" ayon_required_addons = {} ayon_compatible_addons = {} From 93ea50cac145de2e568c6f17480fe487e62ec10c Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 1 Apr 2025 12:15:22 +0200 Subject: [PATCH 010/108] Update correct category --- server/settings/publish_plugins.py | 2 +- server/settings/tools.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/settings/publish_plugins.py b/server/settings/publish_plugins.py index c32d8d360c..028cf9fffa 100644 --- a/server/settings/publish_plugins.py +++ b/server/settings/publish_plugins.py @@ -918,7 +918,7 @@ class IntegrateHeroTemplateNameProfileModel(BaseSettingsModel): template_name: str = SettingsField( "", title="Template name", - enum_resolver=anatomy_template_items_enum(category="publish") + enum_resolver=anatomy_template_items_enum(category="hero") ) diff --git a/server/settings/tools.py b/server/settings/tools.py index 28ceeedbe4..b003ef2244 100644 --- a/server/settings/tools.py +++ b/server/settings/tools.py @@ -314,7 +314,7 @@ class CustomStagingDirProfileModel(BaseSettingsModel): template_name: str = SettingsField( "", title="Template name", - enum_resolver=anatomy_template_items_enum(category="publish") + enum_resolver=anatomy_template_items_enum(category="staging") ) From d8c442e7f56a511c22376dadd828cda715ae19c4 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 2 Apr 2025 15:00:44 +0200 Subject: [PATCH 011/108] Update OCIO config paths and compatible addons - Added a new compatible addon with version requirement. - Updated existing OCIO config paths for ACES 1.3 and added support for ACES 2.0. - Adjusted labels and descriptions for clarity on OCIO versions. --- package.py | 4 +++- server/settings/main.py | 18 +++++++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/package.py b/package.py index af3342f3f2..bb8278151d 100644 --- a/package.py +++ b/package.py @@ -9,4 +9,6 @@ plugin_for = ["ayon_server"] ayon_server_version = ">=1.0.3,<2.0.0" ayon_launcher_version = ">=1.0.2" ayon_required_addons = {} -ayon_compatible_addons = {} +ayon_compatible_addons = { + "ayon_ocio": ">=1.2.0", +} diff --git a/server/settings/main.py b/server/settings/main.py index 261bd7fc04..520c533aab 100644 --- a/server/settings/main.py +++ b/server/settings/main.py @@ -72,18 +72,30 @@ def _fallback_ocio_config_profile_types(): def _ocio_built_in_paths(): return [ { - "value": "{BUILTIN_OCIO_ROOT}/aces_1.3/studio-config-v1.0.0_aces-v1.3_ocio-v2.0.ocio", # noqa: E501 + "value": "{BUILTIN_OCIO_ROOT}/studio-config-v1.0.0_aces-v1.3_ocio-v2.0.ocio", # noqa: E501 "label": "ACES 1.3 Studio (OCIO v2)", "description": ( "Aces 1.3 Studio OCIO config file. Requires OCIO v2.") }, { - "value": "{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio", + "value": "{BUILTIN_OCIO_ROOT}/studio-config-v1.0.0_aces-v1.3_ocio-v2.1.ocio", # noqa: E501 + "label": "ACES 1.3 Studio (OCIO v2.1)", + "description": ( + "Aces 1.3 Studio OCIO config file. Requires OCIO v2.1.") + }, + { + "value": "{BUILTIN_OCIO_ROOT}/studio-config-v3.0.0_aces-v2.0_ocio-v2.4", # noqa: E501 + "label": "ACES 2.0 Studio (OCIO v2.4)", + "description": ( + "Aces 2.0 Studio OCIO config file. Requires OCIO v2.4.") + }, + { + "value": "{BUILTIN_OCIO_ROOT}/OpenColorIOConfigs/aces_1.2/config.ocio", "label": "ACES 1.2", "description": "Aces 1.2 OCIO config file." }, { - "value": "{BUILTIN_OCIO_ROOT}/nuke-default/config.ocio", + "value": "{BUILTIN_OCIO_ROOT}/OpenColorIOConfigs/nuke-default/config.ocio", "label": "Nuke default", }, ] From ee213afce408dfb90e1b3265f16cdb19e59cfd66 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 2 Apr 2025 15:02:19 +0200 Subject: [PATCH 012/108] Update OCIO config paths and add comments - Added line length comments to OCIO config paths - No functional changes, just improved code clarity --- server/settings/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/settings/main.py b/server/settings/main.py index 520c533aab..a7d82ec363 100644 --- a/server/settings/main.py +++ b/server/settings/main.py @@ -90,12 +90,12 @@ def _ocio_built_in_paths(): "Aces 2.0 Studio OCIO config file. Requires OCIO v2.4.") }, { - "value": "{BUILTIN_OCIO_ROOT}/OpenColorIOConfigs/aces_1.2/config.ocio", + "value": "{BUILTIN_OCIO_ROOT}/OpenColorIOConfigs/aces_1.2/config.ocio", # noqa: E501 "label": "ACES 1.2", "description": "Aces 1.2 OCIO config file." }, { - "value": "{BUILTIN_OCIO_ROOT}/OpenColorIOConfigs/nuke-default/config.ocio", + "value": "{BUILTIN_OCIO_ROOT}/OpenColorIOConfigs/nuke-default/config.ocio", # noqa: E501 "label": "Nuke default", }, ] From 89a494b43f5d1a3c27a95f88d13458462381ebb5 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 3 Apr 2025 11:28:52 +0200 Subject: [PATCH 013/108] Added profile targeting Hero --- server/settings/tools.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/server/settings/tools.py b/server/settings/tools.py index b003ef2244..6b07910454 100644 --- a/server/settings/tools.py +++ b/server/settings/tools.py @@ -291,6 +291,29 @@ class PublishTemplateNameProfile(BaseSettingsModel): ) +class HeroTemplateNameProfile(BaseSettingsModel): + _layout = "expanded" + product_types: list[str] = SettingsField( + default_factory=list, + title="Product types" + ) + # TODO this should use hosts enum + hosts: list[str] = SettingsField(default_factory=list, title="Hosts") + task_types: list[str] = SettingsField( + default_factory=list, + title="Task types", + enum_resolver=task_types_enum + ) + task_names: list[str] = SettingsField( + default_factory=list, title="Task names" + ) + template_name: str = SettingsField( + "", + title="Template name", + enum_resolver=anatomy_template_items_enum(category="hero") + ) + + class CustomStagingDirProfileModel(BaseSettingsModel): active: bool = SettingsField(True, title="Is active") hosts: list[str] = SettingsField(default_factory=list, title="Host names") @@ -323,7 +346,7 @@ class PublishToolModel(BaseSettingsModel): default_factory=list, title="Template name profiles" ) - hero_template_name_profiles: list[PublishTemplateNameProfile] = ( + hero_template_name_profiles: list[HeroTemplateNameProfile] = ( SettingsField( default_factory=list, title="Hero template name profiles" From 2cd0b0ddbfdc8086156a2dee50aada811a3233c8 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 4 Apr 2025 14:47:19 +0200 Subject: [PATCH 014/108] Update OCIO config paths for cleaner structure - Removed redundant directory from ACES 1.2 path - Simplified Nuke default config path --- server/settings/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/settings/main.py b/server/settings/main.py index a7d82ec363..21612ee362 100644 --- a/server/settings/main.py +++ b/server/settings/main.py @@ -90,12 +90,12 @@ def _ocio_built_in_paths(): "Aces 2.0 Studio OCIO config file. Requires OCIO v2.4.") }, { - "value": "{BUILTIN_OCIO_ROOT}/OpenColorIOConfigs/aces_1.2/config.ocio", # noqa: E501 + "value": "{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio", # noqa: E501 "label": "ACES 1.2", "description": "Aces 1.2 OCIO config file." }, { - "value": "{BUILTIN_OCIO_ROOT}/OpenColorIOConfigs/nuke-default/config.ocio", # noqa: E501 + "value": "{BUILTIN_OCIO_ROOT}/nuke-default/config.ocio", # noqa: E501 "label": "Nuke default", }, ] From 5bb3d2f407714d259cdc364c7a34ca1878fbbbdd Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 8 Apr 2025 16:34:58 +0200 Subject: [PATCH 015/108] Refactored existing logic for reusing last rendered frame --- .../plugins/publish/extract_review.py | 47 ++++++++++++------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index df87abba91..f3adfdcf74 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -403,12 +403,25 @@ class ExtractReview(pyblish.api.InstancePlugin): files_to_clean = [] if temp_data["input_is_sequence"]: self.log.debug("Checking sequence to fill gaps in sequence..") - files_to_clean = self.fill_sequence_gaps( - files=temp_data["origin_repre"]["files"], - staging_dir=new_repre["stagingDir"], - start_frame=temp_data["frame_start"], - end_frame=temp_data["frame_end"] - ) + + files = temp_data["origin_repre"]["files"] + collections = clique.assemble( + files, + patterns=[clique.PATTERNS["frames"]], + minimum_items=1 + )[0] + if len(collections) != 1: + raise KnownPublishError( + "Multiple collections {} found.".format(collections)) + + collection = collections[0] + if fill_type == "existing": + files_to_clean = self.fill_sequence_gaps_from_existing( + collection=collection, + staging_dir=new_repre["stagingDir"], + start_frame=temp_data["frame_start"], + end_frame=temp_data["frame_end"], + ) # create or update outputName output_name = new_repre.get("outputName", "") @@ -883,6 +896,13 @@ class ExtractReview(pyblish.api.InstancePlugin): def fill_sequence_gaps(self, files, staging_dir, start_frame, end_frame): # type: (list, str, int, int) -> list + def fill_sequence_gaps_from_existing( + self, + collection, + staging_dir: str, + start_frame: int, + end_frame: int + ) -> list: """Fill missing files in sequence by duplicating existing ones. This will take nearest frame file and copy it with so as to fill @@ -890,7 +910,7 @@ class ExtractReview(pyblish.api.InstancePlugin): hole ahead. Args: - files (list): List of representation files. + collection (clique.collection) staging_dir (str): Path to staging directory. start_frame (int): Sequence start (no matter what files are there) end_frame (int): Sequence end (no matter what files are there) @@ -903,19 +923,12 @@ class ExtractReview(pyblish.api.InstancePlugin): KnownPublishError: if more than one collection is obtained. """ - collections = clique.assemble(files)[0] - if len(collections) != 1: - raise KnownPublishError( - "Multiple collections {} found.".format(collections)) - - col = collections[0] - # Prepare which hole is filled with what frame # - the frame is filled only with already existing frames - prev_frame = next(iter(col.indexes)) + prev_frame = next(iter(collection.indexes)) hole_frame_to_nearest = {} for frame in range(int(start_frame), int(end_frame) + 1): - if frame in col.indexes: + if frame in collection.indexes: prev_frame = frame else: # Use previous frame as source for hole @@ -923,7 +936,7 @@ class ExtractReview(pyblish.api.InstancePlugin): # Calculate paths added_files = [] - col_format = col.format("{head}{padding}{tail}") + col_format = collection.format("{head}{padding}{tail}") for hole_frame, src_frame in hole_frame_to_nearest.items(): hole_fpath = os.path.join(staging_dir, col_format % hole_frame) src_fpath = os.path.join(staging_dir, col_format % src_frame) From 25a94412396175d3e8934ceb0f838b5c934aa751 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 8 Apr 2025 16:35:50 +0200 Subject: [PATCH 016/108] Added extension to temp_data --- client/ayon_core/plugins/publish/extract_review.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index f3adfdcf74..5c1de70c24 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -591,6 +591,8 @@ class ExtractReview(pyblish.api.InstancePlugin): ext = os.path.splitext(repre["files"][0])[1].replace(".", "") if ext.lower() in self.alpha_exts: input_allow_bg = True + else: + ext = os.path.splitext(repre["files"])[1].replace(".", "") return { "fps": float(instance.data["fps"]), @@ -611,7 +613,8 @@ class ExtractReview(pyblish.api.InstancePlugin): "input_allow_bg": input_allow_bg, "with_audio": with_audio, "without_handles": without_handles, - "handles_are_set": handles_are_set + "handles_are_set": handles_are_set, + "ext": ext } def _ffmpeg_arguments( From b5170670065e76baff535076c409d429765ac5ac Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 8 Apr 2025 16:38:37 +0200 Subject: [PATCH 017/108] Implemented new blank frame fills --- .../plugins/publish/extract_review.py | 62 ++++++++++++++++++- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index 5c1de70c24..83893443d9 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -422,6 +422,15 @@ class ExtractReview(pyblish.api.InstancePlugin): start_frame=temp_data["frame_start"], end_frame=temp_data["frame_end"], ) + elif fill_type == "blank": + files_to_clean = self.fill_sequence_gaps_with_blanks( + collection=collection, + staging_dir=new_repre["stagingDir"], + resolution_width=temp_data["resolution_width"], + resolution_height=temp_data["resolution_height"], + extension=temp_data["ext"], + ) + # create or update outputName output_name = new_repre.get("outputName", "") @@ -897,8 +906,57 @@ class ExtractReview(pyblish.api.InstancePlugin): return all_args - def fill_sequence_gaps(self, files, staging_dir, start_frame, end_frame): - # type: (list, str, int, int) -> list + def fill_sequence_gaps_with_blanks( + self, + collection: str, + staging_dir: str, + resolution_width: int, + resolution_height: int, + extension: str, + ): + """Fills missing files by blank frame. + + Args: + collection (clique.collection) + staging_dir (str): Path to staging directory. + resolution_width (int): width of source frame + resolution_height (int): height of source frame + extension (str) + + Returns: + list of added files. Those should be cleaned after work + is done. + + """ + blank_frame_path = os.path.join(staging_dir, f"blank.{extension}") + command = get_ffmpeg_tool_args("ffmpeg") + + command.extend([ + "-f", "lavfi", + "-i", "color=c=black:s={}x{}:d=1".format( + resolution_width, resolution_height + ), + "-tune", "stillimage", + "-frames: v" , 1, + blank_frame_path + ]) + + self.log.debug("Executing: {}".format(" ".join(command))) + output = run_subprocess( + command, logger=self.log + ) + self.log.debug("Output: {}".format(output)) + + added_files = [blank_frame_path] + + for missing_frame_name in collection.holes(): + hole_fpath = os.path.join(staging_dir, missing_frame_name) + speedcopy.copyfile(blank_frame_path, hole_fpath) + added_files.append(hole_fpath) + + return added_files + + def fill_sequence_gaps_from_existing( self, collection, From f66ff742f7171994e289cdb3b7a9a9a0501bf3c4 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 9 Apr 2025 15:38:19 +0200 Subject: [PATCH 018/108] Updates review extract to improve quality Improves review extraction by: - Switches output extension to '.png' for better image quality. - Adds compression level to ffmpeg command. - Adds scaling to the video filter. - Forces re-encoding for lossy formats. --- .../plugins/publish/extract_otio_review.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_otio_review.py b/client/ayon_core/plugins/publish/extract_otio_review.py index 7a9a020ff0..e96c1a1b6b 100644 --- a/client/ayon_core/plugins/publish/extract_otio_review.py +++ b/client/ayon_core/plugins/publish/extract_otio_review.py @@ -54,7 +54,7 @@ class ExtractOTIOReview( # plugin default attributes to_width = 1280 to_height = 720 - output_ext = ".jpg" + output_ext = ".png" def process(self, instance): # Not all hosts can import these modules. @@ -474,6 +474,7 @@ class ExtractOTIOReview( command.extend([ "-start_number", str(in_frame_start), + "-compression_level", "5", "-framerate", str(sequence_fps), "-i", input_path ]) @@ -510,6 +511,11 @@ class ExtractOTIOReview( "-tune", "stillimage" ]) + if video or sequence: + command.extend([ + "-vf", f"scale={self.to_width}:{self.to_height}:flags=lanczos" + ]) + # add output attributes command.extend([ "-start_number", str(out_frame_start) @@ -520,9 +526,12 @@ class ExtractOTIOReview( input_extension and self.output_ext == input_extension ): - command.extend([ - "-c", "copy" - ]) + if input_extension.lower() in [ + '.png', '.tif', '.tiff', '.dpx', '.exr']: + command.extend(["-c", "copy"]) + else: + # For lossy formats, force re-encode + command.extend(["-pix_fmt", "rgba"]) # add output path at the end command.append(output_path) From edc0aa5867684eceed3da3a809567ab60f3ecc17 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 9 Apr 2025 15:56:39 +0200 Subject: [PATCH 019/108] Remove unnecessary E501 comments --- server/settings/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/settings/main.py b/server/settings/main.py index 21612ee362..f49866dc95 100644 --- a/server/settings/main.py +++ b/server/settings/main.py @@ -90,12 +90,12 @@ def _ocio_built_in_paths(): "Aces 2.0 Studio OCIO config file. Requires OCIO v2.4.") }, { - "value": "{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio", # noqa: E501 + "value": "{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio", "label": "ACES 1.2", "description": "Aces 1.2 OCIO config file." }, { - "value": "{BUILTIN_OCIO_ROOT}/nuke-default/config.ocio", # noqa: E501 + "value": "{BUILTIN_OCIO_ROOT}/nuke-default/config.ocio", "label": "Nuke default", }, ] From d076f152fd4c13cf4b66ef668537d2ce41cfcecb Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 9 Apr 2025 18:22:07 +0200 Subject: [PATCH 020/108] Renamed files_to_clean as now its dictionary --- client/ayon_core/plugins/publish/extract_review.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index 83893443d9..ec2569e3f6 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -416,14 +416,14 @@ class ExtractReview(pyblish.api.InstancePlugin): collection = collections[0] if fill_type == "existing": - files_to_clean = self.fill_sequence_gaps_from_existing( + added_frames_and_files = self.fill_sequence_gaps_from_existing( collection=collection, staging_dir=new_repre["stagingDir"], start_frame=temp_data["frame_start"], end_frame=temp_data["frame_end"], ) elif fill_type == "blank": - files_to_clean = self.fill_sequence_gaps_with_blanks( + added_frames_and_files = self.fill_sequence_gaps_with_blanks( collection=collection, staging_dir=new_repre["stagingDir"], resolution_width=temp_data["resolution_width"], @@ -487,8 +487,8 @@ class ExtractReview(pyblish.api.InstancePlugin): run_subprocess(subprcs_cmd, shell=True, logger=self.log) # delete files added to fill gaps - if files_to_clean: - for f in files_to_clean: + if added_frames_and_files: + for f in added_frames_and_files.values(): os.unlink(f) new_repre.update({ From 6c404c88e970c872ba4acf860d1d1850d91e80fb Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 9 Apr 2025 18:23:43 +0200 Subject: [PATCH 021/108] Added filling by previous published version --- .../plugins/publish/extract_review.py | 124 +++++++++++++++++- 1 file changed, 123 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index ec2569e3f6..1cffcaf68c 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import re import copy @@ -5,6 +7,7 @@ import json import shutil import subprocess from abc import ABC, abstractmethod +from typing import Dict, Any import clique import speedcopy @@ -29,6 +32,7 @@ from ayon_core.pipeline.publish import ( get_publish_instance_label, ) from ayon_core.pipeline.publish.lib import add_repre_files_for_cleanup +from ayon_api import get_last_version_by_product_name, get_representations def frame_to_timecode(frame: int, fps: float) -> str: @@ -430,7 +434,24 @@ class ExtractReview(pyblish.api.InstancePlugin): resolution_height=temp_data["resolution_height"], extension=temp_data["ext"], ) - + elif fill_type == "previous": + added_frames_and_files = self.fill_sequence_gaps_with_previous( + collection=collection, + staging_dir=new_repre["stagingDir"], + instance=instance, + current_repre=repre, + start_frame=temp_data["frame_start"], + end_frame=temp_data["frame_end"], + ) + # fallback to original workflow + if added_frames_and_files is None: + added_frames_and_files = self.fill_sequence_gaps_from_existing( + collection=collection, + staging_dir=new_repre["stagingDir"], + start_frame=temp_data["frame_start"], + end_frame=temp_data["frame_end"], + ) + temp_data["filled_files"] = added_frames_and_files # create or update outputName output_name = new_repre.get("outputName", "") @@ -906,6 +927,99 @@ class ExtractReview(pyblish.api.InstancePlugin): return all_args + def fill_sequence_gaps_with_previous( + self, + collection: str, + staging_dir: str, + instance: pyblish.plugin.Instance, + current_repre: Dict[Any, Any], + start_frame: int, + end_frame: int + ) -> Dict[int, str] | None: + """Tries to replace missing frames from ones from last version""" + repre_file_paths = self._get_last_version_files( + instance, current_repre) + if repre_file_paths is None: + # issues in getting last version files, falling back + return None + + prev_collection = clique.assemble( + repre_file_paths, + patterns=[clique.PATTERNS["frames"]], + minimum_items=1 + )[0][0] + prev_col_format = prev_collection.format("{head}{padding}{tail}") + + added_files = {} + anatomy = instance.context.data["anatomy"] + col_format = collection.format("{head}{padding}{tail}") + for frame in range(start_frame, end_frame + 1): + if frame in collection.indexes: + continue + hole_fpath = os.path.join(staging_dir, col_format % frame) + + previous_version_path = prev_col_format % frame + # limits too large padding coming from Anatomy + previous_version_path = ( + os.path.join( + anatomy.fill_root(os.path.dirname(previous_version_path)), + os.path.basename(previous_version_path) + ) + ) + if not os.path.exists(previous_version_path): + self.log.warning( + "Missing frame should be replaced from " + f"'{previous_version_path}' but that doesn't exist. " + "Falling back to filling from currently last rendered." + ) + return None + + self.log.warning( + f"Replacing missing '{hole_fpath}' with " + f"'{previous_version_path}'" + ) + speedcopy.copyfile(previous_version_path, hole_fpath) + added_files[frame] = hole_fpath + + return added_files + + def _get_last_version_files( + self, + instance: pyblish.plugin.Instance, + current_repre: Dict[Any, Any], + ): + product_name = instance.data["productName"] + project_name = instance.data["projectEntity"]["name"] + folder_entity = instance.data["folderEntity"] + + version_entity = get_last_version_by_product_name( + project_name, + product_name, + folder_entity["id"], + fields={"id"} + ) + if not version_entity: + return None + + repres = get_representations( + project_name, + version_ids=[version_entity["id"]] + ) + matching_repre = None + for repre in repres: + if repre["name"] == current_repre["name"]: + matching_repre = repre + break + if not matching_repre: + return None + + repre_file_paths = [ + file_info["path"] + for file_info in matching_repre["files"] + ] + + return repre_file_paths + def fill_sequence_gaps_with_blanks( self, collection: str, @@ -1052,6 +1166,14 @@ class ExtractReview(pyblish.api.InstancePlugin): # Make sure to have full path to one input file full_input_path_single_file = full_input_path + filled_files = temp_data.get("filled_files", {}) + if filled_files: + first_frame, first_file = list(filled_files.items())[0] + if first_file < full_input_path_single_file: + self.log.warning(f"Using filled frame: '{first_file}'") + full_input_path_single_file = first_file + temp_data["first_sequence_frame"] = first_frame + filename_suffix = output_def["filename_suffix"] output_ext = output_def.get("ext") From be54d9deb815aa03c615bf5629af8bd6f708f0bd Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 9 Apr 2025 18:24:27 +0200 Subject: [PATCH 022/108] Missed renamed variable initialization --- client/ayon_core/plugins/publish/extract_review.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index 1cffcaf68c..61fbf2d90d 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -404,7 +404,7 @@ class ExtractReview(pyblish.api.InstancePlugin): ) temp_data = self.prepare_temp_data(instance, repre, output_def) - files_to_clean = [] + added_frames_and_files = {} if temp_data["input_is_sequence"]: self.log.debug("Checking sequence to fill gaps in sequence..") From 3fe0c251dfdf449dc037706368cc87585c5771dc Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 9 Apr 2025 18:29:20 +0200 Subject: [PATCH 023/108] Refactored filling by blanks --- .../plugins/publish/extract_review.py | 31 +++++++------------ 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index 61fbf2d90d..8c3186d390 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -430,6 +430,8 @@ class ExtractReview(pyblish.api.InstancePlugin): added_frames_and_files = self.fill_sequence_gaps_with_blanks( collection=collection, staging_dir=new_repre["stagingDir"], + start_frame=temp_data["frame_start"], + end_frame=temp_data["frame_end"], resolution_width=temp_data["resolution_width"], resolution_height=temp_data["resolution_height"], extension=temp_data["ext"], @@ -1024,24 +1026,13 @@ class ExtractReview(pyblish.api.InstancePlugin): self, collection: str, staging_dir: str, + start_frame: int, + end_frame: int, resolution_width: int, resolution_height: int, extension: str, - ): - """Fills missing files by blank frame. - - Args: - collection (clique.collection) - staging_dir (str): Path to staging directory. - resolution_width (int): width of source frame - resolution_height (int): height of source frame - extension (str) - - Returns: - list of added files. Those should be cleaned after work - is done. - - """ + ) -> Dict[int, str] | None: + """Fills missing files by blank frame.""" blank_frame_path = os.path.join(staging_dir, f"blank.{extension}") command = get_ffmpeg_tool_args("ffmpeg") @@ -1063,14 +1054,16 @@ class ExtractReview(pyblish.api.InstancePlugin): added_files = [blank_frame_path] - for missing_frame_name in collection.holes(): - hole_fpath = os.path.join(staging_dir, missing_frame_name) + col_format = collection.format("{head}{padding}{tail}") + for frame in range(start_frame, end_frame + 1): + if frame in collection.indexes: + continue + hole_fpath = os.path.join(staging_dir, col_format % frame) speedcopy.copyfile(blank_frame_path, hole_fpath) - added_files.append(hole_fpath) + added_files[frame] = hole_fpath return added_files - def fill_sequence_gaps_from_existing( self, collection, From 0860fd130254a7a93916f50155e2370a5285d753 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 9 Apr 2025 18:29:45 +0200 Subject: [PATCH 024/108] Updated return type --- client/ayon_core/plugins/publish/extract_review.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index 8c3186d390..fdb7a887f8 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -1084,7 +1084,7 @@ class ExtractReview(pyblish.api.InstancePlugin): end_frame (int): Sequence end (no matter what files are there) Returns: - list of added files. Those should be cleaned after work + dict[int, str] of added files. Those should be cleaned after work is done. Raises: @@ -1113,7 +1113,7 @@ class ExtractReview(pyblish.api.InstancePlugin): "Missing previously detected file: {}".format(src_fpath)) speedcopy.copyfile(src_fpath, hole_fpath) - added_files.append(hole_fpath) + added_files[hole_frame] = hole_fpath return added_files From 51f86e5c5743f3252bbc60745f44f806ff386457 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 11 Apr 2025 11:20:55 +0200 Subject: [PATCH 025/108] Bump server dependency version --- package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.py b/package.py index 9f01049fb9..d9edc36cdd 100644 --- a/package.py +++ b/package.py @@ -6,7 +6,7 @@ client_dir = "ayon_core" plugin_for = ["ayon_server"] -ayon_server_version = ">=1.7.5,<2.0.0" +ayon_server_version = ">=1.7.6,<2.0.0" ayon_launcher_version = ">=1.0.2" ayon_required_addons = {} ayon_compatible_addons = {} From f14a00b51024d876076c0c6b1a1d534d16ddb13d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 11 Apr 2025 14:59:36 +0200 Subject: [PATCH 026/108] Implemented review from explicit frames --- .../plugins/publish/extract_review.py | 48 ++++++++++++++++--- 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index fdb7a887f8..1d6aec6d1d 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -453,6 +453,14 @@ class ExtractReview(pyblish.api.InstancePlugin): start_frame=temp_data["frame_start"], end_frame=temp_data["frame_end"], ) + elif fill_type == "only_rendered": + temp_data["explicit_frames"] = [ + os.path.join( + new_repre["stagingDir"], file + ).replace("\\", "/") + for file in files + ] + temp_data["filled_files"] = added_frames_and_files # create or update outputName @@ -514,6 +522,10 @@ class ExtractReview(pyblish.api.InstancePlugin): for f in added_frames_and_files.values(): os.unlink(f) + if (temp_data["explicit_frames_metadata_path"] + and os.path.exists(temp_data["explicit_frames_metadata_path"])): + os.unlink(temp_data["explicit_frames_metadata_path"]) + new_repre.update({ "fps": temp_data["fps"], "name": "{}_{}".format(output_name, output_ext), @@ -646,7 +658,9 @@ class ExtractReview(pyblish.api.InstancePlugin): "with_audio": with_audio, "without_handles": without_handles, "handles_are_set": handles_are_set, - "ext": ext + "ext": ext, + "explicit_frames": [], # absolute paths to rendered files + "explicit_frames_metadata_path": None # abs path to metadata file } def _ffmpeg_arguments( @@ -728,7 +742,8 @@ class ExtractReview(pyblish.api.InstancePlugin): if layer_name: ffmpeg_input_args.extend(["-layer", layer_name]) - if temp_data["input_is_sequence"]: + explicit_frames = temp_data["explicit_frames"] + if temp_data["input_is_sequence"] and not explicit_frames: # Set start frame of input sequence (just frame in filename) # - definition of input filepath # - add handle start if output should be without handles @@ -755,7 +770,7 @@ class ExtractReview(pyblish.api.InstancePlugin): "-to", "{:0.10f}".format(duration_seconds) ]) - if temp_data["output_is_sequence"]: + if temp_data["output_is_sequence"] and not explicit_frames: # Set start frame of output sequence (just frame in filename) # - this is definition of an output ffmpeg_output_args.extend([ @@ -786,10 +801,29 @@ class ExtractReview(pyblish.api.InstancePlugin): "-frames:v", str(output_frames_len) ]) - # Add video/image input path - ffmpeg_input_args.extend([ - "-i", path_to_subprocess_arg(temp_data["full_input_path"]) - ]) + if not explicit_frames: + # Add video/image input path + ffmpeg_input_args.extend([ + "-i", path_to_subprocess_arg(temp_data["full_input_path"]) + ]) + else: + staging_dir = os.path.dirname(temp_data["full_input_path"]) + explicit_frames_path = os.path.join( + staging_dir, "explicit_frames.txt") + with open(explicit_frames_path, "w") as fp: + lines = [ + f"file {file}" + for file in temp_data["explicit_frames"] + ] + fp.write("\n".join(lines)) + temp_data["explicit_frames_metadata_path"] = explicit_frames_path + + # let ffmpeg use only rendered files, might have gaps + ffmpeg_input_args.extend([ + "-f", "concat", + "-safe", "0", + "-i", path_to_subprocess_arg(explicit_frames_path) + ]) # Add audio arguments if there are any. Skipped when output are images. if not temp_data["output_ext_is_image"] and temp_data["with_audio"]: From c210f62e1980a0784d231477d7488be45b38d09d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 11 Apr 2025 18:12:15 +0200 Subject: [PATCH 027/108] Added fill_missing_frames to settings --- server/settings/publish_plugins.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/server/settings/publish_plugins.py b/server/settings/publish_plugins.py index 39a9c028f9..3a0e932606 100644 --- a/server/settings/publish_plugins.py +++ b/server/settings/publish_plugins.py @@ -12,6 +12,14 @@ from ayon_server.settings import ( from ayon_server.types import ColorRGBA_uint8 +def _handle_missing_frames_enum(): + return [ + {"value": "closest_existing", "label": "Use closest existing"}, + {"value": "blank", "label": "Generate blank frame"}, + {"value": "previous_version", "label": "Use previous version"}, + {"value": "only_rendered", "label": "Use only rendered"}, + ] + class EnabledModel(BaseSettingsModel): enabled: bool = SettingsField(True) @@ -642,6 +650,12 @@ class ExtractReviewOutputDefModel(BaseSettingsModel): default_factory=ExtractReviewLetterBox, title="Letter Box" ) + fill_missing_frames:str = SettingsField( + title="Handle missing frames", + description="How to handle frames that are missing from entity frame " + "range.", + enum_resolver=_handle_missing_frames_enum + ) @validator("name") def validate_name(cls, value): @@ -1261,7 +1275,8 @@ DEFAULT_PUBLISH_VALUES = { "fill_color": [0, 0, 0, 1.0], "line_thickness": 0, "line_color": [255, 0, 0, 1.0] - } + }, + "fill_missing_frames": "closest_existing" }, { "name": "h264", @@ -1311,7 +1326,8 @@ DEFAULT_PUBLISH_VALUES = { "fill_color": [0, 0, 0, 1.0], "line_thickness": 0, "line_color": [255, 0, 0, 1.0] - } + }, + "fill_missing_frames": "closest_existing" } ] } From deea9366bc4fde68efcdccf30d78a59015573548 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 11 Apr 2025 18:20:51 +0200 Subject: [PATCH 028/108] Fix initialization --- client/ayon_core/plugins/publish/extract_review.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index 1d6aec6d1d..66841203b6 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -1137,7 +1137,7 @@ class ExtractReview(pyblish.api.InstancePlugin): hole_frame_to_nearest[frame] = prev_frame # Calculate paths - added_files = [] + added_files = {} col_format = collection.format("{head}{padding}{tail}") for hole_frame, src_frame in hole_frame_to_nearest.items(): hole_fpath = os.path.join(staging_dir, col_format % hole_frame) From 8a9c95a69b150f5b40db44d9f6bea7d2ed2609ce Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 11 Apr 2025 18:38:49 +0200 Subject: [PATCH 029/108] Use fill_missing_frames from Settings --- client/ayon_core/plugins/publish/extract_review.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index 66841203b6..1299b16f84 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -420,13 +420,16 @@ class ExtractReview(pyblish.api.InstancePlugin): collection = collections[0] if fill_type == "existing": + + fill_missing_frames = _output_def["fill_missing_frames"] + if fill_missing_frames == "closest_existing": added_frames_and_files = self.fill_sequence_gaps_from_existing( collection=collection, staging_dir=new_repre["stagingDir"], start_frame=temp_data["frame_start"], end_frame=temp_data["frame_end"], ) - elif fill_type == "blank": + elif fill_missing_frames == "blank": added_frames_and_files = self.fill_sequence_gaps_with_blanks( collection=collection, staging_dir=new_repre["stagingDir"], @@ -436,7 +439,7 @@ class ExtractReview(pyblish.api.InstancePlugin): resolution_height=temp_data["resolution_height"], extension=temp_data["ext"], ) - elif fill_type == "previous": + elif fill_missing_frames == "previous_version": added_frames_and_files = self.fill_sequence_gaps_with_previous( collection=collection, staging_dir=new_repre["stagingDir"], @@ -453,7 +456,7 @@ class ExtractReview(pyblish.api.InstancePlugin): start_frame=temp_data["frame_start"], end_frame=temp_data["frame_end"], ) - elif fill_type == "only_rendered": + elif fill_missing_frames == "only_rendered": temp_data["explicit_frames"] = [ os.path.join( new_repre["stagingDir"], file From 050db01c82eb18af31d7cd0adf18d095c3e41ed7 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 11 Apr 2025 18:39:45 +0200 Subject: [PATCH 030/108] Remove blank frame --- client/ayon_core/plugins/publish/extract_review.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index 1299b16f84..a3892bec62 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -438,6 +438,7 @@ class ExtractReview(pyblish.api.InstancePlugin): resolution_width=temp_data["resolution_width"], resolution_height=temp_data["resolution_height"], extension=temp_data["ext"], + temp_data=temp_data ) elif fill_missing_frames == "previous_version": added_frames_and_files = self.fill_sequence_gaps_with_previous( @@ -525,9 +526,8 @@ class ExtractReview(pyblish.api.InstancePlugin): for f in added_frames_and_files.values(): os.unlink(f) - if (temp_data["explicit_frames_metadata_path"] - and os.path.exists(temp_data["explicit_frames_metadata_path"])): - os.unlink(temp_data["explicit_frames_metadata_path"]) + for f in temp_data["paths_to_remove"]: + os.unlink(f) new_repre.update({ "fps": temp_data["fps"], @@ -663,7 +663,7 @@ class ExtractReview(pyblish.api.InstancePlugin): "handles_are_set": handles_are_set, "ext": ext, "explicit_frames": [], # absolute paths to rendered files - "explicit_frames_metadata_path": None # abs path to metadata file + "paths_to_remove": [] } def _ffmpeg_arguments( @@ -819,7 +819,7 @@ class ExtractReview(pyblish.api.InstancePlugin): for file in temp_data["explicit_frames"] ] fp.write("\n".join(lines)) - temp_data["explicit_frames_metadata_path"] = explicit_frames_path + temp_data["paths_to_remove"].append(explicit_frames_path) # let ffmpeg use only rendered files, might have gaps ffmpeg_input_args.extend([ @@ -1068,9 +1068,11 @@ class ExtractReview(pyblish.api.InstancePlugin): resolution_width: int, resolution_height: int, extension: str, + temp_data: Dict[str, Any] ) -> Dict[int, str] | None: """Fills missing files by blank frame.""" blank_frame_path = os.path.join(staging_dir, f"blank.{extension}") + temp_data["paths_to_remove"].append(blank_frame_path) command = get_ffmpeg_tool_args("ffmpeg") command.extend([ From 9415668912a43b96de29c77e34607f64af4ae2d0 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 11 Apr 2025 18:40:07 +0200 Subject: [PATCH 031/108] Fix initialization --- client/ayon_core/plugins/publish/extract_review.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index a3892bec62..9b4e36ca8e 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -1091,7 +1091,7 @@ class ExtractReview(pyblish.api.InstancePlugin): ) self.log.debug("Output: {}".format(output)) - added_files = [blank_frame_path] + added_files = {} col_format = collection.format("{head}{padding}{tail}") for frame in range(start_frame, end_frame + 1): From 488c29e97942a8107d65ca5697ce9c5ba2ba39a7 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 11 Apr 2025 18:40:19 +0200 Subject: [PATCH 032/108] Fix command --- client/ayon_core/plugins/publish/extract_review.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index 9b4e36ca8e..06743d476c 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -1081,7 +1081,7 @@ class ExtractReview(pyblish.api.InstancePlugin): resolution_width, resolution_height ), "-tune", "stillimage", - "-frames: v" , 1, + "-frames:v", "1", blank_frame_path ]) From 12b2ed84a3064fc541007a228a0e38d923ea021a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 11 Apr 2025 18:57:19 +0200 Subject: [PATCH 033/108] Fix local rendering --- client/ayon_core/plugins/publish/extract_review.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index 06743d476c..1370aa1fd1 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -411,15 +411,12 @@ class ExtractReview(pyblish.api.InstancePlugin): files = temp_data["origin_repre"]["files"] collections = clique.assemble( files, - patterns=[clique.PATTERNS["frames"]], - minimum_items=1 )[0] if len(collections) != 1: raise KnownPublishError( "Multiple collections {} found.".format(collections)) collection = collections[0] - if fill_type == "existing": fill_missing_frames = _output_def["fill_missing_frames"] if fill_missing_frames == "closest_existing": From 832beb1732ede03e7babc2ea2c497fb28cfe1a0a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 11 Apr 2025 18:59:57 +0200 Subject: [PATCH 034/108] Refactor names --- .../plugins/publish/extract_review.py | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index 1370aa1fd1..ed789ae895 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -404,7 +404,7 @@ class ExtractReview(pyblish.api.InstancePlugin): ) temp_data = self.prepare_temp_data(instance, repre, output_def) - added_frames_and_files = {} + new_frame_files = {} if temp_data["input_is_sequence"]: self.log.debug("Checking sequence to fill gaps in sequence..") @@ -420,14 +420,14 @@ class ExtractReview(pyblish.api.InstancePlugin): fill_missing_frames = _output_def["fill_missing_frames"] if fill_missing_frames == "closest_existing": - added_frames_and_files = self.fill_sequence_gaps_from_existing( + new_frame_files = self.fill_sequence_gaps_from_existing( collection=collection, staging_dir=new_repre["stagingDir"], start_frame=temp_data["frame_start"], end_frame=temp_data["frame_end"], ) elif fill_missing_frames == "blank": - added_frames_and_files = self.fill_sequence_gaps_with_blanks( + new_frame_files = self.fill_sequence_gaps_with_blanks( collection=collection, staging_dir=new_repre["stagingDir"], start_frame=temp_data["frame_start"], @@ -438,7 +438,7 @@ class ExtractReview(pyblish.api.InstancePlugin): temp_data=temp_data ) elif fill_missing_frames == "previous_version": - added_frames_and_files = self.fill_sequence_gaps_with_previous( + new_frame_files = self.fill_sequence_gaps_with_previous( collection=collection, staging_dir=new_repre["stagingDir"], instance=instance, @@ -447,13 +447,14 @@ class ExtractReview(pyblish.api.InstancePlugin): end_frame=temp_data["frame_end"], ) # fallback to original workflow - if added_frames_and_files is None: - added_frames_and_files = self.fill_sequence_gaps_from_existing( + if new_frame_files is None: + new_frame_files = ( + self.fill_sequence_gaps_from_existing( collection=collection, staging_dir=new_repre["stagingDir"], start_frame=temp_data["frame_start"], end_frame=temp_data["frame_end"], - ) + )) elif fill_missing_frames == "only_rendered": temp_data["explicit_frames"] = [ os.path.join( @@ -462,7 +463,7 @@ class ExtractReview(pyblish.api.InstancePlugin): for file in files ] - temp_data["filled_files"] = added_frames_and_files + temp_data["filled_files"] = new_frame_files # create or update outputName output_name = new_repre.get("outputName", "") @@ -519,8 +520,8 @@ class ExtractReview(pyblish.api.InstancePlugin): run_subprocess(subprcs_cmd, shell=True, logger=self.log) # delete files added to fill gaps - if added_frames_and_files: - for f in added_frames_and_files.values(): + if new_frame_files: + for f in new_frame_files.values(): os.unlink(f) for f in temp_data["paths_to_remove"]: From e61266bc82be539911209cce6fc433fcc9c70ac7 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 14 Apr 2025 12:14:00 +0200 Subject: [PATCH 035/108] Skip validation for explicit frames --- client/ayon_core/plugins/publish/extract_review.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index ed789ae895..31cb3763da 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -462,6 +462,13 @@ class ExtractReview(pyblish.api.InstancePlugin): ).replace("\\", "/") for file in files ] + frame_start = min(collection.indexes) + frame_end = max(collection.indexes) + # modify range for burnins + instance.data["frameStart"] = frame_start + instance.data["frameEnd"] = frame_end + temp_data["frame_start"] = frame_start + temp_data["frame_end"] = frame_end temp_data["filled_files"] = new_frame_files From a4db943903af482f0689f6574013042ff4051963 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 14 Apr 2025 14:22:24 +0200 Subject: [PATCH 036/108] Added default directly to enum Used if additional output defs are present. --- server/settings/publish_plugins.py | 1 + 1 file changed, 1 insertion(+) diff --git a/server/settings/publish_plugins.py b/server/settings/publish_plugins.py index 3a0e932606..f9893add1d 100644 --- a/server/settings/publish_plugins.py +++ b/server/settings/publish_plugins.py @@ -652,6 +652,7 @@ class ExtractReviewOutputDefModel(BaseSettingsModel): ) fill_missing_frames:str = SettingsField( title="Handle missing frames", + default="closest_existing", description="How to handle frames that are missing from entity frame " "range.", enum_resolver=_handle_missing_frames_enum From f8ab13dd2aff1b5d13367c1b460305e5c4422a04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Mon, 14 Apr 2025 15:50:20 +0200 Subject: [PATCH 037/108] Update client/ayon_core/plugins/publish/extract_otio_review.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/plugins/publish/extract_otio_review.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/extract_otio_review.py b/client/ayon_core/plugins/publish/extract_otio_review.py index e96c1a1b6b..f7babc2b7f 100644 --- a/client/ayon_core/plugins/publish/extract_otio_review.py +++ b/client/ayon_core/plugins/publish/extract_otio_review.py @@ -527,7 +527,8 @@ class ExtractOTIOReview( and self.output_ext == input_extension ): if input_extension.lower() in [ - '.png', '.tif', '.tiff', '.dpx', '.exr']: + ".png", ".tif", ".tiff", ".dpx", ".exr" + ]: command.extend(["-c", "copy"]) else: # For lossy formats, force re-encode From c82537008f2439fc4591475939d281b651246ee6 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 15 Apr 2025 13:56:43 +0200 Subject: [PATCH 038/108] Refactor proper variable names --- client/ayon_core/plugins/publish/extract_review.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index 31cb3763da..93f1098f4c 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -528,11 +528,11 @@ class ExtractReview(pyblish.api.InstancePlugin): # delete files added to fill gaps if new_frame_files: - for f in new_frame_files.values(): - os.unlink(f) + for filepath in new_frame_files.values(): + os.unlink(filepath) - for f in temp_data["paths_to_remove"]: - os.unlink(f) + for filepath in temp_data["paths_to_remove"]: + os.unlink(filepath) new_repre.update({ "fps": temp_data["fps"], From 1019eded3bd2dccd479c396f37ae83184d9530b0 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 15 Apr 2025 13:59:01 +0200 Subject: [PATCH 039/108] Refactor filled_files are always initialized --- client/ayon_core/plugins/publish/extract_review.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index 93f1098f4c..f39bc0cff8 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -1203,7 +1203,7 @@ class ExtractReview(pyblish.api.InstancePlugin): # Make sure to have full path to one input file full_input_path_single_file = full_input_path - filled_files = temp_data.get("filled_files", {}) + filled_files = temp_data["filled_files"] if filled_files: first_frame, first_file = list(filled_files.items())[0] if first_file < full_input_path_single_file: From 4c305d9596ce1f8bc1907e83968b55a750940811 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 15 Apr 2025 14:00:17 +0200 Subject: [PATCH 040/108] Refactor renamed explicit_frames to explicit_input_paths --- client/ayon_core/plugins/publish/extract_review.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index f39bc0cff8..6e31d11dce 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -456,7 +456,7 @@ class ExtractReview(pyblish.api.InstancePlugin): end_frame=temp_data["frame_end"], )) elif fill_missing_frames == "only_rendered": - temp_data["explicit_frames"] = [ + temp_data["explicit_input_paths"] = [ os.path.join( new_repre["stagingDir"], file ).replace("\\", "/") @@ -667,7 +667,7 @@ class ExtractReview(pyblish.api.InstancePlugin): "without_handles": without_handles, "handles_are_set": handles_are_set, "ext": ext, - "explicit_frames": [], # absolute paths to rendered files + "explicit_input_paths": [], # absolute paths to rendered files "paths_to_remove": [] } @@ -750,8 +750,8 @@ class ExtractReview(pyblish.api.InstancePlugin): if layer_name: ffmpeg_input_args.extend(["-layer", layer_name]) - explicit_frames = temp_data["explicit_frames"] - if temp_data["input_is_sequence"] and not explicit_frames: + explicit_input_paths = temp_data["explicit_input_paths"] + if temp_data["input_is_sequence"] and not explicit_input_paths: # Set start frame of input sequence (just frame in filename) # - definition of input filepath # - add handle start if output should be without handles @@ -778,7 +778,7 @@ class ExtractReview(pyblish.api.InstancePlugin): "-to", "{:0.10f}".format(duration_seconds) ]) - if temp_data["output_is_sequence"] and not explicit_frames: + if temp_data["output_is_sequence"] and not explicit_input_paths: # Set start frame of output sequence (just frame in filename) # - this is definition of an output ffmpeg_output_args.extend([ @@ -809,7 +809,7 @@ class ExtractReview(pyblish.api.InstancePlugin): "-frames:v", str(output_frames_len) ]) - if not explicit_frames: + if not explicit_input_paths: # Add video/image input path ffmpeg_input_args.extend([ "-i", path_to_subprocess_arg(temp_data["full_input_path"]) @@ -821,7 +821,7 @@ class ExtractReview(pyblish.api.InstancePlugin): with open(explicit_frames_path, "w") as fp: lines = [ f"file {file}" - for file in temp_data["explicit_frames"] + for file in temp_data["explicit_input_paths"] ] fp.write("\n".join(lines)) temp_data["paths_to_remove"].append(explicit_frames_path) From 8b8d29042a6eb83b3c7973ee1d07fda4db552a62 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 15 Apr 2025 14:01:26 +0200 Subject: [PATCH 041/108] Refactor reordered import --- client/ayon_core/plugins/publish/extract_review.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index 6e31d11dce..0192964422 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -13,6 +13,8 @@ import clique import speedcopy import pyblish.api +from ayon_api import get_last_version_by_product_name, get_representations + from ayon_core.lib import ( get_ffmpeg_tool_args, filter_profiles, @@ -32,7 +34,6 @@ from ayon_core.pipeline.publish import ( get_publish_instance_label, ) from ayon_core.pipeline.publish.lib import add_repre_files_for_cleanup -from ayon_api import get_last_version_by_product_name, get_representations def frame_to_timecode(frame: int, fps: float) -> str: From efb3a01f4b4fe79de85e73e35b28ceab025a8594 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 15 Apr 2025 14:02:15 +0200 Subject: [PATCH 042/108] Removed unnecessary import --- client/ayon_core/plugins/publish/extract_review.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index 0192964422..0a1089ec9f 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -1,5 +1,3 @@ -from __future__ import annotations - import os import re import copy From 6df129b93f3460778f4fec02f8736efd1a2c62d4 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 15 Apr 2025 16:37:56 +0200 Subject: [PATCH 043/108] Optimizes review encoding for image sequences Simplifies the encoding process for image sequences by removing the conditional check for specific image formats when using the 'copy' codec. This ensures consistent and efficient handling of image sequence encoding for review purposes. --- .../ayon_core/plugins/publish/extract_otio_review.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_otio_review.py b/client/ayon_core/plugins/publish/extract_otio_review.py index f7babc2b7f..908d78ca0d 100644 --- a/client/ayon_core/plugins/publish/extract_otio_review.py +++ b/client/ayon_core/plugins/publish/extract_otio_review.py @@ -526,13 +526,10 @@ class ExtractOTIOReview( input_extension and self.output_ext == input_extension ): - if input_extension.lower() in [ - ".png", ".tif", ".tiff", ".dpx", ".exr" - ]: - command.extend(["-c", "copy"]) - else: - # For lossy formats, force re-encode - command.extend(["-pix_fmt", "rgba"]) + command.extend(["-c", "copy"]) + else: + # For lossy formats, force re-encode + command.extend(["-pix_fmt", "rgba"]) # add output path at the end command.append(output_path) From cdf8764bb13c452eca607fa5aa9a05db14921724 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 15 Apr 2025 17:18:28 +0200 Subject: [PATCH 044/108] Fix return type --- client/ayon_core/plugins/publish/extract_review.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index 0a1089ec9f..3a7c6a6b1e 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -5,7 +5,7 @@ import json import shutil import subprocess from abc import ABC, abstractmethod -from typing import Dict, Any +from typing import Dict, Any, Union import clique import speedcopy @@ -978,7 +978,7 @@ class ExtractReview(pyblish.api.InstancePlugin): current_repre: Dict[Any, Any], start_frame: int, end_frame: int - ) -> Dict[int, str] | None: + ) -> Union[Dict[int, str], None]: """Tries to replace missing frames from ones from last version""" repre_file_paths = self._get_last_version_files( instance, current_repre) @@ -1073,7 +1073,7 @@ class ExtractReview(pyblish.api.InstancePlugin): resolution_height: int, extension: str, temp_data: Dict[str, Any] - ) -> Dict[int, str] | None: + ) -> Union[Dict[int, str], None]: """Fills missing files by blank frame.""" blank_frame_path = os.path.join(staging_dir, f"blank.{extension}") temp_data["paths_to_remove"].append(blank_frame_path) From d0999af4efaf6abcee42b4165f7919e6253b97b0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 16 Apr 2025 11:14:55 +0200 Subject: [PATCH 045/108] added aces subfolders --- server/settings/main.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/settings/main.py b/server/settings/main.py index f49866dc95..97434d0b93 100644 --- a/server/settings/main.py +++ b/server/settings/main.py @@ -72,19 +72,19 @@ def _fallback_ocio_config_profile_types(): def _ocio_built_in_paths(): return [ { - "value": "{BUILTIN_OCIO_ROOT}/studio-config-v1.0.0_aces-v1.3_ocio-v2.0.ocio", # noqa: E501 + "value": "{BUILTIN_OCIO_ROOT}/aces_1.3/studio-config-v1.0.0_aces-v1.3_ocio-v2.0.ocio", # noqa: E501 "label": "ACES 1.3 Studio (OCIO v2)", "description": ( "Aces 1.3 Studio OCIO config file. Requires OCIO v2.") }, { - "value": "{BUILTIN_OCIO_ROOT}/studio-config-v1.0.0_aces-v1.3_ocio-v2.1.ocio", # noqa: E501 + "value": "{BUILTIN_OCIO_ROOT}/aces_1.3/studio-config-v1.0.0_aces-v1.3_ocio-v2.1.ocio", # noqa: E501 "label": "ACES 1.3 Studio (OCIO v2.1)", "description": ( "Aces 1.3 Studio OCIO config file. Requires OCIO v2.1.") }, { - "value": "{BUILTIN_OCIO_ROOT}/studio-config-v3.0.0_aces-v2.0_ocio-v2.4", # noqa: E501 + "value": "{BUILTIN_OCIO_ROOT}/aces_2.0/studio-config-v3.0.0_aces-v2.0_ocio-v2.4", # noqa: E501 "label": "ACES 2.0 Studio (OCIO v2.4)", "description": ( "Aces 2.0 Studio OCIO config file. Requires OCIO v2.4.") From ba80b3b9b459d74d5aface7b39509316cd6d808b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 17 Apr 2025 17:31:08 +0200 Subject: [PATCH 046/108] Added flags for ffmpeg not skipping some frames because DTS --- client/ayon_core/plugins/publish/extract_review.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index e8ed4c80fc..3615cc53c9 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -829,7 +829,9 @@ class ExtractReview(pyblish.api.InstancePlugin): ffmpeg_input_args.extend([ "-f", "concat", "-safe", "0", - "-i", path_to_subprocess_arg(explicit_frames_path) + "-fflags", "+genpts+igndts", + "-i", path_to_subprocess_arg(explicit_frames_path), + "-r", "25" ]) # Add audio arguments if there are any. Skipped when output are images. From 71dc3650ace89b6ee77583ad43dd05690be32fa9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 23 Apr 2025 10:25:51 +0200 Subject: [PATCH 047/108] Adds explicit resolution override to publisher Adds a plugin that allows users to explicitly override the resolution settings (width, height, pixel aspect) of instances during the publishing process. This provides a way to ensure consistency and accuracy in resolution values across different tasks and product types. The plugin is configurable through the AYON settings, allowing administrators to define the available resolution options and the product types for which the override is enabled. --- .../publish/collect_explicit_resolution.py | 104 ++++++++++++++++++ server/settings/publish_plugins.py | 92 +++++++++++++++- 2 files changed, 195 insertions(+), 1 deletion(-) create mode 100644 client/ayon_core/plugins/publish/collect_explicit_resolution.py diff --git a/client/ayon_core/plugins/publish/collect_explicit_resolution.py b/client/ayon_core/plugins/publish/collect_explicit_resolution.py new file mode 100644 index 0000000000..3ff08cbd34 --- /dev/null +++ b/client/ayon_core/plugins/publish/collect_explicit_resolution.py @@ -0,0 +1,104 @@ +import pyblish.api +from ayon_core.lib import EnumDef +from ayon_core.pipeline import colorspace +from ayon_core.pipeline import publish +from ayon_core.pipeline.publish import PublishError + + +class CollectExplicitResolution( + pyblish.api.InstancePlugin, + publish.AYONPyblishPluginMixin +): + """Collect explicit user defined resolution attributes for instances""" + + label = "Choose Explicit Resolution" + order = pyblish.api.CollectorOrder + 0.49 + settings_category = "core" + + enabled = False + + default_resolution_item = (None, "Don't override") + # Settings + product_types = [] + options = [] + + # caching resoluton items + resolution_items = None + + def process(self, instance): + """Process the instance and collect explicit resolution attributes""" + + # Get the values from the instance data + values = self.get_attr_values_from_data(instance.data) + resolution_value = values.get("explicit_resolution", None) + if resolution_value is None: + return + + # Get the width, height and pixel_aspect from the resolution value + resolution_data = self._get_resolution_values(resolution_value) + + # Set the values to the instance data + instance.data.update(resolution_data) + + def _get_resolution_values(self, resolution_value): + """ + Returns width, height and pixel_aspect from the resolution value + + Arguments: + resolution_value (str): resolution value + + Returns: + dict: dictionary with width, height and pixel_aspect + """ + resolution_items = self._get_resolution_items() + item_values = None + # check if resolution_value is in cached items + if resolution_value in resolution_items: + item_values = resolution_items[resolution_value] + + if item_values: + # if the item is in the cache, get the values from it + return { + "resolutionWidth": item_values["width"], + "resolutionHeight": item_values["height"], + "pixelAspect": item_values["pixel_aspect"] + } + else: + raise PublishError( + f"Invalid resolution value: {resolution_value}") + + @classmethod + def _get_resolution_items(cls): + if cls.resolution_items is None: + resolution_items = {} + for item in cls.options: + item_text = f"{item['width']}x{item['height']}x{item['pixel_aspect']}" + resolution_items[item_text] = item + + cls.resolution_items = resolution_items + + return cls.resolution_items + + @classmethod + def get_attr_defs_for_instance( + cls, create_context, instance + ): + if instance.product_type not in cls.product_types: + return [] + + # Get the resolution items + resolution_items = cls._get_resolution_items() + + items = [cls.default_resolution_item] + # Add all cached resolution items to the dropdown options + for item_text in resolution_items: + items.append((item_text, item_text)) + + return [ + EnumDef( + "explicit_resolution", + items, + default="Don't override", + label="Override Resolution" + ) + ] diff --git a/server/settings/publish_plugins.py b/server/settings/publish_plugins.py index 5f5891e4f4..7ad6c9c506 100644 --- a/server/settings/publish_plugins.py +++ b/server/settings/publish_plugins.py @@ -1,4 +1,6 @@ +from collections.abc import Iterable from pydantic import validator +from typing import Any from ayon_server.settings import ( BaseSettingsModel, @@ -8,7 +10,7 @@ from ayon_server.settings import ( ensure_unique_names, task_types_enum, ) - +from ayon_server.exceptions import BadRequestException from ayon_server.types import ColorRGBA_uint8 @@ -157,6 +159,77 @@ class CollectUSDLayerContributionsModel(BaseSettingsModel): return value +class ResolutionOptionsModel(BaseSettingsModel): + _layout = "compact" + width: int = SettingsField( + 1920, + ge=0, + le=100000, + title="Width", + description=( + "Width resolution number value"), + placeholder="Width" + ) + height: int = SettingsField( + 1080, + title="Height", + ge=0, + le=100000, + description=( + "Height resolution number value"), + placeholder="Height" + ) + pixel_aspect: float = SettingsField( + 1.0, + title="Pixel aspect", + ge=0.0, + le=100000.0, + description=( + "Pixel Aspect resolution decimal number value"), + placeholder="Pixel aspect" + ) + + +def ensure_unique_resolution_option( + objects: Iterable[Any], field_name: str | None = None) -> None: # noqa: C901 + """Ensure a list of objects have unique option attributes. + + This function checks if the list of objects has unique 'width', + 'height' and 'pixel_aspect' properties. + """ + options = [] + for obj in objects: + item_test_text = f"{obj.width}x{obj.height}x{obj.pixel_aspect}" + if item_test_text not in options: + options.append(item_test_text) + else: + raise BadRequestException( + f"Duplicate option '{item_test_text}'") + + +class CollectExplicitResolutionModel(BaseSettingsModel): + enabled: bool = SettingsField(True, title="Enabled") + product_types: list[str] = SettingsField( + default_factory=list, + title="Product types", + description=( + "Only activate the attribute for following product types." + ) + ) + options: list[ResolutionOptionsModel] = SettingsField( + default_factory=list, + title="Resolution options", + description=( + "Options to be provided in publisher attribute" + ) + ) + + @validator("options") + def validate_unique_options(cls, value): + ensure_unique_resolution_option(value) + return value + + class AyonEntityURIModel(BaseSettingsModel): use_ayon_entity_uri: bool = SettingsField( title="Use AYON Entity URI", @@ -988,6 +1061,10 @@ class PublishPuginsModel(BaseSettingsModel): title="Collect USD Layer Contributions", ) ) + CollectExplicitResolution: CollectExplicitResolutionModel = SettingsField( + default_factory=CollectExplicitResolutionModel, + title="Collect Explicit Resolution" + ) ValidateEditorialAssetName: ValidateBaseModel = SettingsField( default_factory=ValidateBaseModel, title="Validate Editorial Asset Name" @@ -1162,6 +1239,19 @@ DEFAULT_PUBLISH_VALUES = { }, ] }, + "CollectExplicitResolution": { + "enabled": True, + "product_types": [ + "shot" + ], + "options": [ + { + "width": 2048, + "height": 1080, + "aspect_ratio": 1.5, + } + ] + }, "ValidateEditorialAssetName": { "enabled": True, "optional": False, From 71d37d8b59ee3be0b5dcb784b4b478d40c5d2288 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 23 Apr 2025 14:04:41 +0200 Subject: [PATCH 048/108] Updated command creation Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/plugins/publish/extract_review.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index 3615cc53c9..a4eb1140ac 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -1079,9 +1079,8 @@ class ExtractReview(pyblish.api.InstancePlugin): """Fills missing files by blank frame.""" blank_frame_path = os.path.join(staging_dir, f"blank.{extension}") temp_data["paths_to_remove"].append(blank_frame_path) - command = get_ffmpeg_tool_args("ffmpeg") - - command.extend([ + command = get_ffmpeg_tool_args( + "ffmpeg", "-f", "lavfi", "-i", "color=c=black:s={}x{}:d=1".format( resolution_width, resolution_height @@ -1089,7 +1088,7 @@ class ExtractReview(pyblish.api.InstancePlugin): "-tune", "stillimage", "-frames:v", "1", blank_frame_path - ]) + ) self.log.debug("Executing: {}".format(" ".join(command))) output = run_subprocess( From c53de6d226ff48b8c30baaf4889563eb373e058f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 23 Apr 2025 14:05:08 +0200 Subject: [PATCH 049/108] Formatting change Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- server/settings/publish_plugins.py | 1 + 1 file changed, 1 insertion(+) diff --git a/server/settings/publish_plugins.py b/server/settings/publish_plugins.py index d487649f65..dbbbb9609d 100644 --- a/server/settings/publish_plugins.py +++ b/server/settings/publish_plugins.py @@ -20,6 +20,7 @@ def _handle_missing_frames_enum(): {"value": "only_rendered", "label": "Use only rendered"}, ] + class EnabledModel(BaseSettingsModel): enabled: bool = SettingsField(True) From 46d27ff7a4942fee39edf42ccab72cc05226fe40 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 23 Apr 2025 14:05:24 +0200 Subject: [PATCH 050/108] Formatting change Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- server/settings/publish_plugins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/settings/publish_plugins.py b/server/settings/publish_plugins.py index dbbbb9609d..cbe3894975 100644 --- a/server/settings/publish_plugins.py +++ b/server/settings/publish_plugins.py @@ -651,7 +651,7 @@ class ExtractReviewOutputDefModel(BaseSettingsModel): default_factory=ExtractReviewLetterBox, title="Letter Box" ) - fill_missing_frames:str = SettingsField( + fill_missing_frames: str = SettingsField( title="Handle missing frames", default="closest_existing", description="How to handle frames that are missing from entity frame " From 6d008edbada3d1ac3776b1ae51dc044c6454cb25 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 23 Apr 2025 14:05:56 +0200 Subject: [PATCH 051/108] Refactor first file query Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/plugins/publish/extract_review.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index a4eb1140ac..265bbb0828 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -1205,7 +1205,7 @@ class ExtractReview(pyblish.api.InstancePlugin): filled_files = temp_data["filled_files"] if filled_files: - first_frame, first_file = list(filled_files.items())[0] + first_frame, first_file = next(iter(filled_files.items())) if first_file < full_input_path_single_file: self.log.warning(f"Using filled frame: '{first_file}'") full_input_path_single_file = first_file From cc1ba078ed9f65608eb2a3035597bcf25c668d51 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 23 Apr 2025 14:07:33 +0200 Subject: [PATCH 052/108] Changed variable name Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/plugins/publish/extract_review.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index 265bbb0828..48bb2819ff 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -819,8 +819,8 @@ class ExtractReview(pyblish.api.InstancePlugin): staging_dir, "explicit_frames.txt") with open(explicit_frames_path, "w") as fp: lines = [ - f"file {file}" - for file in temp_data["explicit_input_paths"] + f"file {path}" + for path in temp_data["explicit_input_paths"] ] fp.write("\n".join(lines)) temp_data["paths_to_remove"].append(explicit_frames_path) From 24ec921ff6abce87a11f34a1162c11c310fbb634 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 23 Apr 2025 14:08:18 +0200 Subject: [PATCH 053/108] Formatting change Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/plugins/publish/extract_review.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index 48bb2819ff..cec6dd742c 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -829,7 +829,7 @@ class ExtractReview(pyblish.api.InstancePlugin): ffmpeg_input_args.extend([ "-f", "concat", "-safe", "0", - "-fflags", "+genpts+igndts", + "-fflags", "+genpts+igndts", "-i", path_to_subprocess_arg(explicit_frames_path), "-r", "25" ]) From ed2d0baaf27d25053c7b755e8587815346f9ea63 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 23 Apr 2025 14:12:43 +0200 Subject: [PATCH 054/108] Renamed key --- client/ayon_core/plugins/publish/extract_review.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index cec6dd742c..b8c75ff60a 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -433,7 +433,7 @@ class ExtractReview(pyblish.api.InstancePlugin): end_frame=temp_data["frame_end"], resolution_width=temp_data["resolution_width"], resolution_height=temp_data["resolution_height"], - extension=temp_data["ext"], + extension=temp_data["input_ext"], temp_data=temp_data ) elif fill_missing_frames == "previous_version": @@ -665,7 +665,7 @@ class ExtractReview(pyblish.api.InstancePlugin): "with_audio": with_audio, "without_handles": without_handles, "handles_are_set": handles_are_set, - "ext": ext, + "input_ext": ext, "explicit_input_paths": [], # absolute paths to rendered files "paths_to_remove": [] } From 1836daad6251065e684b2bb4c7908475fecd2851 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 23 Apr 2025 14:14:04 +0200 Subject: [PATCH 055/108] Refactor condition --- client/ayon_core/plugins/publish/extract_review.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index b8c75ff60a..252b3c7b6f 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -624,6 +624,8 @@ class ExtractReview(pyblish.api.InstancePlugin): input_is_sequence = self.input_is_sequence(repre) input_allow_bg = False first_sequence_frame = None + + ext = os.path.splitext(repre["files"])[1].replace(".", "") if input_is_sequence and repre["files"]: # Calculate first frame that should be used cols, _ = clique.assemble(repre["files"]) @@ -642,8 +644,6 @@ class ExtractReview(pyblish.api.InstancePlugin): ext = os.path.splitext(repre["files"][0])[1].replace(".", "") if ext.lower() in self.alpha_exts: input_allow_bg = True - else: - ext = os.path.splitext(repre["files"])[1].replace(".", "") return { "fps": float(instance.data["fps"]), From 3e042f4bcd1003793c1498822f88d37ca64538e2 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 23 Apr 2025 14:17:19 +0200 Subject: [PATCH 056/108] Fixed return type --- client/ayon_core/plugins/publish/extract_review.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index 252b3c7b6f..9b2a139515 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -1114,7 +1114,7 @@ class ExtractReview(pyblish.api.InstancePlugin): staging_dir: str, start_frame: int, end_frame: int - ) -> list: + ) -> Dict[int, str]: """Fill missing files in sequence by duplicating existing ones. This will take nearest frame file and copy it with so as to fill From 23bd9706d6b83d2b8b81d54d49d0111bf4579cda Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 23 Apr 2025 14:20:45 +0200 Subject: [PATCH 057/108] Replaced Union with Optional --- client/ayon_core/plugins/publish/extract_review.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index 9b2a139515..2577714675 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -5,7 +5,7 @@ import json import shutil import subprocess from abc import ABC, abstractmethod -from typing import Dict, Any, Union +from typing import Dict, Any, Optional import clique import speedcopy @@ -980,7 +980,7 @@ class ExtractReview(pyblish.api.InstancePlugin): current_repre: Dict[Any, Any], start_frame: int, end_frame: int - ) -> Union[Dict[int, str], None]: + ) -> Optional[Dict[int, str]]: """Tries to replace missing frames from ones from last version""" repre_file_paths = self._get_last_version_files( instance, current_repre) @@ -1075,7 +1075,7 @@ class ExtractReview(pyblish.api.InstancePlugin): resolution_height: int, extension: str, temp_data: Dict[str, Any] - ) -> Union[Dict[int, str], None]: + ) -> Optional[Dict[int, str]]: """Fills missing files by blank frame.""" blank_frame_path = os.path.join(staging_dir, f"blank.{extension}") temp_data["paths_to_remove"].append(blank_frame_path) From f73c6eccefe73ba61757231b5627cb6524959a8d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 23 Apr 2025 14:29:17 +0200 Subject: [PATCH 058/108] Simplified argument --- client/ayon_core/plugins/publish/extract_review.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index 2577714675..699bf42876 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -441,7 +441,7 @@ class ExtractReview(pyblish.api.InstancePlugin): collection=collection, staging_dir=new_repre["stagingDir"], instance=instance, - current_repre=repre, + current_repre_name=repre["name"], start_frame=temp_data["frame_start"], end_frame=temp_data["frame_end"], ) @@ -977,13 +977,13 @@ class ExtractReview(pyblish.api.InstancePlugin): collection: str, staging_dir: str, instance: pyblish.plugin.Instance, - current_repre: Dict[Any, Any], + current_repre_name: str, start_frame: int, end_frame: int ) -> Optional[Dict[int, str]]: """Tries to replace missing frames from ones from last version""" repre_file_paths = self._get_last_version_files( - instance, current_repre) + instance, current_repre_name) if repre_file_paths is None: # issues in getting last version files, falling back return None @@ -1031,7 +1031,7 @@ class ExtractReview(pyblish.api.InstancePlugin): def _get_last_version_files( self, instance: pyblish.plugin.Instance, - current_repre: Dict[Any, Any], + current_repre_name: str, ): product_name = instance.data["productName"] project_name = instance.data["projectEntity"]["name"] @@ -1052,7 +1052,7 @@ class ExtractReview(pyblish.api.InstancePlugin): ) matching_repre = None for repre in repres: - if repre["name"] == current_repre["name"]: + if repre["name"] == current_repre_name: matching_repre = repre break if not matching_repre: From fe78983491556d82716d146a0e83f3e44de0e190 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 23 Apr 2025 14:53:56 +0200 Subject: [PATCH 059/108] Fix condition --- client/ayon_core/plugins/publish/extract_review.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index 699bf42876..312b594acd 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -625,7 +625,6 @@ class ExtractReview(pyblish.api.InstancePlugin): input_allow_bg = False first_sequence_frame = None - ext = os.path.splitext(repre["files"])[1].replace(".", "") if input_is_sequence and repre["files"]: # Calculate first frame that should be used cols, _ = clique.assemble(repre["files"]) @@ -644,6 +643,8 @@ class ExtractReview(pyblish.api.InstancePlugin): ext = os.path.splitext(repre["files"][0])[1].replace(".", "") if ext.lower() in self.alpha_exts: input_allow_bg = True + else: + ext = os.path.splitext(repre["files"])[1].replace(".", "") return { "fps": float(instance.data["fps"]), From bc509fcf0084d465ff4d51f0f3391a03298e799f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 24 Apr 2025 11:37:26 +0200 Subject: [PATCH 060/108] Simplified querying for old repre --- .../ayon_core/plugins/publish/extract_review.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index 312b594acd..b56e5a2ac0 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -1047,17 +1047,16 @@ class ExtractReview(pyblish.api.InstancePlugin): if not version_entity: return None - repres = get_representations( + matching_repres = get_representations( project_name, - version_ids=[version_entity["id"]] + version_ids=[version_entity["id"]], + representation_names=[current_repre_name], + fields={"files"} ) - matching_repre = None - for repre in repres: - if repre["name"] == current_repre_name: - matching_repre = repre - break - if not matching_repre: + + if not matching_repres: return None + matching_repre = list(matching_repres)[0] repre_file_paths = [ file_info["path"] From e9d3462da28bf114bef0b950f897f9ef740cf9a9 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 24 Apr 2025 11:39:44 +0200 Subject: [PATCH 061/108] Fix description --- server/settings/publish_plugins.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/settings/publish_plugins.py b/server/settings/publish_plugins.py index cbe3894975..47dd5ebfb0 100644 --- a/server/settings/publish_plugins.py +++ b/server/settings/publish_plugins.py @@ -654,8 +654,7 @@ class ExtractReviewOutputDefModel(BaseSettingsModel): fill_missing_frames: str = SettingsField( title="Handle missing frames", default="closest_existing", - description="How to handle frames that are missing from entity frame " - "range.", + description="How to handle gaps in sequence frame ranges.", enum_resolver=_handle_missing_frames_enum ) From 468ab32b9a8c21e2add24bbdce7aad64ffe9ae50 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 24 Apr 2025 11:44:43 +0200 Subject: [PATCH 062/108] Remove unused import and add trailing commas --- .../plugins/publish/collect_explicit_resolution.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/client/ayon_core/plugins/publish/collect_explicit_resolution.py b/client/ayon_core/plugins/publish/collect_explicit_resolution.py index 3ff08cbd34..1aa7147627 100644 --- a/client/ayon_core/plugins/publish/collect_explicit_resolution.py +++ b/client/ayon_core/plugins/publish/collect_explicit_resolution.py @@ -1,13 +1,12 @@ import pyblish.api from ayon_core.lib import EnumDef -from ayon_core.pipeline import colorspace from ayon_core.pipeline import publish from ayon_core.pipeline.publish import PublishError class CollectExplicitResolution( pyblish.api.InstancePlugin, - publish.AYONPyblishPluginMixin + publish.AYONPyblishPluginMixin, ): """Collect explicit user defined resolution attributes for instances""" @@ -61,7 +60,7 @@ class CollectExplicitResolution( return { "resolutionWidth": item_values["width"], "resolutionHeight": item_values["height"], - "pixelAspect": item_values["pixel_aspect"] + "pixelAspect": item_values["pixel_aspect"], } else: raise PublishError( @@ -72,7 +71,8 @@ class CollectExplicitResolution( if cls.resolution_items is None: resolution_items = {} for item in cls.options: - item_text = f"{item['width']}x{item['height']}x{item['pixel_aspect']}" + item_text = ( + f"{item['width']}x{item['height']}x{item['pixel_aspect']}") resolution_items[item_text] = item cls.resolution_items = resolution_items @@ -81,7 +81,7 @@ class CollectExplicitResolution( @classmethod def get_attr_defs_for_instance( - cls, create_context, instance + cls, create_context, instance, ): if instance.product_type not in cls.product_types: return [] @@ -99,6 +99,6 @@ class CollectExplicitResolution( "explicit_resolution", items, default="Don't override", - label="Override Resolution" - ) + label="Override Resolution", + ), ] From 5410d69ad2137a91d51a1260f254470e605da125 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 24 Apr 2025 12:09:54 +0200 Subject: [PATCH 063/108] Simplified fill_root logic I must be wrong in my previous tests, it works even this simple way. --- client/ayon_core/plugins/publish/extract_review.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index b56e5a2ac0..a53e8eee8f 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -1005,13 +1005,7 @@ class ExtractReview(pyblish.api.InstancePlugin): hole_fpath = os.path.join(staging_dir, col_format % frame) previous_version_path = prev_col_format % frame - # limits too large padding coming from Anatomy - previous_version_path = ( - os.path.join( - anatomy.fill_root(os.path.dirname(previous_version_path)), - os.path.basename(previous_version_path) - ) - ) + previous_version_path = anatomy.fill_root(previous_version_path) if not os.path.exists(previous_version_path): self.log.warning( "Missing frame should be replaced from " From 4a38e1175f4b6c9304ce233fe3bf8af58d2c41b8 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 24 Apr 2025 13:20:33 +0200 Subject: [PATCH 064/108] Fix rendering explicit frames This seems only safe way --- .../plugins/publish/extract_review.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index a53e8eee8f..0c4b99cf66 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -526,12 +526,12 @@ class ExtractReview(pyblish.api.InstancePlugin): run_subprocess(subprcs_cmd, shell=True, logger=self.log) # delete files added to fill gaps - if new_frame_files: - for filepath in new_frame_files.values(): - os.unlink(filepath) - - for filepath in temp_data["paths_to_remove"]: - os.unlink(filepath) + # if new_frame_files: + # for filepath in new_frame_files.values(): + # # os.unlink(filepath) + # + # for filepath in temp_data["paths_to_remove"]: + # os.unlink(filepath) new_repre.update({ "fps": temp_data["fps"], @@ -818,9 +818,10 @@ class ExtractReview(pyblish.api.InstancePlugin): staging_dir = os.path.dirname(temp_data["full_input_path"]) explicit_frames_path = os.path.join( staging_dir, "explicit_frames.txt") + frame_duration = 1 / temp_data["fps"] with open(explicit_frames_path, "w") as fp: lines = [ - f"file {path}" + f"file '{path}'{os.linesep}duration {frame_duration}" for path in temp_data["explicit_input_paths"] ] fp.write("\n".join(lines)) @@ -830,9 +831,8 @@ class ExtractReview(pyblish.api.InstancePlugin): ffmpeg_input_args.extend([ "-f", "concat", "-safe", "0", - "-fflags", "+genpts+igndts", "-i", path_to_subprocess_arg(explicit_frames_path), - "-r", "25" + "-r", str(temp_data["fps"]) ]) # Add audio arguments if there are any. Skipped when output are images. From e5d673c0209c065112bcd8e192c5889b68b5d16e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 24 Apr 2025 13:23:40 +0200 Subject: [PATCH 065/108] Reverted unwanted commenting out --- client/ayon_core/plugins/publish/extract_review.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index 0c4b99cf66..daa58e0e93 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -525,13 +525,13 @@ class ExtractReview(pyblish.api.InstancePlugin): run_subprocess(subprcs_cmd, shell=True, logger=self.log) - # delete files added to fill gaps - # if new_frame_files: - # for filepath in new_frame_files.values(): - # # os.unlink(filepath) - # - # for filepath in temp_data["paths_to_remove"]: - # os.unlink(filepath) + #delete files added to fill gaps + if new_frame_files: + for filepath in new_frame_files.values(): + os.unlink(filepath) + + for filepath in temp_data["paths_to_remove"]: + os.unlink(filepath) new_repre.update({ "fps": temp_data["fps"], From 026ec6419673c8c896524bb83ce74fd315243050 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 24 Apr 2025 14:19:53 +0200 Subject: [PATCH 066/108] Fix typo Co-authored-by: Roy Nieterau --- client/ayon_core/plugins/publish/extract_review.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index daa58e0e93..f824e1db3c 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -525,7 +525,7 @@ class ExtractReview(pyblish.api.InstancePlugin): run_subprocess(subprcs_cmd, shell=True, logger=self.log) - #delete files added to fill gaps + # delete files added to fill gaps if new_frame_files: for filepath in new_frame_files.values(): os.unlink(filepath) From f47c0b4027eccc0ce17962c3298a21f5ce3745d0 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 29 Apr 2025 16:21:40 +0800 Subject: [PATCH 067/108] If there is no valid review representation for thumbnail creation, make sure the representation is with the image content so that it can create thumbnail --- .../plugins/publish/extract_thumbnail.py | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_thumbnail.py b/client/ayon_core/plugins/publish/extract_thumbnail.py index b72862ea22..adcd7be846 100644 --- a/client/ayon_core/plugins/publish/extract_thumbnail.py +++ b/client/ayon_core/plugins/publish/extract_thumbnail.py @@ -17,7 +17,7 @@ from ayon_core.lib import ( ) from ayon_core.lib.transcoding import convert_colorspace -from ayon_core.lib.transcoding import VIDEO_EXTENSIONS +from ayon_core.lib.transcoding import VIDEO_EXTENSIONS, IMAGE_EXTENSIONS class ExtractThumbnail(pyblish.api.InstancePlugin): @@ -349,7 +349,8 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): continue if "review" not in tags: - continue + if not self._is_valid_media_repre(repre): + continue if not repre.get("files"): self.log.debug(( @@ -360,6 +361,21 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): filtered_repres.append(repre) return filtered_repres + def _is_valid_media_repre(self, repre): + """Check if representation contains valid media files""" + files = repre.get("files") + if not files: + return False + + # Get first file's extension + if isinstance(files, (list, tuple)): + first_file = files[0] + else: + first_file = files + + ext = os.path.splitext(first_file)[1].lower() + return ext in IMAGE_EXTENSIONS + def _create_thumbnail_oiio( self, src_path, From 59e986dde23fb53cac335ca454b7405d9579ec1e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 29 Apr 2025 16:23:27 +0800 Subject: [PATCH 068/108] add debug message into self._is_valid_media_repre --- client/ayon_core/plugins/publish/extract_thumbnail.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/ayon_core/plugins/publish/extract_thumbnail.py b/client/ayon_core/plugins/publish/extract_thumbnail.py index adcd7be846..47710de5f6 100644 --- a/client/ayon_core/plugins/publish/extract_thumbnail.py +++ b/client/ayon_core/plugins/publish/extract_thumbnail.py @@ -365,6 +365,9 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): """Check if representation contains valid media files""" files = repre.get("files") if not files: + self.log.debug(( + "Representation \"{}\" doesn't have files. Skipping" + ).format(repre["name"])) return False # Get first file's extension From 637b157fd387c5d7943c2f57ec3508aaa79a3823 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 29 Apr 2025 16:24:28 +0800 Subject: [PATCH 069/108] rename self._is_valid_media_repre to self._is_valid_image_repre --- client/ayon_core/plugins/publish/extract_thumbnail.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_thumbnail.py b/client/ayon_core/plugins/publish/extract_thumbnail.py index 47710de5f6..b48e6a69b7 100644 --- a/client/ayon_core/plugins/publish/extract_thumbnail.py +++ b/client/ayon_core/plugins/publish/extract_thumbnail.py @@ -349,7 +349,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): continue if "review" not in tags: - if not self._is_valid_media_repre(repre): + if not self._is_valid_images_repre(repre): continue if not repre.get("files"): @@ -361,8 +361,8 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): filtered_repres.append(repre) return filtered_repres - def _is_valid_media_repre(self, repre): - """Check if representation contains valid media files""" + def _is_valid_images_repre(self, repre): + """Check if representation contains valid image files""" files = repre.get("files") if not files: self.log.debug(( From 625e782d7e2065bde600f3cd6c99387b411a3d10 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 29 Apr 2025 16:32:17 +0800 Subject: [PATCH 070/108] improve the docstring --- client/ayon_core/plugins/publish/extract_thumbnail.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/extract_thumbnail.py b/client/ayon_core/plugins/publish/extract_thumbnail.py index b48e6a69b7..6e0d899f30 100644 --- a/client/ayon_core/plugins/publish/extract_thumbnail.py +++ b/client/ayon_core/plugins/publish/extract_thumbnail.py @@ -362,7 +362,14 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): return filtered_repres def _is_valid_images_repre(self, repre): - """Check if representation contains valid image files""" + """Check if representation contains valid image files + + Args: + repre (dict): representation + + Returns: + bool: whether the representation has the valid image content + """ files = repre.get("files") if not files: self.log.debug(( From c0db02f7b519eb36ab249f60d5c122053cca862d Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 29 Apr 2025 17:14:14 +0800 Subject: [PATCH 071/108] improve the check on the valid representations for thumbnail creation --- .../plugins/publish/extract_thumbnail.py | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_thumbnail.py b/client/ayon_core/plugins/publish/extract_thumbnail.py index 6e0d899f30..cb941a53a9 100644 --- a/client/ayon_core/plugins/publish/extract_thumbnail.py +++ b/client/ayon_core/plugins/publish/extract_thumbnail.py @@ -336,7 +336,8 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): return need_thumb_repres def _get_filtered_repres(self, instance): - filtered_repres = [] + review_repres = [] + other_repres = [] src_repres = instance.data.get("representations") or [] for repre in src_repres: @@ -348,18 +349,22 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # to be published locally continue - if "review" not in tags: - if not self._is_valid_images_repre(repre): - continue - if not repre.get("files"): self.log.debug(( "Representation \"{}\" doesn't have files. Skipping" ).format(repre["name"])) continue - filtered_repres.append(repre) - return filtered_repres + has_review_tag = "review" in tags + if not has_review_tag and not self._is_valid_images_repre(repre): + continue + + if has_review_tag: + review_repres.append(repre) + else: + other_repres.append(repre) + + return review_repres + other_repres def _is_valid_images_repre(self, repre): """Check if representation contains valid image files @@ -372,9 +377,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): """ files = repre.get("files") if not files: - self.log.debug(( - "Representation \"{}\" doesn't have files. Skipping" - ).format(repre["name"])) return False # Get first file's extension @@ -384,7 +386,8 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): first_file = files ext = os.path.splitext(first_file)[1].lower() - return ext in IMAGE_EXTENSIONS + + return ext in IMAGE_EXTENSIONS or ext in VIDEO_EXTENSIONS def _create_thumbnail_oiio( self, From 4bb0e14106cb65e5937225695ef336c5e87a3a1d Mon Sep 17 00:00:00 2001 From: Kayla Man <64118225+moonyuet@users.noreply.github.com> Date: Tue, 29 Apr 2025 19:10:01 +0800 Subject: [PATCH 072/108] Update client/ayon_core/plugins/publish/extract_thumbnail.py Co-authored-by: Roy Nieterau --- client/ayon_core/plugins/publish/extract_thumbnail.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_thumbnail.py b/client/ayon_core/plugins/publish/extract_thumbnail.py index cb941a53a9..9592264ee2 100644 --- a/client/ayon_core/plugins/publish/extract_thumbnail.py +++ b/client/ayon_core/plugins/publish/extract_thumbnail.py @@ -356,12 +356,9 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): continue has_review_tag = "review" in tags - if not has_review_tag and not self._is_valid_images_repre(repre): - continue - if has_review_tag: review_repres.append(repre) - else: + elif self._is_valid_images_repre(repre): other_repres.append(repre) return review_repres + other_repres From 212a918b3151b9d69ec0eafc69f911b61561e259 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 29 Apr 2025 13:25:49 +0200 Subject: [PATCH 073/108] added base ruff config file --- ruff.toml | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 ruff.toml diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 0000000000..153a5c7d88 --- /dev/null +++ b/ruff.toml @@ -0,0 +1,74 @@ +# Exclude a variety of commonly ignored directories. +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".ipynb_checkpoints", + ".mypy_cache", + ".nox", + ".pants.d", + ".pyenv", + ".pytest_cache", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".vscode", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "site-packages", + "venv", +] + +# Same as Black. +line-length = 79 +indent-width = 4 + +[lint] +# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. +# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or +# McCabe complexity (`C901`) by default. +select = ["E", "F", "W"] +ignore = [] + +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = [] + +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +[format] +# Like Black, use double quotes for strings. +quote-style = "double" + +# Like Black, indent with spaces, rather than tabs. +indent-style = "space" + +# Like Black, respect magic trailing commas. +skip-magic-trailing-comma = false + +# Like Black, automatically detect the appropriate line ending. +line-ending = "auto" + +# Enable auto-formatting of code examples in docstrings. Markdown, +# reStructuredText code/literal blocks and doctests are all supported. +# +# This is currently disabled by default, but it is planned for this +# to be opt-out in the future. +docstring-code-format = false + +# Set the line length limit used when formatting code snippets in +# docstrings. +# +# This only has an effect when the `docstring-code-format` setting is +# enabled. +docstring-code-line-length = "dynamic" From 644f79bf0d67341f05474240840fe129faa459eb Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 29 Apr 2025 13:29:17 +0200 Subject: [PATCH 074/108] added specific ignores --- ruff.toml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ruff.toml b/ruff.toml index 153a5c7d88..e94728c0fc 100644 --- a/ruff.toml +++ b/ruff.toml @@ -26,6 +26,8 @@ exclude = [ "node_modules", "site-packages", "venv", + "vendor", + "generated", ] # Same as Black. @@ -46,6 +48,14 @@ unfixable = [] # Allow unused variables when underscore-prefixed. dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" +exclude = [ + "client/ayon_core/modules/click_wrap.py", + "client/ayon_core/scripts/slates/__init__.py" +] + +[lint.per-file-ignores] +"client/ayon_core/lib/__init__.py" = ["E402"] + [format] # Like Black, use double quotes for strings. quote-style = "double" From 79dbdb38e2c682a126d0c7f75aad00f6ecabef02 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 29 Apr 2025 13:29:36 +0200 Subject: [PATCH 075/108] added linting details --- ruff.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ruff.toml b/ruff.toml index e94728c0fc..49dd7a8c17 100644 --- a/ruff.toml +++ b/ruff.toml @@ -35,9 +35,9 @@ line-length = 79 indent-width = 4 [lint] +preview = true +pydocstyle.convention = "google" # Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. -# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or -# McCabe complexity (`C901`) by default. select = ["E", "F", "W"] ignore = [] From 9f7f1ed314d6e2419b587053229e2bb870812765 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 29 Apr 2025 13:29:43 +0200 Subject: [PATCH 076/108] added target python --- ruff.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ruff.toml b/ruff.toml index 49dd7a8c17..701055a65a 100644 --- a/ruff.toml +++ b/ruff.toml @@ -34,6 +34,9 @@ exclude = [ line-length = 79 indent-width = 4 +# Assume Python 3.9 +target-version = "py39" + [lint] preview = true pydocstyle.convention = "google" From 87514ba2e30a13e30d2bccfe6db6bd5962a87b8c Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 29 Apr 2025 13:30:02 +0200 Subject: [PATCH 077/108] remove ruff config from pyproject.toml --- pyproject.toml | 76 -------------------------------------------------- 1 file changed, 76 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 4c4272cc30..309b0f91a1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,82 +41,6 @@ pymdown-extensions = "^10.14.3" mike = "^2.1.3" mkdocstrings-shell = "^1.0.2" - -[tool.ruff] -# Exclude a variety of commonly ignored directories. -exclude = [ - ".bzr", - ".direnv", - ".eggs", - ".git", - ".git-rewrite", - ".hg", - ".ipynb_checkpoints", - ".mypy_cache", - ".nox", - ".pants.d", - ".pyenv", - ".pytest_cache", - ".pytype", - ".ruff_cache", - ".svn", - ".tox", - ".venv", - ".vscode", - "__pypackages__", - "_build", - "buck-out", - "build", - "dist", - "node_modules", - "site-packages", - "venv", - "vendor", - "generated", -] - -# Same as Black. -line-length = 79 -indent-width = 4 - -# Assume Python 3.9 -target-version = "py39" - -[tool.ruff.lint] -preview = true -pydocstyle.convention = "google" -# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. -select = ["E", "F", "W"] -ignore = [] - -# Allow fix for all enabled rules (when `--fix`) is provided. -fixable = ["ALL"] -unfixable = [] - -# Allow unused variables when underscore-prefixed. -dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" - -exclude = [ - "client/ayon_core/modules/click_wrap.py", - "client/ayon_core/scripts/slates/__init__.py" -] - -[tool.ruff.lint.per-file-ignores] -"client/ayon_core/lib/__init__.py" = ["E402"] - -[tool.ruff.format] -# Like Black, use double quotes for strings. -quote-style = "double" - -# Like Black, indent with spaces, rather than tabs. -indent-style = "space" - -# Like Black, respect magic trailing commas. -skip-magic-trailing-comma = false - -# Like Black, automatically detect the appropriate line ending. -line-ending = "auto" - [tool.codespell] # Ignore words that are not in the dictionary. ignore-words-list = "ayon,ynput,parms,parm,hda,developpement" From 2cb2a71e51e37614a12fc0c23378b61d57eee716 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 29 Apr 2025 13:31:07 +0200 Subject: [PATCH 078/108] remove outdated paths --- pyproject.toml | 2 +- ruff.toml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 309b0f91a1..51f6048d8a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ ignore-words-list = "ayon,ynput,parms,parm,hda,developpement" # Remove with next codespell release (>2.2.6) ignore-regex = ".*codespell:ignore.*" -skip = "./.*,./package/*,*/vendor/*,*/unreal/integration/*,*/aftereffects/api/extension/js/libs/*" +skip = "./.*,./package/*,*/client/ayon_core/vendor/*" count = true quiet-level = 3 diff --git a/ruff.toml b/ruff.toml index 701055a65a..f9b073e818 100644 --- a/ruff.toml +++ b/ruff.toml @@ -52,7 +52,6 @@ unfixable = [] dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" exclude = [ - "client/ayon_core/modules/click_wrap.py", "client/ayon_core/scripts/slates/__init__.py" ] From 591bf7c57be83a34677e4df720a42dd02d6b6017 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 29 Apr 2025 14:01:01 +0200 Subject: [PATCH 079/108] bump ruff version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 51f6048d8a..3d85c30eda 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ pytest = "^8.0" pytest-print = "^1.0" ayon-python-api = "^1.0" # linting dependencies -ruff = "^0.3.3" +ruff = "0.11.7" pre-commit = "^3.6.2" codespell = "^2.2.6" semver = "^3.0.2" From c32cdba660ae1d30bfb22831cb3d63dfec325d82 Mon Sep 17 00:00:00 2001 From: Kayla Man <64118225+moonyuet@users.noreply.github.com> Date: Tue, 29 Apr 2025 20:40:50 +0800 Subject: [PATCH 080/108] Update client/ayon_core/plugins/publish/extract_thumbnail.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/plugins/publish/extract_thumbnail.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_thumbnail.py b/client/ayon_core/plugins/publish/extract_thumbnail.py index 9592264ee2..f67c571c90 100644 --- a/client/ayon_core/plugins/publish/extract_thumbnail.py +++ b/client/ayon_core/plugins/publish/extract_thumbnail.py @@ -372,15 +372,10 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): Returns: bool: whether the representation has the valid image content """ - files = repre.get("files") - if not files: - return False - # Get first file's extension - if isinstance(files, (list, tuple)): - first_file = files[0] - else: - first_file = files + first_file = repre["files"] + if isinstance(first_file, (list, tuple)): + first_file = first_file[0] ext = os.path.splitext(first_file)[1].lower() From 6d517d5e87074d7b4d4295c96e9bb405b36306b2 Mon Sep 17 00:00:00 2001 From: Kayla Man <64118225+moonyuet@users.noreply.github.com> Date: Tue, 29 Apr 2025 20:41:01 +0800 Subject: [PATCH 081/108] Update client/ayon_core/plugins/publish/extract_thumbnail.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/plugins/publish/extract_thumbnail.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_thumbnail.py b/client/ayon_core/plugins/publish/extract_thumbnail.py index f67c571c90..3a428c46a7 100644 --- a/client/ayon_core/plugins/publish/extract_thumbnail.py +++ b/client/ayon_core/plugins/publish/extract_thumbnail.py @@ -355,8 +355,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): ).format(repre["name"])) continue - has_review_tag = "review" in tags - if has_review_tag: + if "review" in tags: review_repres.append(repre) elif self._is_valid_images_repre(repre): other_repres.append(repre) From 0f977b92840eda1a55e7489d651232c3dbe512f5 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 29 Apr 2025 15:52:48 +0200 Subject: [PATCH 082/108] Add addon version compatibility --- package.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.py b/package.py index 0358c2f4cd..d1e2fac5ca 100644 --- a/package.py +++ b/package.py @@ -11,4 +11,6 @@ ayon_launcher_version = ">=1.0.2" ayon_required_addons = {} ayon_compatible_addons = { "harmony": ">0.4.0", + "fusion": ">=0.3.3", + "openrv": ">=1.0.2" } From 5d8e3e37c1f32ad5e314588dc2210b13b3d57eb1 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 29 Apr 2025 15:53:00 +0200 Subject: [PATCH 083/108] Cosmetics --- package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.py b/package.py index d1e2fac5ca..9fbb6a7000 100644 --- a/package.py +++ b/package.py @@ -12,5 +12,5 @@ ayon_required_addons = {} ayon_compatible_addons = { "harmony": ">0.4.0", "fusion": ">=0.3.3", - "openrv": ">=1.0.2" + "openrv": ">=1.0.2", } From 20435c18bcb770f0fe03a1144181a917d09670ac Mon Sep 17 00:00:00 2001 From: Ynbot Date: Tue, 29 Apr 2025 13:56:28 +0000 Subject: [PATCH 084/108] [Automated] Add generated package files from main --- client/ayon_core/version.py | 2 +- package.py | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/version.py b/client/ayon_core/version.py index 01e431577e..6bc1e253c4 100644 --- a/client/ayon_core/version.py +++ b/client/ayon_core/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring AYON addon 'core' version.""" -__version__ = "1.1.8+dev" +__version__ = "1.1.9" diff --git a/package.py b/package.py index 0358c2f4cd..111cfd08d0 100644 --- a/package.py +++ b/package.py @@ -1,6 +1,6 @@ name = "core" title = "Core" -version = "1.1.8+dev" +version = "1.1.9" client_dir = "ayon_core" diff --git a/pyproject.toml b/pyproject.toml index 3d85c30eda..dd85bba802 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ [tool.poetry] name = "ayon-core" -version = "1.1.8+dev" +version = "1.1.9" description = "" authors = ["Ynput Team "] readme = "README.md" From 839550d0cf5d89cbe6e2dc3fbf4306d8b383b628 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 29 Apr 2025 13:57:15 +0000 Subject: [PATCH 085/108] chore(): update bug report / version --- .github/ISSUE_TEMPLATE/bug_report.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index c0ab04abef..f602156aea 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,16 @@ body: label: Version description: What version are you running? Look to AYON Tray options: + - 1.1.9 + - 1.1.8 + - 1.1.7 + - 1.1.6 + - 1.1.5 + - 1.1.4 + - 1.1.3 + - 1.1.2 + - 1.1.1 + - 1.1.0 - 1.0.14 - 1.0.13 - 1.0.12 From abe038ee16d657d59e1bcb03b3c5e324e7fcff1c Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 29 Apr 2025 16:13:02 +0200 Subject: [PATCH 086/108] bump version to 1.1.9+dev --- client/ayon_core/version.py | 2 +- package.py | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/version.py b/client/ayon_core/version.py index 6bc1e253c4..ed56f67bb4 100644 --- a/client/ayon_core/version.py +++ b/client/ayon_core/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring AYON addon 'core' version.""" -__version__ = "1.1.9" +__version__ = "1.1.9+dev" diff --git a/package.py b/package.py index 111cfd08d0..d28ebd059f 100644 --- a/package.py +++ b/package.py @@ -1,6 +1,6 @@ name = "core" title = "Core" -version = "1.1.9" +version = "1.1.9+dev" client_dir = "ayon_core" diff --git a/pyproject.toml b/pyproject.toml index dd85bba802..5a89fbf7e3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ [tool.poetry] name = "ayon-core" -version = "1.1.9" +version = "1.1.9+dev" description = "" authors = ["Ynput Team "] readme = "README.md" From b85e6b72cbad55190a0a539ca0ef9fd3d5de5b4a Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 29 Apr 2025 17:05:13 +0200 Subject: [PATCH 087/108] run update bug report after release trigger --- .github/workflows/update_bug_report.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/update_bug_report.yml b/.github/workflows/update_bug_report.yml index 1e5da414bb..98a8454e4b 100644 --- a/.github/workflows/update_bug_report.yml +++ b/.github/workflows/update_bug_report.yml @@ -1,10 +1,11 @@ name: 🐞 Update Bug Report on: + workflow_run: + workflows: ["🚀 Release Trigger"] + types: + - completed workflow_dispatch: - release: - # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#release - types: [published] jobs: update-bug-report: From 52d0cc8748707fb694c333b8f78693e0928d5759 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 30 Apr 2025 17:00:09 +0200 Subject: [PATCH 088/108] :sparkles: add hook to filter paths --- .../hooks/pre_remove_launcher_paths.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 client/ayon_core/hooks/pre_remove_launcher_paths.py diff --git a/client/ayon_core/hooks/pre_remove_launcher_paths.py b/client/ayon_core/hooks/pre_remove_launcher_paths.py new file mode 100644 index 0000000000..a96978b368 --- /dev/null +++ b/client/ayon_core/hooks/pre_remove_launcher_paths.py @@ -0,0 +1,36 @@ +""""Pre launch hook to remove launcher paths from the system.""" +import os +from ayon_applications import PreLaunchHook, LaunchTypes + + +class PreRemoveLauncherPaths(PreLaunchHook): + """Remove launcher paths from the system. + + This hook is used to remove launcher paths from the system before launching + an application. It is used to ensure that the application is launched with + the correct environment variables. Especially for Windows, where + paths in `PATH` are used to load DLLs. This is important to avoid + conflicts with other applications that may have the same DLLs in their + paths. + """ + + order = 1 + + platforms = {"linux", "windows", "darwin"} + launch_types = {LaunchTypes.local} + + def execute(self): + # Remove launcher paths from the system + paths = [] + try: + ayon_root = self.launch_context.env["AYON_ROOT"] + except KeyError: + self.log.warning("AYON_ROOT not found in environment variables.") + return + + paths.extend( + path + for path in self.launch_context.env.get("PATH", "").split(os.pathsep) + if not path.startswith(ayon_root) + ) + self.launch_context.env["PATH"] = os.pathsep.join(paths) From cd7c4a37783b3c53b8e934a8524a18941c9ac0c0 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 30 Apr 2025 17:20:44 +0200 Subject: [PATCH 089/108] :recycle: make checks safer --- client/ayon_core/hooks/pre_remove_launcher_paths.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/hooks/pre_remove_launcher_paths.py b/client/ayon_core/hooks/pre_remove_launcher_paths.py index a96978b368..800df0c214 100644 --- a/client/ayon_core/hooks/pre_remove_launcher_paths.py +++ b/client/ayon_core/hooks/pre_remove_launcher_paths.py @@ -23,14 +23,14 @@ class PreRemoveLauncherPaths(PreLaunchHook): # Remove launcher paths from the system paths = [] try: - ayon_root = self.launch_context.env["AYON_ROOT"] + ayon_root = os.path.normpath(self.launch_context.env["AYON_ROOT"]) except KeyError: self.log.warning("AYON_ROOT not found in environment variables.") return paths.extend( path - for path in self.launch_context.env.get("PATH", "").split(os.pathsep) - if not path.startswith(ayon_root) + for path in self.launch_context.env.get("PATH").split(os.pathsep) + if not os.path.normpath(path).startswith(ayon_root) ) self.launch_context.env["PATH"] = os.pathsep.join(paths) From 1f48b1568d0a988e749e42436ac545547756d726 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Wed, 30 Apr 2025 19:02:55 +0200 Subject: [PATCH 090/108] Update client/ayon_core/hooks/pre_remove_launcher_paths.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/hooks/pre_remove_launcher_paths.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/hooks/pre_remove_launcher_paths.py b/client/ayon_core/hooks/pre_remove_launcher_paths.py index 800df0c214..ee6ebd8950 100644 --- a/client/ayon_core/hooks/pre_remove_launcher_paths.py +++ b/client/ayon_core/hooks/pre_remove_launcher_paths.py @@ -30,7 +30,7 @@ class PreRemoveLauncherPaths(PreLaunchHook): paths.extend( path - for path in self.launch_context.env.get("PATH").split(os.pathsep) + for path in self.launch_context.env.get("PATH", "").split(os.pathsep) if not os.path.normpath(path).startswith(ayon_root) ) self.launch_context.env["PATH"] = os.pathsep.join(paths) From edabad6c13ec98e05a3b8c3f4d0e1b32b462165c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Wed, 30 Apr 2025 19:07:30 +0200 Subject: [PATCH 091/108] Update client/ayon_core/hooks/pre_remove_launcher_paths.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/hooks/pre_remove_launcher_paths.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/client/ayon_core/hooks/pre_remove_launcher_paths.py b/client/ayon_core/hooks/pre_remove_launcher_paths.py index ee6ebd8950..4e3835d08b 100644 --- a/client/ayon_core/hooks/pre_remove_launcher_paths.py +++ b/client/ayon_core/hooks/pre_remove_launcher_paths.py @@ -22,11 +22,7 @@ class PreRemoveLauncherPaths(PreLaunchHook): def execute(self): # Remove launcher paths from the system paths = [] - try: - ayon_root = os.path.normpath(self.launch_context.env["AYON_ROOT"]) - except KeyError: - self.log.warning("AYON_ROOT not found in environment variables.") - return + ayon_root = os.path.normpath(os.environ["AYON_ROOT"]) paths.extend( path From 624dfcccadb4595d90f5c92127a30fbca99be897 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 2 May 2025 14:46:23 +0200 Subject: [PATCH 092/108] :recycle: some refactoring --- .../hooks/pre_remove_launcher_paths.py | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/client/ayon_core/hooks/pre_remove_launcher_paths.py b/client/ayon_core/hooks/pre_remove_launcher_paths.py index 4e3835d08b..96ee997501 100644 --- a/client/ayon_core/hooks/pre_remove_launcher_paths.py +++ b/client/ayon_core/hooks/pre_remove_launcher_paths.py @@ -1,6 +1,10 @@ """"Pre launch hook to remove launcher paths from the system.""" +from __future__ import annotations + import os -from ayon_applications import PreLaunchHook, LaunchTypes +from typing import ClassVar + +from ayon_applications import PreLaunchHook class PreRemoveLauncherPaths(PreLaunchHook): @@ -15,18 +19,17 @@ class PreRemoveLauncherPaths(PreLaunchHook): """ order = 1 + launch_types: ClassVar[set] = set() - platforms = {"linux", "windows", "darwin"} - launch_types = {LaunchTypes.local} - - def execute(self): + def execute(self) -> None: + """Execute the hook.""" # Remove launcher paths from the system - paths = [] ayon_root = os.path.normpath(os.environ["AYON_ROOT"]) - paths.extend( + paths = [ path - for path in self.launch_context.env.get("PATH", "").split(os.pathsep) + for path in self.launch_context.env.get( + "PATH", "").split(os.pathsep) if not os.path.normpath(path).startswith(ayon_root) - ) + ] self.launch_context.env["PATH"] = os.pathsep.join(paths) From 917e32cb13923dfdc77d4bbdd1b041ee8492f3f4 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 2 May 2025 14:47:46 +0200 Subject: [PATCH 093/108] :recycle: remove unnecessary type hinting --- client/ayon_core/hooks/pre_remove_launcher_paths.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/client/ayon_core/hooks/pre_remove_launcher_paths.py b/client/ayon_core/hooks/pre_remove_launcher_paths.py index 96ee997501..df27e512d0 100644 --- a/client/ayon_core/hooks/pre_remove_launcher_paths.py +++ b/client/ayon_core/hooks/pre_remove_launcher_paths.py @@ -1,8 +1,5 @@ """"Pre launch hook to remove launcher paths from the system.""" -from __future__ import annotations - import os -from typing import ClassVar from ayon_applications import PreLaunchHook @@ -17,9 +14,7 @@ class PreRemoveLauncherPaths(PreLaunchHook): conflicts with other applications that may have the same DLLs in their paths. """ - order = 1 - launch_types: ClassVar[set] = set() def execute(self) -> None: """Execute the hook.""" From 0063465626bb81f4a8cf9579576d406b473e189b Mon Sep 17 00:00:00 2001 From: Ynbot Date: Fri, 2 May 2025 12:51:59 +0000 Subject: [PATCH 094/108] [Automated] Add generated package files from main --- client/ayon_core/version.py | 2 +- package.py | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/version.py b/client/ayon_core/version.py index ed56f67bb4..af87dbce6c 100644 --- a/client/ayon_core/version.py +++ b/client/ayon_core/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring AYON addon 'core' version.""" -__version__ = "1.1.9+dev" +__version__ = "1.2.0" diff --git a/package.py b/package.py index 6229131f63..d6c58f223a 100644 --- a/package.py +++ b/package.py @@ -1,6 +1,6 @@ name = "core" title = "Core" -version = "1.1.9+dev" +version = "1.2.0" client_dir = "ayon_core" diff --git a/pyproject.toml b/pyproject.toml index 5a89fbf7e3..2ce6d971dd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ [tool.poetry] name = "ayon-core" -version = "1.1.9+dev" +version = "1.2.0" description = "" authors = ["Ynput Team "] readme = "README.md" From 75110078618beab1f636bf26c24dcfd9b0393c75 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Fri, 2 May 2025 12:52:33 +0000 Subject: [PATCH 095/108] [Automated] Update version in package.py for develop --- client/ayon_core/version.py | 2 +- package.py | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/version.py b/client/ayon_core/version.py index af87dbce6c..4fd7bde336 100644 --- a/client/ayon_core/version.py +++ b/client/ayon_core/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring AYON addon 'core' version.""" -__version__ = "1.2.0" +__version__ = "1.2.0+dev" diff --git a/package.py b/package.py index d6c58f223a..1695cc7808 100644 --- a/package.py +++ b/package.py @@ -1,6 +1,6 @@ name = "core" title = "Core" -version = "1.2.0" +version = "1.2.0+dev" client_dir = "ayon_core" diff --git a/pyproject.toml b/pyproject.toml index 2ce6d971dd..c7e2bb5000 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ [tool.poetry] name = "ayon-core" -version = "1.2.0" +version = "1.2.0+dev" description = "" authors = ["Ynput Team "] readme = "README.md" From 3bd3c2f6f5bf85c95f172b1b97e2e2a685b09fcf Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 2 May 2025 12:53:24 +0000 Subject: [PATCH 096/108] chore(): update bug report / version --- .github/ISSUE_TEMPLATE/bug_report.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index f602156aea..c1e18faf55 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,7 @@ body: label: Version description: What version are you running? Look to AYON Tray options: + - 1.2.0 - 1.1.9 - 1.1.8 - 1.1.7 From 47f8dcdce6871e96e295d0ab6d00957877360666 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 5 May 2025 12:05:01 +0200 Subject: [PATCH 097/108] Updates OCIO config paths Updates the built-in OCIO config paths to correct versioning and descriptions, ensuring accurate configuration options. --- server/settings/main.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/server/settings/main.py b/server/settings/main.py index 97434d0b93..a582763b4b 100644 --- a/server/settings/main.py +++ b/server/settings/main.py @@ -72,10 +72,10 @@ def _fallback_ocio_config_profile_types(): def _ocio_built_in_paths(): return [ { - "value": "{BUILTIN_OCIO_ROOT}/aces_1.3/studio-config-v1.0.0_aces-v1.3_ocio-v2.0.ocio", # noqa: E501 - "label": "ACES 1.3 Studio (OCIO v2)", + "value": "{BUILTIN_OCIO_ROOT}/aces_2.0/studio-config-v3.0.0_aces-v2.0_ocio-v2.4", # noqa: E501 + "label": "ACES 2.0 Studio (OCIO v2.4)", "description": ( - "Aces 1.3 Studio OCIO config file. Requires OCIO v2.") + "Aces 2.0 Studio OCIO config file. Requires OCIO v2.4.") }, { "value": "{BUILTIN_OCIO_ROOT}/aces_1.3/studio-config-v1.0.0_aces-v1.3_ocio-v2.1.ocio", # noqa: E501 @@ -84,10 +84,10 @@ def _ocio_built_in_paths(): "Aces 1.3 Studio OCIO config file. Requires OCIO v2.1.") }, { - "value": "{BUILTIN_OCIO_ROOT}/aces_2.0/studio-config-v3.0.0_aces-v2.0_ocio-v2.4", # noqa: E501 - "label": "ACES 2.0 Studio (OCIO v2.4)", + "value": "{BUILTIN_OCIO_ROOT}/aces_1.3/studio-config-v1.0.0_aces-v1.3_ocio-v2.0.ocio", # noqa: E501 + "label": "ACES 1.3 Studio (OCIO v2)", "description": ( - "Aces 2.0 Studio OCIO config file. Requires OCIO v2.4.") + "Aces 1.3 Studio OCIO config file. Requires OCIO v2.") }, { "value": "{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio", From 8ef5c45eba8d074a01432c8f4373239baa33210d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 5 May 2025 16:05:26 +0200 Subject: [PATCH 098/108] Generate blank frame only if necessary --- .../plugins/publish/extract_review.py | 41 +++++++++++++------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index f824e1db3c..de1c785475 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -1071,8 +1071,35 @@ class ExtractReview(pyblish.api.InstancePlugin): temp_data: Dict[str, Any] ) -> Optional[Dict[int, str]]: """Fills missing files by blank frame.""" + + blank_frame_path = None + + added_files = {} + + col_format = collection.format("{head}{padding}{tail}") + for frame in range(start_frame, end_frame + 1): + if frame in collection.indexes: + continue + hole_fpath = os.path.join(staging_dir, col_format % frame) + if blank_frame_path is None: + blank_frame_path = self._create_blank_frame( + staging_dir, extension, resolution_width, resolution_height + ) + temp_data["paths_to_remove"].append(blank_frame_path) + speedcopy.copyfile(blank_frame_path, hole_fpath) + added_files[frame] = hole_fpath + + return added_files + + def _create_blank_frame( + self, + staging_dir, + extension, + resolution_width, + resolution_height + ): blank_frame_path = os.path.join(staging_dir, f"blank.{extension}") - temp_data["paths_to_remove"].append(blank_frame_path) + command = get_ffmpeg_tool_args( "ffmpeg", "-f", "lavfi", @@ -1090,17 +1117,7 @@ class ExtractReview(pyblish.api.InstancePlugin): ) self.log.debug("Output: {}".format(output)) - added_files = {} - - col_format = collection.format("{head}{padding}{tail}") - for frame in range(start_frame, end_frame + 1): - if frame in collection.indexes: - continue - hole_fpath = os.path.join(staging_dir, col_format % frame) - speedcopy.copyfile(blank_frame_path, hole_fpath) - added_files[frame] = hole_fpath - - return added_files + return blank_frame_path def fill_sequence_gaps_from_existing( self, From 93b59710b25303d105960e3ef370086f167c1b02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Thu, 8 May 2025 17:27:52 +0200 Subject: [PATCH 099/108] Apply suggestions from code review Co-authored-by: Robin De Lillo Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../publish/collect_explicit_resolution.py | 18 +++++++++--------- server/settings/publish_plugins.py | 16 ++++++++-------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/client/ayon_core/plugins/publish/collect_explicit_resolution.py b/client/ayon_core/plugins/publish/collect_explicit_resolution.py index 1aa7147627..1a388d5487 100644 --- a/client/ayon_core/plugins/publish/collect_explicit_resolution.py +++ b/client/ayon_core/plugins/publish/collect_explicit_resolution.py @@ -50,21 +50,21 @@ class CollectExplicitResolution( dict: dictionary with width, height and pixel_aspect """ resolution_items = self._get_resolution_items() - item_values = None - # check if resolution_value is in cached items - if resolution_value in resolution_items: - item_values = resolution_items[resolution_value] + # ensure resolution_value is part of expected items + item_values = resolution_items.get(resolution_value) + # if the item is in the cache, get the values from it if item_values: - # if the item is in the cache, get the values from it return { "resolutionWidth": item_values["width"], "resolutionHeight": item_values["height"], "pixelAspect": item_values["pixel_aspect"], } - else: - raise PublishError( - f"Invalid resolution value: {resolution_value}") + + raise PublishError( + f"Invalid resolution value: {resolution_value} " + f"expected choices: {resolution_items}" + ) @classmethod def _get_resolution_items(cls): @@ -72,7 +72,7 @@ class CollectExplicitResolution( resolution_items = {} for item in cls.options: item_text = ( - f"{item['width']}x{item['height']}x{item['pixel_aspect']}") + f"{item['width']}x{item['height']} ({item['pixel_aspect']})") resolution_items[item_text] = item cls.resolution_items = resolution_items diff --git a/server/settings/publish_plugins.py b/server/settings/publish_plugins.py index 7ad6c9c506..cce312c2f8 100644 --- a/server/settings/publish_plugins.py +++ b/server/settings/publish_plugins.py @@ -191,21 +191,21 @@ class ResolutionOptionsModel(BaseSettingsModel): def ensure_unique_resolution_option( - objects: Iterable[Any], field_name: str | None = None) -> None: # noqa: C901 + objects: list[Any], field_name: str | None = None) -> None: # noqa: C901 """Ensure a list of objects have unique option attributes. This function checks if the list of objects has unique 'width', 'height' and 'pixel_aspect' properties. """ - options = [] + options = set() for obj in objects: item_test_text = f"{obj.width}x{obj.height}x{obj.pixel_aspect}" - if item_test_text not in options: - options.append(item_test_text) - else: + if item_test_text in options: raise BadRequestException( f"Duplicate option '{item_test_text}'") + options.add(item_test_text) + class CollectExplicitResolutionModel(BaseSettingsModel): enabled: bool = SettingsField(True, title="Enabled") @@ -218,14 +218,14 @@ class CollectExplicitResolutionModel(BaseSettingsModel): ) options: list[ResolutionOptionsModel] = SettingsField( default_factory=list, - title="Resolution options", + title="Resolution choices", description=( - "Options to be provided in publisher attribute" + "Available resolution choices to be displayed in the publishers attribute." ) ) @validator("options") - def validate_unique_options(cls, value): + def validate_unique_resolution_options(cls, value): ensure_unique_resolution_option(value) return value From 753960ca9ace6aafe067e85a0e7384f868a999a6 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 8 May 2025 17:33:08 +0200 Subject: [PATCH 100/108] Refactors explicit resolution collection Changes the collector order to ensure correct execution. Renames the "Override Resolution" label to "Force product resolution" for clarity. Removes default resolution values from server settings. The explicit resolution is intended to be defined on the instance level, not as a global default. --- .../plugins/publish/collect_explicit_resolution.py | 4 ++-- server/settings/publish_plugins.py | 8 +------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/client/ayon_core/plugins/publish/collect_explicit_resolution.py b/client/ayon_core/plugins/publish/collect_explicit_resolution.py index 1a388d5487..7d70271846 100644 --- a/client/ayon_core/plugins/publish/collect_explicit_resolution.py +++ b/client/ayon_core/plugins/publish/collect_explicit_resolution.py @@ -11,7 +11,7 @@ class CollectExplicitResolution( """Collect explicit user defined resolution attributes for instances""" label = "Choose Explicit Resolution" - order = pyblish.api.CollectorOrder + 0.49 + order = pyblish.api.CollectorOrder - 0.091 settings_category = "core" enabled = False @@ -99,6 +99,6 @@ class CollectExplicitResolution( "explicit_resolution", items, default="Don't override", - label="Override Resolution", + label="Force product resolution", ), ] diff --git a/server/settings/publish_plugins.py b/server/settings/publish_plugins.py index cce312c2f8..4b75fb46b6 100644 --- a/server/settings/publish_plugins.py +++ b/server/settings/publish_plugins.py @@ -1244,13 +1244,7 @@ DEFAULT_PUBLISH_VALUES = { "product_types": [ "shot" ], - "options": [ - { - "width": 2048, - "height": 1080, - "aspect_ratio": 1.5, - } - ] + "options": [] }, "ValidateEditorialAssetName": { "enabled": True, From 824dc0cc81d30618afc0afead8c584c99be8f53a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 9 May 2025 10:50:20 +0200 Subject: [PATCH 101/108] Improves readability of resolution display Updates the format of resolution items displayed in the publisher's attribute for better readability. Removes an unused import from server settings. --- .../ayon_core/plugins/publish/collect_explicit_resolution.py | 4 +++- server/settings/publish_plugins.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/plugins/publish/collect_explicit_resolution.py b/client/ayon_core/plugins/publish/collect_explicit_resolution.py index 7d70271846..3ea3d42102 100644 --- a/client/ayon_core/plugins/publish/collect_explicit_resolution.py +++ b/client/ayon_core/plugins/publish/collect_explicit_resolution.py @@ -72,7 +72,9 @@ class CollectExplicitResolution( resolution_items = {} for item in cls.options: item_text = ( - f"{item['width']}x{item['height']} ({item['pixel_aspect']})") + f"{item['width']}x{item['height']} " + f"({item['pixel_aspect']})" + ) resolution_items[item_text] = item cls.resolution_items = resolution_items diff --git a/server/settings/publish_plugins.py b/server/settings/publish_plugins.py index 56c8a929b1..0d8489d8ff 100644 --- a/server/settings/publish_plugins.py +++ b/server/settings/publish_plugins.py @@ -1,4 +1,3 @@ -from collections.abc import Iterable from pydantic import validator from typing import Any @@ -221,7 +220,8 @@ class CollectExplicitResolutionModel(BaseSettingsModel): default_factory=list, title="Resolution choices", description=( - "Available resolution choices to be displayed in the publishers attribute." + "Available resolution choices to be displayed in " + "the publishers attribute." ) ) From 9bf848f1a81d5f6d0ec041f5c97f7df017ddd7e2 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 9 May 2025 11:30:24 +0200 Subject: [PATCH 102/108] Create explicit_frames.txt as temp file --- client/ayon_core/plugins/publish/extract_review.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index de1c785475..87208f5574 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -6,6 +6,7 @@ import shutil import subprocess from abc import ABC, abstractmethod from typing import Dict, Any, Optional +import tempfile import clique import speedcopy @@ -815,10 +816,13 @@ class ExtractReview(pyblish.api.InstancePlugin): "-i", path_to_subprocess_arg(temp_data["full_input_path"]) ]) else: - staging_dir = os.path.dirname(temp_data["full_input_path"]) - explicit_frames_path = os.path.join( - staging_dir, "explicit_frames.txt") frame_duration = 1 / temp_data["fps"] + + explicit_frames_meta = tempfile.NamedTemporaryFile( + mode="w", prefix="explicit_frames", suffix=".txt", delete=False + ) + explicit_frames_meta.close() + explicit_frames_path = explicit_frames_meta.name with open(explicit_frames_path, "w") as fp: lines = [ f"file '{path}'{os.linesep}duration {frame_duration}" From 92aa7e1ccb89103da0e7addfa5405b77fd4258b1 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 9 May 2025 11:53:15 +0200 Subject: [PATCH 103/108] Updates ayon_ocio addon version Updates the minimum compatible version of the 'ayon_ocio' addon. This ensures compatibility with the latest features and fixes in the addon. --- package.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.py b/package.py index 0dad0b9792..601d703857 100644 --- a/package.py +++ b/package.py @@ -10,7 +10,7 @@ ayon_server_version = ">=1.7.6,<2.0.0" ayon_launcher_version = ">=1.0.2" ayon_required_addons = {} ayon_compatible_addons = { - "ayon_ocio": ">=1.2.0", + "ayon_ocio": ">=1.2.1", "harmony": ">0.4.0", "fusion": ">=0.3.3", "openrv": ">=1.0.2", From e4c5b0d0a53d2382199f4cbc92ed3785a8bb8a12 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 9 May 2025 11:59:52 +0200 Subject: [PATCH 104/108] Fixes typo in default OCIO config path Corrects a typo in the built-in OCIO config path. It appends the missing ".ocio" extension to the ACES 2.0 Studio config path, ensuring that the OCIO configuration is correctly recognized and loaded. --- server/settings/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/settings/main.py b/server/settings/main.py index a582763b4b..dd6af0a104 100644 --- a/server/settings/main.py +++ b/server/settings/main.py @@ -72,7 +72,7 @@ def _fallback_ocio_config_profile_types(): def _ocio_built_in_paths(): return [ { - "value": "{BUILTIN_OCIO_ROOT}/aces_2.0/studio-config-v3.0.0_aces-v2.0_ocio-v2.4", # noqa: E501 + "value": "{BUILTIN_OCIO_ROOT}/aces_2.0/studio-config-v3.0.0_aces-v2.0_ocio-v2.4.ocio", # noqa: E501 "label": "ACES 2.0 Studio (OCIO v2.4)", "description": ( "Aces 2.0 Studio OCIO config file. Requires OCIO v2.4.") From c7faefa99489fe67b3014ebed5b9f8d6e6a0092e Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 9 May 2025 14:22:00 +0200 Subject: [PATCH 105/108] reverse the replacements --- .../ayon_core/pipeline/create/product_name.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/client/ayon_core/pipeline/create/product_name.py b/client/ayon_core/pipeline/create/product_name.py index 0daec8a7ad..ecffa4a340 100644 --- a/client/ayon_core/pipeline/create/product_name.py +++ b/client/ayon_core/pipeline/create/product_name.py @@ -52,15 +52,15 @@ def get_product_name_template( # TODO remove formatting keys replacement template = ( matching_profile["template"] - .replace("{task[name]}", "{task}") - .replace("{Task[name]}", "{Task}") - .replace("{TASK[NAME]}", "{TASK}") - .replace("{product[type]}", "{family}") - .replace("{Product[type]}", "{Family}") - .replace("{PRODUCT[TYPE]}", "{FAMILY}") - .replace("{folder[name]}", "{asset}") - .replace("{Folder[name]}", "{Asset}") - .replace("{FOLDER[NAME]}", "{ASSET}") + .replace("{task}", "{task[name]}") + .replace("{Task}", "{Task[name]}") + .replace("{TASK}", "{TASK[NAME]}") + .replace("{family}", "{product[type]}") + .replace("{Family}", "{Product[type]}") + .replace("{FAMILY}", "{PRODUCT[TYPE]}") + .replace("{asset}", "{folder[name]}") + .replace("{Asset}", "{Folder[name]}") + .replace("{ASSET}", "{FOLDER[NAME]}") ) # Make sure template is set (matching may have empty string) From 2ac35d6dd8021c892c6c664b59066d29a9a950ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Mon, 12 May 2025 14:57:42 +0200 Subject: [PATCH 106/108] Apply suggestions from code review Co-authored-by: Robin De Lillo --- client/ayon_core/plugins/publish/extract_otio_review.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_otio_review.py b/client/ayon_core/plugins/publish/extract_otio_review.py index 908d78ca0d..f217be551c 100644 --- a/client/ayon_core/plugins/publish/extract_otio_review.py +++ b/client/ayon_core/plugins/publish/extract_otio_review.py @@ -474,7 +474,6 @@ class ExtractOTIOReview( command.extend([ "-start_number", str(in_frame_start), - "-compression_level", "5", "-framerate", str(sequence_fps), "-i", input_path ]) @@ -513,7 +512,8 @@ class ExtractOTIOReview( if video or sequence: command.extend([ - "-vf", f"scale={self.to_width}:{self.to_height}:flags=lanczos" + "-vf", f"scale={self.to_width}:{self.to_height}:flags=lanczos", + "-compression_level", "5", ]) # add output attributes From ce40d020d9a0c51f86066401e267ad3961fed91f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 12 May 2025 15:38:14 +0200 Subject: [PATCH 107/108] Updates image format to png and adds scaling Updates the image format for review outputs to PNG, adds scaling and compression to the ffmpeg calls, and includes pixel format specification for better compatibility and quality. --- .../editorial/test_extract_otio_review.py | 82 +++++++++++-------- 1 file changed, 50 insertions(+), 32 deletions(-) diff --git a/tests/client/ayon_core/pipeline/editorial/test_extract_otio_review.py b/tests/client/ayon_core/pipeline/editorial/test_extract_otio_review.py index 45191a2c53..a46ea149d7 100644 --- a/tests/client/ayon_core/pipeline/editorial/test_extract_otio_review.py +++ b/tests/client/ayon_core/pipeline/editorial/test_extract_otio_review.py @@ -103,17 +103,18 @@ def test_image_sequence_with_embedded_tc_and_handles_out_of_range(): # 10 head black handles generated from gap (991-1000) "/path/to/ffmpeg -t 0.4166666666666667 -r 24.0 -f lavfi -i " "color=c=black:s=1280x720 -tune stillimage -start_number 991 " - "C:/result/output.%04d.jpg", + "-pix_fmt rgba C:/result/output.%04d.png", # 10 tail black handles generated from gap (1102-1111) "/path/to/ffmpeg -t 0.4166666666666667 -r 24.0 -f lavfi -i " "color=c=black:s=1280x720 -tune stillimage -start_number 1102 " - "C:/result/output.%04d.jpg", + "-pix_fmt rgba C:/result/output.%04d.png", # Report from source exr (1001-1101) with enforce framerate "/path/to/ffmpeg -start_number 1000 -framerate 24.0 -i " - f"C:\\exr_embedded_tc{os.sep}output.%04d.exr -start_number 1001 " - "C:/result/output.%04d.jpg" + f"C:\\exr_embedded_tc{os.sep}output.%04d.exr " + "-vf scale=1280:720:flags=lanczos -compression_level 5 " + "-start_number 1001 -pix_fmt rgba C:/result/output.%04d.png" ] assert calls == expected @@ -130,20 +131,22 @@ def test_image_sequence_and_handles_out_of_range(): expected = [ # 5 head black frames generated from gap (991-995) - "/path/to/ffmpeg -t 0.2 -r 25.0 -f lavfi -i color=c=black:s=1280x720" - " -tune stillimage -start_number 991 C:/result/output.%04d.jpg", + "/path/to/ffmpeg -t 0.2 -r 25.0 -f lavfi -i color=c=black:s=1280x720 " + "-tune stillimage -start_number 991 -pix_fmt rgba " + "C:/result/output.%04d.png", # 9 tail back frames generated from gap (1097-1105) - "/path/to/ffmpeg -t 0.36 -r 25.0 -f lavfi -i color=c=black:s=1280x720" - " -tune stillimage -start_number 1097 C:/result/output.%04d.jpg", + "/path/to/ffmpeg -t 0.36 -r 25.0 -f lavfi -i color=c=black:s=1280x720 " + "-tune stillimage -start_number 1097 -pix_fmt rgba C:/result/output.%04d.png", # Report from source tiff (996-1096) # 996-1000 = additional 5 head frames # 1001-1095 = source range conformed to 25fps # 1096-1096 = additional 1 tail frames "/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i " - f"C:\\tif_seq{os.sep}output.%04d.tif -start_number 996" - f" C:/result/output.%04d.jpg" + f"C:\\tif_seq{os.sep}output.%04d.tif " + "-vf scale=1280:720:flags=lanczos -compression_level 5 -start_number 996 " + "-pix_fmt rgba C:/result/output.%04d.png" ] assert calls == expected @@ -164,7 +167,7 @@ def test_movie_with_embedded_tc_no_gap_handles(): # - duration = 68fr (source) + 20fr (handles) = 88frames = 3.666s "/path/to/ffmpeg -ss 0.16666666666666666 -t 3.6666666666666665 " "-i C:\\data\\qt_embedded_tc.mov -start_number 991 " - "C:/result/output.%04d.jpg" + "-pix_fmt rgba C:/result/output.%04d.png" ] assert calls == expected @@ -181,12 +184,12 @@ def test_short_movie_head_gap_handles(): expected = [ # 10 head black frames generated from gap (991-1000) "/path/to/ffmpeg -t 0.4 -r 25.0 -f lavfi -i color=c=black:s=1280x720" - " -tune stillimage -start_number 991 C:/result/output.%04d.jpg", + " -tune stillimage -start_number 991 -pix_fmt rgba C:/result/output.%04d.png", # source range + 10 tail frames # duration = 50fr (source) + 10fr (tail handle) = 60 fr = 2.4s "/path/to/ffmpeg -ss 0.0 -t 2.4 -i C:\\data\\movie.mp4" - " -start_number 1001 C:/result/output.%04d.jpg" + " -start_number 1001 -pix_fmt rgba C:/result/output.%04d.png" ] assert calls == expected @@ -204,13 +207,13 @@ def test_short_movie_tail_gap_handles(): # 10 tail black frames generated from gap (1067-1076) "/path/to/ffmpeg -t 0.4166666666666667 -r 24.0 -f lavfi -i " "color=c=black:s=1280x720 -tune stillimage -start_number 1067 " - "C:/result/output.%04d.jpg", + "-pix_fmt rgba C:/result/output.%04d.png", # 10 head frames + source range # duration = 10fr (head handle) + 66fr (source) = 76fr = 3.16s "/path/to/ffmpeg -ss 1.0416666666666667 -t 3.1666666666666665 -i " "C:\\data\\qt_no_tc_24fps.mov -start_number 991" - " C:/result/output.%04d.jpg" + " -pix_fmt rgba C:/result/output.%04d.png" ] assert calls == expected @@ -239,62 +242,75 @@ def test_multiple_review_clips_no_gap(): # 10 head black frames generated from gap (991-1000) '/path/to/ffmpeg -t 0.4 -r 25.0 -f lavfi' ' -i color=c=black:s=1280x720 -tune ' - 'stillimage -start_number 991 C:/result/output.%04d.jpg', + 'stillimage -start_number 991 -pix_fmt rgba C:/result/output.%04d.png', # Alternance 25fps tiff sequence and 24fps exr sequence # for 100 frames each '/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i ' f'C:\\no_tc{os.sep}output.%04d.tif ' - '-start_number 1001 C:/result/output.%04d.jpg', + '-vf scale=1280:720:flags=lanczos -compression_level 5 ' + '-start_number 1001 -pix_fmt rgba C:/result/output.%04d.png', '/path/to/ffmpeg -start_number 1000 -framerate 24.0 -i ' f'C:\\with_tc{os.sep}output.%04d.exr ' - '-start_number 1102 C:/result/output.%04d.jpg', + '-vf scale=1280:720:flags=lanczos -compression_level 5 ' + '-start_number 1102 -pix_fmt rgba C:/result/output.%04d.png', '/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i ' f'C:\\no_tc{os.sep}output.%04d.tif ' - '-start_number 1198 C:/result/output.%04d.jpg', + '-vf scale=1280:720:flags=lanczos -compression_level 5 ' + '-start_number 1198 -pix_fmt rgba C:/result/output.%04d.png', '/path/to/ffmpeg -start_number 1000 -framerate 24.0 -i ' f'C:\\with_tc{os.sep}output.%04d.exr ' - '-start_number 1299 C:/result/output.%04d.jpg', + '-vf scale=1280:720:flags=lanczos -compression_level 5 ' + '-start_number 1299 -pix_fmt rgba C:/result/output.%04d.png', # Repeated 25fps tiff sequence multiple times till the end '/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i ' f'C:\\no_tc{os.sep}output.%04d.tif ' - '-start_number 1395 C:/result/output.%04d.jpg', + '-vf scale=1280:720:flags=lanczos -compression_level 5 ' + '-start_number 1395 -pix_fmt rgba C:/result/output.%04d.png', '/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i ' f'C:\\no_tc{os.sep}output.%04d.tif ' - '-start_number 1496 C:/result/output.%04d.jpg', + '-vf scale=1280:720:flags=lanczos -compression_level 5 ' + '-start_number 1496 -pix_fmt rgba C:/result/output.%04d.png', '/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i ' f'C:\\no_tc{os.sep}output.%04d.tif ' - '-start_number 1597 C:/result/output.%04d.jpg', + '-vf scale=1280:720:flags=lanczos -compression_level 5 ' + '-start_number 1597 -pix_fmt rgba C:/result/output.%04d.png', '/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i ' f'C:\\no_tc{os.sep}output.%04d.tif ' - '-start_number 1698 C:/result/output.%04d.jpg', + '-vf scale=1280:720:flags=lanczos -compression_level 5 ' + '-start_number 1698 -pix_fmt rgba C:/result/output.%04d.png', '/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i ' f'C:\\no_tc{os.sep}output.%04d.tif ' - '-start_number 1799 C:/result/output.%04d.jpg', + '-vf scale=1280:720:flags=lanczos -compression_level 5 ' + '-start_number 1799 -pix_fmt rgba C:/result/output.%04d.png', '/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i ' f'C:\\no_tc{os.sep}output.%04d.tif ' - '-start_number 1900 C:/result/output.%04d.jpg', + '-vf scale=1280:720:flags=lanczos -compression_level 5 ' + '-start_number 1900 -pix_fmt rgba C:/result/output.%04d.png', '/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i ' f'C:\\no_tc{os.sep}output.%04d.tif ' - '-start_number 2001 C:/result/output.%04d.jpg', + '-vf scale=1280:720:flags=lanczos -compression_level 5 ' + '-start_number 2001 -pix_fmt rgba C:/result/output.%04d.png', '/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i ' f'C:\\no_tc{os.sep}output.%04d.tif ' - '-start_number 2102 C:/result/output.%04d.jpg', + '-vf scale=1280:720:flags=lanczos -compression_level 5 ' + '-start_number 2102 -pix_fmt rgba C:/result/output.%04d.png', '/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i ' f'C:\\no_tc{os.sep}output.%04d.tif ' - '-start_number 2203 C:/result/output.%04d.jpg' + '-vf scale=1280:720:flags=lanczos -compression_level 5 ' + '-start_number 2203 -pix_fmt rgba C:/result/output.%04d.png' ] assert calls == expected @@ -323,15 +339,17 @@ def test_multiple_review_clips_with_gap(): # Gap on review track (12 frames) '/path/to/ffmpeg -t 0.5 -r 24.0 -f lavfi' ' -i color=c=black:s=1280x720 -tune ' - 'stillimage -start_number 991 C:/result/output.%04d.jpg', + 'stillimage -start_number 991 -pix_fmt rgba C:/result/output.%04d.png', '/path/to/ffmpeg -start_number 1000 -framerate 24.0 -i ' f'C:\\with_tc{os.sep}output.%04d.exr ' - '-start_number 1003 C:/result/output.%04d.jpg', + '-vf scale=1280:720:flags=lanczos -compression_level 5 ' + '-start_number 1003 -pix_fmt rgba C:/result/output.%04d.png', '/path/to/ffmpeg -start_number 1000 -framerate 24.0 -i ' f'C:\\with_tc{os.sep}output.%04d.exr ' - '-start_number 1091 C:/result/output.%04d.jpg' + '-vf scale=1280:720:flags=lanczos -compression_level 5 ' + '-start_number 1091 -pix_fmt rgba C:/result/output.%04d.png' ] assert calls == expected From 9137d1c0bb7336a0d849f5488d30531aa16372b7 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 12 May 2025 15:50:06 +0200 Subject: [PATCH 108/108] Adds scaling and compression to ffmpeg calls Updates the ffmpeg calls within the editorial extraction tests to include scaling and compression parameters. This ensures consistent image quality and size across different source media. --- .../editorial/test_extract_otio_review.py | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/tests/client/ayon_core/pipeline/editorial/test_extract_otio_review.py b/tests/client/ayon_core/pipeline/editorial/test_extract_otio_review.py index a46ea149d7..6a74df7f43 100644 --- a/tests/client/ayon_core/pipeline/editorial/test_extract_otio_review.py +++ b/tests/client/ayon_core/pipeline/editorial/test_extract_otio_review.py @@ -137,7 +137,8 @@ def test_image_sequence_and_handles_out_of_range(): # 9 tail back frames generated from gap (1097-1105) "/path/to/ffmpeg -t 0.36 -r 25.0 -f lavfi -i color=c=black:s=1280x720 " - "-tune stillimage -start_number 1097 -pix_fmt rgba C:/result/output.%04d.png", + "-tune stillimage -start_number 1097 -pix_fmt rgba " + "C:/result/output.%04d.png", # Report from source tiff (996-1096) # 996-1000 = additional 5 head frames @@ -145,8 +146,8 @@ def test_image_sequence_and_handles_out_of_range(): # 1096-1096 = additional 1 tail frames "/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i " f"C:\\tif_seq{os.sep}output.%04d.tif " - "-vf scale=1280:720:flags=lanczos -compression_level 5 -start_number 996 " - "-pix_fmt rgba C:/result/output.%04d.png" + "-vf scale=1280:720:flags=lanczos -compression_level 5 " + "-start_number 996 -pix_fmt rgba C:/result/output.%04d.png" ] assert calls == expected @@ -166,8 +167,9 @@ def test_movie_with_embedded_tc_no_gap_handles(): # - first_frame = 14 src - 10 (head tail) = frame 4 = 0.1666s # - duration = 68fr (source) + 20fr (handles) = 88frames = 3.666s "/path/to/ffmpeg -ss 0.16666666666666666 -t 3.6666666666666665 " - "-i C:\\data\\qt_embedded_tc.mov -start_number 991 " - "-pix_fmt rgba C:/result/output.%04d.png" + "-i C:\\data\\qt_embedded_tc.mov -vf scale=1280:720:flags=lanczos " + "-compression_level 5 -start_number 991 -pix_fmt rgba " + "C:/result/output.%04d.png" ] assert calls == expected @@ -184,12 +186,14 @@ def test_short_movie_head_gap_handles(): expected = [ # 10 head black frames generated from gap (991-1000) "/path/to/ffmpeg -t 0.4 -r 25.0 -f lavfi -i color=c=black:s=1280x720" - " -tune stillimage -start_number 991 -pix_fmt rgba C:/result/output.%04d.png", + " -tune stillimage -start_number 991 -pix_fmt rgba " + "C:/result/output.%04d.png", # source range + 10 tail frames # duration = 50fr (source) + 10fr (tail handle) = 60 fr = 2.4s - "/path/to/ffmpeg -ss 0.0 -t 2.4 -i C:\\data\\movie.mp4" - " -start_number 1001 -pix_fmt rgba C:/result/output.%04d.png" + "/path/to/ffmpeg -ss 0.0 -t 2.4 -i C:\\data\\movie.mp4 -vf " + "scale=1280:720:flags=lanczos -compression_level 5 " + "-start_number 1001 -pix_fmt rgba C:/result/output.%04d.png" ] assert calls == expected @@ -212,8 +216,9 @@ def test_short_movie_tail_gap_handles(): # 10 head frames + source range # duration = 10fr (head handle) + 66fr (source) = 76fr = 3.16s "/path/to/ffmpeg -ss 1.0416666666666667 -t 3.1666666666666665 -i " - "C:\\data\\qt_no_tc_24fps.mov -start_number 991" - " -pix_fmt rgba C:/result/output.%04d.png" + "C:\\data\\qt_no_tc_24fps.mov -vf scale=1280:720:flags=lanczos " + "-compression_level 5 -start_number 991 -pix_fmt rgba " + "C:/result/output.%04d.png" ] assert calls == expected