From 95743f29a3e67baa6d39f1975fd001575c8abdfb Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 30 Jan 2025 14:35:23 +0100 Subject: [PATCH 001/153] 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/153] 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 2c102722627fad9949c950b70b47ddc7abf0bbf0 Mon Sep 17 00:00:00 2001 From: ChunYou Date: Tue, 4 Feb 2025 02:45:36 +0000 Subject: [PATCH 003/153] Make concurrent copying of files and change hero integration to use speedcopy on windows --- client/ayon_core/lib/file_transaction.py | 66 ++++++++++--------- .../plugins/publish/integrate_hero_version.py | 20 ++++-- 2 files changed, 50 insertions(+), 36 deletions(-) diff --git a/client/ayon_core/lib/file_transaction.py b/client/ayon_core/lib/file_transaction.py index a502403958..7c330eb9b2 100644 --- a/client/ayon_core/lib/file_transaction.py +++ b/client/ayon_core/lib/file_transaction.py @@ -11,6 +11,7 @@ if sys.platform == "win32": else: from shutil import copyfile +import concurrent.futures class DuplicateDestinationError(ValueError): """Error raised when transfer destination already exists in queue. @@ -109,41 +110,46 @@ class FileTransaction: self._transfers[dst] = (src, opts) def process(self): - # Backup any existing files - for dst, (src, _) in self._transfers.items(): - self.log.debug("Checking file ... {} -> {}".format(src, dst)) - path_same = self._same_paths(src, dst) - if path_same or not os.path.exists(dst): - continue + with concurrent.futures.ThreadPoolExecutor() as executor: + backup_futures = [] + for dst, (src, _) in self._transfers.items(): + backup_futures.append(executor.submit(self.backup_file, dst, src)) + concurrent.futures.wait(backup_futures) - # Backup original file - # todo: add timestamp or uuid to ensure unique - backup = dst + ".bak" - self._backup_to_original[backup] = dst + transfer_futures = [] + for dst, (src, opts) in self._transfers.items(): + transfer_futures.append(executor.submit(self.transfer_file, dst, src, opts)) + concurrent.futures.wait(transfer_futures) + + def backup_file(self, dst, src): + self.log.debug("Checking file ... {} -> {}".format(src, dst)) + path_same = self._same_paths(src, dst) + if path_same or not os.path.exists(dst): + return + + # Backup original file + backup = dst + ".bak" + self._backup_to_original[backup] = dst + self.log.debug("Backup existing file: {} -> {}".format(dst, backup)) + os.rename(dst, backup) + + def transfer_file(self, dst, src, opts): + path_same = self._same_paths(src, dst) + if path_same: self.log.debug( - "Backup existing file: {} -> {}".format(dst, backup)) - os.rename(dst, backup) + "Source and destination are same files {} -> {}".format(src, dst)) + return - # Copy the files to transfer - for dst, (src, opts) in self._transfers.items(): - path_same = self._same_paths(src, dst) - if path_same: - self.log.debug( - "Source and destination are same files {} -> {}".format( - src, dst)) - continue + self._create_folder_for_file(dst) - self._create_folder_for_file(dst) + if opts["mode"] == self.MODE_COPY: + self.log.debug("Copying file ... {} -> {}".format(src, dst)) + copyfile(src, dst) + elif opts["mode"] == self.MODE_HARDLINK: + self.log.debug("Hardlinking file ... {} -> {}".format(src, dst)) + create_hard_link(src, dst) - if opts["mode"] == self.MODE_COPY: - self.log.debug("Copying file ... {} -> {}".format(src, dst)) - copyfile(src, dst) - elif opts["mode"] == self.MODE_HARDLINK: - self.log.debug("Hardlinking file ... {} -> {}".format( - src, dst)) - create_hard_link(src, dst) - - self._transferred.append(dst) + self._transferred.append(dst) def finalize(self): # Delete any backed up files diff --git a/client/ayon_core/plugins/publish/integrate_hero_version.py b/client/ayon_core/plugins/publish/integrate_hero_version.py index 2163596864..92becb6e01 100644 --- a/client/ayon_core/plugins/publish/integrate_hero_version.py +++ b/client/ayon_core/plugins/publish/integrate_hero_version.py @@ -2,6 +2,14 @@ import os import copy import errno import shutil +import sys +# this is needed until speedcopy for linux is fixed +if sys.platform == "win32": + from speedcopy import copyfile +else: + from shutil import copyfile + +import concurrent.futures import clique import pyblish.api @@ -415,11 +423,11 @@ class IntegrateHeroVersion( # Copy(hardlink) paths of source and destination files # TODO should we *only* create hardlinks? # TODO should we keep files for deletion until this is successful? - for src_path, dst_path in src_to_dst_file_paths: - self.copy_file(src_path, dst_path) - - for src_path, dst_path in other_file_paths_mapping: - self.copy_file(src_path, dst_path) + with concurrent.futures.ThreadPoolExecutor() as executor: + file_futures = [] + for src_path, dst_path in src_to_dst_file_paths + other_file_paths_mapping: + file_futures.append(executor.submit(self.copy_file, src_path, dst_path)) + concurrent.futures.wait(file_futures) # Update prepared representation etity data with files # and integrate it to server. @@ -648,7 +656,7 @@ class IntegrateHeroVersion( src_path, dst_path )) - shutil.copy(src_path, dst_path) + copyfile(src_path, dst_path) def version_from_representations(self, project_name, repres): for repre in repres: From 4a8c79285dce2d1e497aa30301ff430882255296 Mon Sep 17 00:00:00 2001 From: r42-chun <73248638+r42-chun@users.noreply.github.com> Date: Tue, 4 Feb 2025 10:23:23 +0000 Subject: [PATCH 004/153] Update client/ayon_core/lib/file_transaction.py Co-authored-by: Roy Nieterau --- client/ayon_core/lib/file_transaction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/lib/file_transaction.py b/client/ayon_core/lib/file_transaction.py index 7c330eb9b2..08fcd66984 100644 --- a/client/ayon_core/lib/file_transaction.py +++ b/client/ayon_core/lib/file_transaction.py @@ -130,7 +130,7 @@ class FileTransaction: # Backup original file backup = dst + ".bak" self._backup_to_original[backup] = dst - self.log.debug("Backup existing file: {} -> {}".format(dst, backup)) + self.log.debug(f"Backup existing file: {dst} -> {backup}") os.rename(dst, backup) def transfer_file(self, dst, src, opts): From 4bca62dcfe041665051b1342be0fc8c0a9bd0081 Mon Sep 17 00:00:00 2001 From: r42-chun <73248638+r42-chun@users.noreply.github.com> Date: Tue, 4 Feb 2025 10:23:30 +0000 Subject: [PATCH 005/153] Update client/ayon_core/lib/file_transaction.py Co-authored-by: Roy Nieterau --- client/ayon_core/lib/file_transaction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/lib/file_transaction.py b/client/ayon_core/lib/file_transaction.py index 08fcd66984..138861760f 100644 --- a/client/ayon_core/lib/file_transaction.py +++ b/client/ayon_core/lib/file_transaction.py @@ -143,7 +143,7 @@ class FileTransaction: self._create_folder_for_file(dst) if opts["mode"] == self.MODE_COPY: - self.log.debug("Copying file ... {} -> {}".format(src, dst)) + self.log.debug(f"Copying file ... {src} -> {dst}") copyfile(src, dst) elif opts["mode"] == self.MODE_HARDLINK: self.log.debug("Hardlinking file ... {} -> {}".format(src, dst)) From 8dae70ab590789d03f3b5afe482a79cd0c1449a4 Mon Sep 17 00:00:00 2001 From: r42-chun <73248638+r42-chun@users.noreply.github.com> Date: Tue, 4 Feb 2025 10:23:35 +0000 Subject: [PATCH 006/153] Update client/ayon_core/lib/file_transaction.py Co-authored-by: Roy Nieterau --- client/ayon_core/lib/file_transaction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/lib/file_transaction.py b/client/ayon_core/lib/file_transaction.py index 138861760f..8f119ed2ae 100644 --- a/client/ayon_core/lib/file_transaction.py +++ b/client/ayon_core/lib/file_transaction.py @@ -146,7 +146,7 @@ class FileTransaction: self.log.debug(f"Copying file ... {src} -> {dst}") copyfile(src, dst) elif opts["mode"] == self.MODE_HARDLINK: - self.log.debug("Hardlinking file ... {} -> {}".format(src, dst)) + self.log.debug(f"Hardlinking file ... {src} -> {dst}") create_hard_link(src, dst) self._transferred.append(dst) From b2e84b2b0a6f397b402084d95fa70b607bf39d0e Mon Sep 17 00:00:00 2001 From: r42-chun <73248638+r42-chun@users.noreply.github.com> Date: Tue, 4 Feb 2025 10:23:43 +0000 Subject: [PATCH 007/153] Update client/ayon_core/lib/file_transaction.py Co-authored-by: Roy Nieterau --- client/ayon_core/lib/file_transaction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/lib/file_transaction.py b/client/ayon_core/lib/file_transaction.py index 8f119ed2ae..e19f095920 100644 --- a/client/ayon_core/lib/file_transaction.py +++ b/client/ayon_core/lib/file_transaction.py @@ -137,7 +137,7 @@ class FileTransaction: path_same = self._same_paths(src, dst) if path_same: self.log.debug( - "Source and destination are same files {} -> {}".format(src, dst)) + f"Source and destination are same files {src} -> {dst}") return self._create_folder_for_file(dst) From 724e206900c57ec90daed0171571f9a52566d458 Mon Sep 17 00:00:00 2001 From: r42-chun <73248638+r42-chun@users.noreply.github.com> Date: Tue, 4 Feb 2025 10:24:08 +0000 Subject: [PATCH 008/153] Update client/ayon_core/lib/file_transaction.py Co-authored-by: Roy Nieterau --- client/ayon_core/lib/file_transaction.py | 1 - 1 file changed, 1 deletion(-) diff --git a/client/ayon_core/lib/file_transaction.py b/client/ayon_core/lib/file_transaction.py index e19f095920..2d1c7726eb 100644 --- a/client/ayon_core/lib/file_transaction.py +++ b/client/ayon_core/lib/file_transaction.py @@ -11,7 +11,6 @@ if sys.platform == "win32": else: from shutil import copyfile -import concurrent.futures class DuplicateDestinationError(ValueError): """Error raised when transfer destination already exists in queue. From 8721824bd9d889b97eb91ff60668c6bd64638072 Mon Sep 17 00:00:00 2001 From: r42-chun <73248638+r42-chun@users.noreply.github.com> Date: Tue, 4 Feb 2025 10:24:14 +0000 Subject: [PATCH 009/153] Update client/ayon_core/lib/file_transaction.py Co-authored-by: Roy Nieterau --- client/ayon_core/lib/file_transaction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/lib/file_transaction.py b/client/ayon_core/lib/file_transaction.py index 2d1c7726eb..5c55f56b7d 100644 --- a/client/ayon_core/lib/file_transaction.py +++ b/client/ayon_core/lib/file_transaction.py @@ -121,7 +121,7 @@ class FileTransaction: concurrent.futures.wait(transfer_futures) def backup_file(self, dst, src): - self.log.debug("Checking file ... {} -> {}".format(src, dst)) + self.log.debug(f"Checking file ... {src} -> {dst}") path_same = self._same_paths(src, dst) if path_same or not os.path.exists(dst): return From 236f72832cb313c959678acc05090a006b188fde Mon Sep 17 00:00:00 2001 From: ChunYou Date: Tue, 4 Feb 2025 13:19:00 +0000 Subject: [PATCH 010/153] Add underscores to bacup and transfer file functions --- client/ayon_core/lib/file_transaction.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/lib/file_transaction.py b/client/ayon_core/lib/file_transaction.py index 5c55f56b7d..f271f3081c 100644 --- a/client/ayon_core/lib/file_transaction.py +++ b/client/ayon_core/lib/file_transaction.py @@ -112,15 +112,15 @@ class FileTransaction: with concurrent.futures.ThreadPoolExecutor() as executor: backup_futures = [] for dst, (src, _) in self._transfers.items(): - backup_futures.append(executor.submit(self.backup_file, dst, src)) + backup_futures.append(executor.submit(self._backup_file, dst, src)) concurrent.futures.wait(backup_futures) transfer_futures = [] for dst, (src, opts) in self._transfers.items(): - transfer_futures.append(executor.submit(self.transfer_file, dst, src, opts)) + transfer_futures.append(executor.submit(self._transfer_file, dst, src, opts)) concurrent.futures.wait(transfer_futures) - def backup_file(self, dst, src): + def _backup_file(self, dst, src): self.log.debug(f"Checking file ... {src} -> {dst}") path_same = self._same_paths(src, dst) if path_same or not os.path.exists(dst): @@ -132,7 +132,7 @@ class FileTransaction: self.log.debug(f"Backup existing file: {dst} -> {backup}") os.rename(dst, backup) - def transfer_file(self, dst, src, opts): + def _transfer_file(self, dst, src, opts): path_same = self._same_paths(src, dst) if path_same: self.log.debug( From 284ad5316be44940857f9c27c3357eae085b89a7 Mon Sep 17 00:00:00 2001 From: ChunYou Date: Tue, 4 Feb 2025 13:59:06 +0000 Subject: [PATCH 011/153] Added some error handling --- client/ayon_core/lib/file_transaction.py | 35 +++++++++++++++++------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/client/ayon_core/lib/file_transaction.py b/client/ayon_core/lib/file_transaction.py index f271f3081c..83a371967f 100644 --- a/client/ayon_core/lib/file_transaction.py +++ b/client/ayon_core/lib/file_transaction.py @@ -108,17 +108,32 @@ class FileTransaction: self._transfers[dst] = (src, opts) - def process(self): - with concurrent.futures.ThreadPoolExecutor() as executor: - backup_futures = [] - for dst, (src, _) in self._transfers.items(): - backup_futures.append(executor.submit(self._backup_file, dst, src)) - concurrent.futures.wait(backup_futures) - transfer_futures = [] - for dst, (src, opts) in self._transfers.items(): - transfer_futures.append(executor.submit(self._transfer_file, dst, src, opts)) - concurrent.futures.wait(transfer_futures) + def _process_futures(self, futures): + """Wait for futures and raise exceptions if any task fails.""" + try: + for future in concurrent.futures.as_completed(futures): + future.result() # If an exception occurs, it will be raised here + except Exception as e: + print(f"File Transaction task failed with error: {e}", file=sys.stderr) + raise + + def process(self): + try: + with concurrent.futures.ThreadPoolExecutor(max_workers=min(8, len(self._transfers))) as executor: + # Submit backup tasks + backup_futures = [executor.submit(self._backup_file, dst, src) for dst, (src, _) in + self._transfers.items()] + self._process_futures(backup_futures) + + # Submit transfer tasks + transfer_futures = [executor.submit(self._transfer_file, dst, src, opts) for dst, (src, opts) in + self._transfers.items()] + self._process_futures(transfer_futures) + + except Exception as e: + print(f"File Transaction Failed: {e}", file=sys.stderr) + sys.exit(1) def _backup_file(self, dst, src): self.log.debug(f"Checking file ... {src} -> {dst}") From 8c9c69efa78e99cc0fc6eceeff7a918736ab211e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 5 Feb 2025 02:35:35 +0100 Subject: [PATCH 012/153] Add validation for the ThreadPoolExecutor calls, raise on error --- client/ayon_core/lib/file_transaction.py | 45 ++++++++--------- client/ayon_core/lib/threadpool.py | 49 +++++++++++++++++++ .../plugins/publish/integrate_hero_version.py | 18 ++++--- 3 files changed, 81 insertions(+), 31 deletions(-) create mode 100644 client/ayon_core/lib/threadpool.py diff --git a/client/ayon_core/lib/file_transaction.py b/client/ayon_core/lib/file_transaction.py index 83a371967f..e82599b2fb 100644 --- a/client/ayon_core/lib/file_transaction.py +++ b/client/ayon_core/lib/file_transaction.py @@ -2,6 +2,7 @@ import os import logging import sys import errno +from concurrent.futures import ThreadPoolExecutor from ayon_core.lib import create_hard_link @@ -11,6 +12,11 @@ if sys.platform == "win32": else: from shutil import copyfile +from .threadpool import as_completed_stop_and_raise_on_error + + +log = logging.getLogger(__name__) + class DuplicateDestinationError(ValueError): """Error raised when transfer destination already exists in queue. @@ -108,32 +114,23 @@ class FileTransaction: self._transfers[dst] = (src, opts) - - def _process_futures(self, futures): - """Wait for futures and raise exceptions if any task fails.""" - try: - for future in concurrent.futures.as_completed(futures): - future.result() # If an exception occurs, it will be raised here - except Exception as e: - print(f"File Transaction task failed with error: {e}", file=sys.stderr) - raise - def process(self): - try: - with concurrent.futures.ThreadPoolExecutor(max_workers=min(8, len(self._transfers))) as executor: - # Submit backup tasks - backup_futures = [executor.submit(self._backup_file, dst, src) for dst, (src, _) in - self._transfers.items()] - self._process_futures(backup_futures) + with ThreadPoolExecutor(max_workers=8) as executor: + # Submit backup tasks + backup_futures = [ + executor.submit(self._backup_file, dst, src) + for dst, (src, _) in self._transfers.items() + ] + as_completed_stop_and_raise_on_error( + executor, backup_futures, logger=self.log) - # Submit transfer tasks - transfer_futures = [executor.submit(self._transfer_file, dst, src, opts) for dst, (src, opts) in - self._transfers.items()] - self._process_futures(transfer_futures) - - except Exception as e: - print(f"File Transaction Failed: {e}", file=sys.stderr) - sys.exit(1) + # Submit transfer tasks + transfer_futures = [ + executor.submit(self._transfer_file, dst, src, opts) + for dst, (src, opts) in self._transfers.items() + ] + as_completed_stop_and_raise_on_error( + executor, transfer_futures, logger=self.log) def _backup_file(self, dst, src): self.log.debug(f"Checking file ... {src} -> {dst}") diff --git a/client/ayon_core/lib/threadpool.py b/client/ayon_core/lib/threadpool.py new file mode 100644 index 0000000000..b1b2476342 --- /dev/null +++ b/client/ayon_core/lib/threadpool.py @@ -0,0 +1,49 @@ +import logging +import concurrent.futures +from concurrent.futures import ThreadPoolExecutor, Future +from typing import List, Optional + +log = logging.getLogger(__name__) + + +def as_completed_stop_and_raise_on_error( + executor: ThreadPoolExecutor, + futures: List[Future], + logger: Optional[logging.Logger] = None): + """For the ThreadPoolExecutor shutdown and cancel futures as soon one of + the workers raises an error as they complete. + + The ThreadPoolExecutor only cancels pending futures on exception but will + still complete those that are running - each which also themselves could + fail. We log all exceptions, but re-raise the last exception only. + """ + if logger is None: + logger = log + + for future in concurrent.futures.as_completed(futures): + exception = future.exception() + if exception: + # As soon as an error occurs, stop executing more futures. + # Running workers however, will still complete so we also want + # to log those errors if any occurred on them. + executor.shutdown(wait=True, cancel_futures=True) + break + else: + # Futures are completed, no exceptions occurred + return + + # An exception occurred in at least one future. Get exceptions from + # all futures that are done and ended up failing until that point. + exceptions = [] + for future in futures: + if not future.cancelled() and future.done(): + exception = future.exception() + if exception: + exceptions.append(exception) + + # Log any exceptions that occurred in all workers + for exception in exceptions: + logger.error("Error occurred in worker", exc_info=exception) + + # Raise the last exception + raise exceptions[-1] diff --git a/client/ayon_core/plugins/publish/integrate_hero_version.py b/client/ayon_core/plugins/publish/integrate_hero_version.py index 92becb6e01..78a2796d35 100644 --- a/client/ayon_core/plugins/publish/integrate_hero_version.py +++ b/client/ayon_core/plugins/publish/integrate_hero_version.py @@ -1,16 +1,16 @@ import os import copy import errno +import itertools import shutil import sys +from concurrent.futures import ThreadPoolExecutor # this is needed until speedcopy for linux is fixed if sys.platform == "win32": from speedcopy import copyfile else: from shutil import copyfile -import concurrent.futures - import clique import pyblish.api import ayon_api @@ -21,6 +21,7 @@ from ayon_api.operations import ( from ayon_api.utils import create_entity_id from ayon_core.lib import create_hard_link, source_hash +from ayon_core.lib.threadpool import as_completed_stop_and_raise_on_error from ayon_core.pipeline.publish import ( get_publish_template_name, OptionalPyblishPluginMixin, @@ -423,11 +424,14 @@ class IntegrateHeroVersion( # Copy(hardlink) paths of source and destination files # TODO should we *only* create hardlinks? # TODO should we keep files for deletion until this is successful? - with concurrent.futures.ThreadPoolExecutor() as executor: - file_futures = [] - for src_path, dst_path in src_to_dst_file_paths + other_file_paths_mapping: - file_futures.append(executor.submit(self.copy_file, src_path, dst_path)) - concurrent.futures.wait(file_futures) + with ThreadPoolExecutor(max_workers=8) as executor: + futures = [ + executor.submit(self.copy_file, src_path, dst_path) + for src_path, dst_path + in itertools.chain(src_to_dst_file_paths, + other_file_paths_mapping) + ] + as_completed_stop_and_raise_on_error(executor, futures) # Update prepared representation etity data with files # and integrate it to server. From 42760fbffe26523aa8dc45ae8ba616dba375bcfb Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 5 Feb 2025 02:39:11 +0100 Subject: [PATCH 013/153] Remove redundant log instance --- client/ayon_core/lib/file_transaction.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/client/ayon_core/lib/file_transaction.py b/client/ayon_core/lib/file_transaction.py index e82599b2fb..06ba07c148 100644 --- a/client/ayon_core/lib/file_transaction.py +++ b/client/ayon_core/lib/file_transaction.py @@ -15,9 +15,6 @@ else: from .threadpool import as_completed_stop_and_raise_on_error -log = logging.getLogger(__name__) - - class DuplicateDestinationError(ValueError): """Error raised when transfer destination already exists in queue. From 9c67bf1990d26fdcde5e3566c058aadfdda6bf8e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 5 Feb 2025 11:51:32 +0100 Subject: [PATCH 014/153] Move `as_completed_stop_and_raise_on_error` to `file_transaction.py` --- client/ayon_core/lib/file_transaction.py | 49 +++++++++++++++++-- client/ayon_core/lib/threadpool.py | 49 ------------------- .../plugins/publish/integrate_hero_version.py | 2 +- 3 files changed, 47 insertions(+), 53 deletions(-) delete mode 100644 client/ayon_core/lib/threadpool.py diff --git a/client/ayon_core/lib/file_transaction.py b/client/ayon_core/lib/file_transaction.py index 06ba07c148..bf206b535c 100644 --- a/client/ayon_core/lib/file_transaction.py +++ b/client/ayon_core/lib/file_transaction.py @@ -1,8 +1,10 @@ +import concurrent.futures import os import logging import sys import errno -from concurrent.futures import ThreadPoolExecutor +from concurrent.futures import ThreadPoolExecutor, Future +from typing import List, Optional from ayon_core.lib import create_hard_link @@ -12,8 +14,6 @@ if sys.platform == "win32": else: from shutil import copyfile -from .threadpool import as_completed_stop_and_raise_on_error - class DuplicateDestinationError(ValueError): """Error raised when transfer destination already exists in queue. @@ -226,3 +226,46 @@ class FileTransaction: return os.stat(src) == os.stat(dst) return src == dst + + +def as_completed_stop_and_raise_on_error( + executor: ThreadPoolExecutor, + futures: List[Future], + logger: Optional[logging.Logger] = None): + """For the ThreadPoolExecutor shutdown and cancel futures as soon one of + the workers raises an error as they complete. + + The ThreadPoolExecutor only cancels pending futures on exception but will + still complete those that are running - each which also themselves could + fail. We log all exceptions, but re-raise the last exception only. + """ + if logger is None: + logger = logging.getLogger(__name__) + + for future in concurrent.futures.as_completed(futures): + exception = future.exception() + if exception: + # As soon as an error occurs, stop executing more futures. + # Running workers however, will still complete so we also want + # to log those errors if any occurred on them. + executor.shutdown(wait=True, cancel_futures=True) + break + else: + # Futures are completed, no exceptions occurred + return + + # An exception occurred in at least one future. Get exceptions from + # all futures that are done and ended up failing until that point. + exceptions = [] + for future in futures: + if not future.cancelled() and future.done(): + exception = future.exception() + if exception: + exceptions.append(exception) + + # Log any exceptions that occurred in all workers + for exception in exceptions: + logger.error("Error occurred in worker", exc_info=exception) + + # Raise the last exception + raise exceptions[-1] diff --git a/client/ayon_core/lib/threadpool.py b/client/ayon_core/lib/threadpool.py deleted file mode 100644 index b1b2476342..0000000000 --- a/client/ayon_core/lib/threadpool.py +++ /dev/null @@ -1,49 +0,0 @@ -import logging -import concurrent.futures -from concurrent.futures import ThreadPoolExecutor, Future -from typing import List, Optional - -log = logging.getLogger(__name__) - - -def as_completed_stop_and_raise_on_error( - executor: ThreadPoolExecutor, - futures: List[Future], - logger: Optional[logging.Logger] = None): - """For the ThreadPoolExecutor shutdown and cancel futures as soon one of - the workers raises an error as they complete. - - The ThreadPoolExecutor only cancels pending futures on exception but will - still complete those that are running - each which also themselves could - fail. We log all exceptions, but re-raise the last exception only. - """ - if logger is None: - logger = log - - for future in concurrent.futures.as_completed(futures): - exception = future.exception() - if exception: - # As soon as an error occurs, stop executing more futures. - # Running workers however, will still complete so we also want - # to log those errors if any occurred on them. - executor.shutdown(wait=True, cancel_futures=True) - break - else: - # Futures are completed, no exceptions occurred - return - - # An exception occurred in at least one future. Get exceptions from - # all futures that are done and ended up failing until that point. - exceptions = [] - for future in futures: - if not future.cancelled() and future.done(): - exception = future.exception() - if exception: - exceptions.append(exception) - - # Log any exceptions that occurred in all workers - for exception in exceptions: - logger.error("Error occurred in worker", exc_info=exception) - - # Raise the last exception - raise exceptions[-1] diff --git a/client/ayon_core/plugins/publish/integrate_hero_version.py b/client/ayon_core/plugins/publish/integrate_hero_version.py index 78a2796d35..69864cce8a 100644 --- a/client/ayon_core/plugins/publish/integrate_hero_version.py +++ b/client/ayon_core/plugins/publish/integrate_hero_version.py @@ -21,7 +21,7 @@ from ayon_api.operations import ( from ayon_api.utils import create_entity_id from ayon_core.lib import create_hard_link, source_hash -from ayon_core.lib.threadpool import as_completed_stop_and_raise_on_error +from ayon_core.lib.file_transaction import as_completed_stop_and_raise_on_error from ayon_core.pipeline.publish import ( get_publish_template_name, OptionalPyblishPluginMixin, From d0e591067156b381c3ced96ed6703b013aeced7d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 12 Mar 2025 17:47:21 +0100 Subject: [PATCH 015/153] 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 016/153] 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 12f6d76043986b8965bd6fc8d6402299b9bf52ea Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 12 Mar 2025 22:01:21 +0100 Subject: [PATCH 017/153] Remove deprecated `StdOutBroker` import fallback --- client/ayon_core/tools/stdout_broker/app.py | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 client/ayon_core/tools/stdout_broker/app.py diff --git a/client/ayon_core/tools/stdout_broker/app.py b/client/ayon_core/tools/stdout_broker/app.py deleted file mode 100644 index ae73db1bb9..0000000000 --- a/client/ayon_core/tools/stdout_broker/app.py +++ /dev/null @@ -1,12 +0,0 @@ -import warnings -from .broker import StdOutBroker - -warnings.warn( - ( - "Import of 'StdOutBroker' from 'ayon_core.tools.stdout_broker.app'" - " is deprecated. Please use 'ayon_core.tools.stdout_broker' instead." - ), - DeprecationWarning -) - -__all__ = ("StdOutBroker", ) From 5540e7923a5f3dc2cc6179deccc783e9c33888c0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 13 Mar 2025 10:59:38 +0100 Subject: [PATCH 018/153] 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 019/153] 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 4283d0b4534a93d1a79c8c86350eb348aef0904e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 27 Mar 2025 12:59:15 +0100 Subject: [PATCH 020/153] Allow disabling removal of rendered files on farm renders With this disabled the `metadata.json` and work area renders will remain on disk after a publish even if the folder is not a persistent staging dir --- .../plugins/publish/collect_rendered_files.py | 7 +++++-- server/settings/publish_plugins.py | 21 +++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/plugins/publish/collect_rendered_files.py b/client/ayon_core/plugins/publish/collect_rendered_files.py index deecf7ba24..c69dddd6ec 100644 --- a/client/ayon_core/plugins/publish/collect_rendered_files.py +++ b/client/ayon_core/plugins/publish/collect_rendered_files.py @@ -31,6 +31,9 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): # Keep "filesequence" for backwards compatibility of older jobs targets = ["filesequence", "farm"] label = "Collect rendered frames" + settings_category = "core" + + remove_files = True _context = None @@ -120,7 +123,7 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): self._fill_staging_dir(repre_data, anatomy) representations.append(repre_data) - if not staging_dir_persistent: + if self.remove_files and not staging_dir_persistent: add_repre_files_for_cleanup(instance, repre_data) instance.data["representations"] = representations @@ -170,7 +173,7 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): os.environ.update(session_data) staging_dir_persistent = self._process_path(data, anatomy) - if not staging_dir_persistent: + if self.remove_files and not staging_dir_persistent: context.data["cleanupFullPaths"].append(path) context.data["cleanupEmptyDirs"].append( os.path.dirname(path) diff --git a/server/settings/publish_plugins.py b/server/settings/publish_plugins.py index c9c66e65d9..029eab5fc4 100644 --- a/server/settings/publish_plugins.py +++ b/server/settings/publish_plugins.py @@ -925,6 +925,20 @@ class IntegrateHeroVersionModel(BaseSettingsModel): "hero versions.") +class CollectRenderedFilesModel(BaseSettingsModel): + remove_files: bool = SettingsField( + True, + title="Remove rendered files", + description=( + "Remove rendered files and metadata json on publish.\n\n" + "Note that when enabled but the render is to a configured " + "persistent staging directory the files will not be removed. " + "However with this disabled the files will **not** be removed in " + "either case." + ) + ) + + class CleanUpModel(BaseSettingsModel): _isGroup = True paterns: list[str] = SettingsField( # codespell:ignore paterns @@ -1026,6 +1040,10 @@ class PublishPuginsModel(BaseSettingsModel): default_factory=IntegrateHeroVersionModel, title="Integrate Hero Version" ) + CollectRenderedFiles: CollectRenderedFilesModel = SettingsField( + default_factory=CollectRenderedFilesModel, + title="Clean up farm rendered files" + ) CleanUp: CleanUpModel = SettingsField( default_factory=CleanUpModel, title="Clean Up" @@ -1410,6 +1428,9 @@ DEFAULT_PUBLISH_VALUES = { ], "use_hardlinks": False }, + "CollectRenderedFiles": { + "remove_files": True + }, "CleanUp": { "paterns": [], # codespell:ignore paterns "remove_temp_renders": False From d20942892f0f6b00c5b5abdf867351b25c2f247b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 31 Mar 2025 14:45:31 +0200 Subject: [PATCH 021/153] 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 022/153] 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 023/153] 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 024/153] 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 025/153] 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 026/153] 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 027/153] 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 028/153] 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 029/153] 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 030/153] 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 031/153] 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 032/153] 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 033/153] 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 034/153] 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 035/153] 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 036/153] 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 037/153] 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 038/153] 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 039/153] 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 040/153] 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 041/153] 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 042/153] 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 043/153] 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 044/153] 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 045/153] 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 046/153] 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 047/153] 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 048/153] 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 049/153] 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 050/153] 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 051/153] 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 b5961dcb92ec4b7fe85916061adc828476c40b15 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 14 Apr 2025 17:52:25 +0200 Subject: [PATCH 052/153] enable preview in ruff linting --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 3da97e6b2a..89f1d2a2cf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -83,6 +83,7 @@ indent-width = 4 target-version = "py39" [tool.ruff.lint] +preview = true # Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. select = ["E", "F", "W"] ignore = [] From b403fccf0524f38450694ce065aedb3b3c172bad Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 14 Apr 2025 17:54:45 +0200 Subject: [PATCH 053/153] fix new line chars --- client/ayon_core/cli.py | 2 -- client/ayon_core/lib/attribute_definitions.py | 4 +--- client/ayon_core/lib/ayon_connection.py | 2 ++ client/ayon_core/lib/terminal.py | 1 + client/ayon_core/plugins/load/export_otio.py | 1 + .../plugins/publish/collect_anatomy_instance_data.py | 1 - .../ayon_core/plugins/publish/collect_farm_env_variables.py | 1 - .../plugins/publish/collect_otio_subset_resources.py | 3 --- client/ayon_core/scripts/otio_burnin.py | 1 - client/ayon_core/scripts/slates/slate_base/base.py | 1 - client/ayon_core/tools/console_interpreter/ui/widgets.py | 1 - client/ayon_core/tools/tray/lib.py | 1 - .../pipeline/editorial/test_collect_otio_frame_ranges.py | 1 + .../ayon_core/pipeline/editorial/test_extract_otio_review.py | 2 ++ .../pipeline/editorial/test_media_range_with_retimes.py | 3 ++- 15 files changed, 10 insertions(+), 15 deletions(-) diff --git a/client/ayon_core/cli.py b/client/ayon_core/cli.py index 6f89a6d17d..322c294cfb 100644 --- a/client/ayon_core/cli.py +++ b/client/ayon_core/cli.py @@ -24,7 +24,6 @@ from ayon_core.lib.env_tools import ( ) - @click.group(invoke_without_command=True) @click.pass_context @click.option("--use-staging", is_flag=True, @@ -173,7 +172,6 @@ def contextselection( main(output_path, project, folder, strict) - @main_cli.command( context_settings=dict( ignore_unknown_options=True, diff --git a/client/ayon_core/lib/attribute_definitions.py b/client/ayon_core/lib/attribute_definitions.py index 6b334aa16a..cb74fea0f1 100644 --- a/client/ayon_core/lib/attribute_definitions.py +++ b/client/ayon_core/lib/attribute_definitions.py @@ -22,12 +22,10 @@ import clique if typing.TYPE_CHECKING: from typing import Self, Tuple, Union, TypedDict, Pattern - class EnumItemDict(TypedDict): label: str value: Any - EnumItemsInputType = Union[ Dict[Any, str], List[Tuple[Any, str]], @@ -35,7 +33,6 @@ if typing.TYPE_CHECKING: List[EnumItemDict] ] - class FileDefItemDict(TypedDict): directory: str filenames: List[str] @@ -289,6 +286,7 @@ AttrDefType = TypeVar("AttrDefType", bound=AbstractAttrDef) # UI attribute definitions won't hold value # ----------------------------------------- + class UIDef(AbstractAttrDef): is_value_def = False diff --git a/client/ayon_core/lib/ayon_connection.py b/client/ayon_core/lib/ayon_connection.py index 1132d77aaa..32aa5ad629 100644 --- a/client/ayon_core/lib/ayon_connection.py +++ b/client/ayon_core/lib/ayon_connection.py @@ -177,10 +177,12 @@ def initialize_ayon_connection(force=False): return _new_get_last_versions( con, *args, **kwargs ) + def _lv_by_pi_wrapper(*args, **kwargs): return _new_get_last_version_by_product_id( con, *args, **kwargs ) + def _lv_by_pn_wrapper(*args, **kwargs): return _new_get_last_version_by_product_name( con, *args, **kwargs diff --git a/client/ayon_core/lib/terminal.py b/client/ayon_core/lib/terminal.py index 10fcc79a27..ea23feeb95 100644 --- a/client/ayon_core/lib/terminal.py +++ b/client/ayon_core/lib/terminal.py @@ -39,6 +39,7 @@ class Terminal: """ from ayon_core.lib import env_value_to_bool + log_no_colors = env_value_to_bool( "AYON_LOG_NO_COLORS", default=None ) diff --git a/client/ayon_core/plugins/load/export_otio.py b/client/ayon_core/plugins/load/export_otio.py index e7a844aed3..8094490246 100644 --- a/client/ayon_core/plugins/load/export_otio.py +++ b/client/ayon_core/plugins/load/export_otio.py @@ -22,6 +22,7 @@ from ayon_core.tools.utils import show_message_dialog OTIO = None FRAME_SPLITTER = "__frame_splitter__" + def _import_otio(): global OTIO if OTIO is None: diff --git a/client/ayon_core/plugins/publish/collect_anatomy_instance_data.py b/client/ayon_core/plugins/publish/collect_anatomy_instance_data.py index 677ebb04a2..2fcf562dd0 100644 --- a/client/ayon_core/plugins/publish/collect_anatomy_instance_data.py +++ b/client/ayon_core/plugins/publish/collect_anatomy_instance_data.py @@ -394,7 +394,6 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin): if aov: anatomy_data["aov"] = aov - def _fill_folder_data(self, instance, project_entity, anatomy_data): # QUESTION: should we make sure that all folder data are popped if # folder data cannot be found? diff --git a/client/ayon_core/plugins/publish/collect_farm_env_variables.py b/client/ayon_core/plugins/publish/collect_farm_env_variables.py index 2782ea86ac..39c421381d 100644 --- a/client/ayon_core/plugins/publish/collect_farm_env_variables.py +++ b/client/ayon_core/plugins/publish/collect_farm_env_variables.py @@ -43,4 +43,3 @@ class CollectCoreJobEnvVars(pyblish.api.ContextPlugin): if value: self.log.debug(f"Setting job env: {key}: {value}") env[key] = value - diff --git a/client/ayon_core/plugins/publish/collect_otio_subset_resources.py b/client/ayon_core/plugins/publish/collect_otio_subset_resources.py index f1fa6a817d..275b8a7f55 100644 --- a/client/ayon_core/plugins/publish/collect_otio_subset_resources.py +++ b/client/ayon_core/plugins/publish/collect_otio_subset_resources.py @@ -194,7 +194,6 @@ class CollectOtioSubsetResources( repre = self._create_representation( frame_start, frame_end, file=filename) - else: _trim = False dirname, filename = os.path.split(media_ref.target_url) @@ -209,7 +208,6 @@ class CollectOtioSubsetResources( repre = self._create_representation( frame_start, frame_end, file=filename, trim=_trim) - instance.data["originalDirname"] = self.staging_dir # add representation to instance data @@ -221,7 +219,6 @@ class CollectOtioSubsetResources( instance.data["representations"].append(repre) - self.log.debug(instance.data) def _create_representation(self, start, end, **kwargs): diff --git a/client/ayon_core/scripts/otio_burnin.py b/client/ayon_core/scripts/otio_burnin.py index cb72606222..77eeecaff6 100644 --- a/client/ayon_core/scripts/otio_burnin.py +++ b/client/ayon_core/scripts/otio_burnin.py @@ -173,7 +173,6 @@ class ModifiedBurnins(ffmpeg_burnins.Burnins): if frame_end is not None: options["frame_end"] = frame_end - options["label"] = align self._add_burnin(text, align, options, DRAWTEXT) diff --git a/client/ayon_core/scripts/slates/slate_base/base.py b/client/ayon_core/scripts/slates/slate_base/base.py index e1648c916a..91793d511d 100644 --- a/client/ayon_core/scripts/slates/slate_base/base.py +++ b/client/ayon_core/scripts/slates/slate_base/base.py @@ -353,7 +353,6 @@ class BaseObj: self.items[item.id] = item item.fill_data_format() - def reset(self): for item in self.items.values(): item.reset() diff --git a/client/ayon_core/tools/console_interpreter/ui/widgets.py b/client/ayon_core/tools/console_interpreter/ui/widgets.py index 2b9361666e..3dc55b081c 100644 --- a/client/ayon_core/tools/console_interpreter/ui/widgets.py +++ b/client/ayon_core/tools/console_interpreter/ui/widgets.py @@ -248,4 +248,3 @@ class EnhancedTabBar(QtWidgets.QTabBar): else: super().mouseReleaseEvent(event) - diff --git a/client/ayon_core/tools/tray/lib.py b/client/ayon_core/tools/tray/lib.py index 13ee1eea5c..deb49b9711 100644 --- a/client/ayon_core/tools/tray/lib.py +++ b/client/ayon_core/tools/tray/lib.py @@ -738,4 +738,3 @@ def main(force=False): sys.exit(1) main() - diff --git a/tests/client/ayon_core/pipeline/editorial/test_collect_otio_frame_ranges.py b/tests/client/ayon_core/pipeline/editorial/test_collect_otio_frame_ranges.py index 20f0c05804..2f67ee244c 100644 --- a/tests/client/ayon_core/pipeline/editorial/test_collect_otio_frame_ranges.py +++ b/tests/client/ayon_core/pipeline/editorial/test_collect_otio_frame_ranges.py @@ -101,6 +101,7 @@ def test_image_sequence(): expected_data, ) + def test_media_retimed(): """ EXR image sequence. 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 8ad2e44b06..45191a2c53 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 @@ -215,6 +215,7 @@ def test_short_movie_tail_gap_handles(): assert calls == expected + def test_multiple_review_clips_no_gap(): """ Use multiple review clips (image sequence). @@ -298,6 +299,7 @@ def test_multiple_review_clips_no_gap(): assert calls == expected + def test_multiple_review_clips_with_gap(): """ Use multiple review clips (image sequence) with gap. diff --git a/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py b/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py index 112d00b3e4..b475d629bb 100644 --- a/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py +++ b/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py @@ -257,7 +257,6 @@ def test_movie_timewarp(): ) - def test_img_sequence_no_handles(): """ Img sequence clip (no embedded timecode) @@ -334,6 +333,7 @@ def test_img_sequence_relative_source_range(): expected_data ) + def test_img_sequence_conform_to_23_976fps(): """ Img sequence clip @@ -409,6 +409,7 @@ def test_img_sequence_reverse_speed_no_tc(): handle_end=0, ) + def test_img_sequence_reverse_speed_from_24_to_23_976fps(): """ Img sequence clip From 486be39faaa6711cf4b61d965fef24a50a34c329 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 14 Apr 2025 17:55:48 +0200 Subject: [PATCH 054/153] fix whitespaces --- client/ayon_core/addon/utils.py | 2 +- client/ayon_core/pipeline/create/context.py | 8 ++++---- client/ayon_core/pipeline/create/structures.py | 2 +- client/ayon_core/pipeline/farm/pyblish_functions.py | 2 +- client/ayon_core/pipeline/staging_dir.py | 2 +- client/ayon_core/plugins/load/delete_old_versions.py | 2 +- client/ayon_core/plugins/publish/collect_hierarchy.py | 2 +- client/ayon_core/plugins/publish/extract_review.py | 2 +- .../ayon_core/plugins/publish/integrate_resources_path.py | 2 +- client/ayon_core/scripts/slates/slate_base/base.py | 2 +- client/ayon_core/tools/creator/window.py | 2 +- .../tools/experimental_tools/pyblish_debug_stepper.py | 4 ++-- client/ayon_core/tools/loader/ui/window.py | 6 +++--- client/ayon_core/tools/publisher/models/create.py | 8 ++++---- 14 files changed, 23 insertions(+), 23 deletions(-) diff --git a/client/ayon_core/addon/utils.py b/client/ayon_core/addon/utils.py index f983e37d3c..bb365f42e1 100644 --- a/client/ayon_core/addon/utils.py +++ b/client/ayon_core/addon/utils.py @@ -37,7 +37,7 @@ def _handle_error( if process_context.headless: if detail: print(detail) - print(f"{10*'*'}\n{message}\n{10*'*'}") + print(f"{10 * '*'}\n{message}\n{10 * '*'}") return current_dir = os.path.dirname(os.path.abspath(__file__)) diff --git a/client/ayon_core/pipeline/create/context.py b/client/ayon_core/pipeline/create/context.py index 6ac6685647..f0d9fa8927 100644 --- a/client/ayon_core/pipeline/create/context.py +++ b/client/ayon_core/pipeline/create/context.py @@ -872,7 +872,7 @@ class CreateContext: """ return self._event_hub.add_callback(INSTANCE_ADDED_TOPIC, callback) - def add_instances_removed_callback (self, callback): + def add_instances_removed_callback(self, callback): """Register callback for removed instances. Event is triggered when instances are already removed from context. @@ -933,7 +933,7 @@ class CreateContext: """ self._event_hub.add_callback(VALUE_CHANGED_TOPIC, callback) - def add_pre_create_attr_defs_change_callback (self, callback): + def add_pre_create_attr_defs_change_callback(self, callback): """Register callback to listen pre-create attribute changes. Create plugin can trigger refresh of pre-create attributes. Usage of @@ -961,7 +961,7 @@ class CreateContext: PRE_CREATE_ATTR_DEFS_CHANGED_TOPIC, callback ) - def add_create_attr_defs_change_callback (self, callback): + def add_create_attr_defs_change_callback(self, callback): """Register callback to listen create attribute changes. Create plugin changed attribute definitions of instance. @@ -986,7 +986,7 @@ class CreateContext: """ self._event_hub.add_callback(CREATE_ATTR_DEFS_CHANGED_TOPIC, callback) - def add_publish_attr_defs_change_callback (self, callback): + def add_publish_attr_defs_change_callback(self, callback): """Register callback to listen publish attribute changes. Publish plugin changed attribute definitions of instance of context. diff --git a/client/ayon_core/pipeline/create/structures.py b/client/ayon_core/pipeline/create/structures.py index 6b45a5c610..d7ba6b9c24 100644 --- a/client/ayon_core/pipeline/create/structures.py +++ b/client/ayon_core/pipeline/create/structures.py @@ -369,7 +369,7 @@ class PublishAttributes: return copy.deepcopy(self._origin_data) def attribute_value_changed(self, key, changes): - self._parent.publish_attribute_value_changed(key, changes) + self._parent.publish_attribute_value_changed(key, changes) def set_publish_plugin_attr_defs( self, diff --git a/client/ayon_core/pipeline/farm/pyblish_functions.py b/client/ayon_core/pipeline/farm/pyblish_functions.py index c6f3ae7115..77a2f1d4c0 100644 --- a/client/ayon_core/pipeline/farm/pyblish_functions.py +++ b/client/ayon_core/pipeline/farm/pyblish_functions.py @@ -1,4 +1,4 @@ -from __future__ import annotations +from __future__ import annotations import copy import os import re diff --git a/client/ayon_core/pipeline/staging_dir.py b/client/ayon_core/pipeline/staging_dir.py index 1cb2979415..a172c177fd 100644 --- a/client/ayon_core/pipeline/staging_dir.py +++ b/client/ayon_core/pipeline/staging_dir.py @@ -209,7 +209,7 @@ def get_staging_dir_info( staging_dir_config = get_staging_dir_config( project_entity["name"], task_type, - task_name , + task_name, product_type, product_name, host_name, diff --git a/client/ayon_core/plugins/load/delete_old_versions.py b/client/ayon_core/plugins/load/delete_old_versions.py index f8c45baff6..3a42ccba7e 100644 --- a/client/ayon_core/plugins/load/delete_old_versions.py +++ b/client/ayon_core/plugins/load/delete_old_versions.py @@ -211,7 +211,7 @@ class DeleteOldVersions(load.ProductLoaderPlugin): f"This will keep only the last {versions_to_keep} " f"versions for the {num_contexts} selected product{s}." ) - informative_text="Warning: This will delete files from disk" + informative_text = "Warning: This will delete files from disk" detailed_text = ( f"Keep only {versions_to_keep} versions for:\n{contexts_list}" ) diff --git a/client/ayon_core/plugins/publish/collect_hierarchy.py b/client/ayon_core/plugins/publish/collect_hierarchy.py index 266c2e1458..56b48c37f6 100644 --- a/client/ayon_core/plugins/publish/collect_hierarchy.py +++ b/client/ayon_core/plugins/publish/collect_hierarchy.py @@ -50,7 +50,7 @@ class CollectHierarchy(pyblish.api.ContextPlugin): "comments": instance.data.get("comments", []), } - shot_data["attributes"] = {} + shot_data["attributes"] = {} SHOT_ATTRS = ( "handleStart", "handleEnd", diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index df87abba91..a15886451b 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -1333,7 +1333,7 @@ class ExtractReview(pyblish.api.InstancePlugin): bg_red, bg_green, bg_blue = overscan_color else: # Backwards compatibility - bg_red, bg_green, bg_blue, _ = overscan_color + bg_red, bg_green, bg_blue, _ = overscan_color overscan_color_value = "#{0:0>2X}{1:0>2X}{2:0>2X}".format( bg_red, bg_green, bg_blue diff --git a/client/ayon_core/plugins/publish/integrate_resources_path.py b/client/ayon_core/plugins/publish/integrate_resources_path.py index 56dc0e5ef7..b518f7f6f1 100644 --- a/client/ayon_core/plugins/publish/integrate_resources_path.py +++ b/client/ayon_core/plugins/publish/integrate_resources_path.py @@ -7,7 +7,7 @@ class IntegrateResourcesPath(pyblish.api.InstancePlugin): label = "Integrate Resources Path" order = pyblish.api.IntegratorOrder - 0.05 - families = ["clip", "projectfile", "plate"] + families = ["clip", "projectfile", "plate"] def process(self, instance): resources = instance.data.get("resources") or [] diff --git a/client/ayon_core/scripts/slates/slate_base/base.py b/client/ayon_core/scripts/slates/slate_base/base.py index 91793d511d..a4427bbb86 100644 --- a/client/ayon_core/scripts/slates/slate_base/base.py +++ b/client/ayon_core/scripts/slates/slate_base/base.py @@ -175,7 +175,7 @@ class BaseObj: self.log.warning("Invalid range '{}'".format(part)) continue - for idx in range(sub_parts[0], sub_parts[1]+1): + for idx in range(sub_parts[0], sub_parts[1] + 1): indexes.append(idx) return indexes diff --git a/client/ayon_core/tools/creator/window.py b/client/ayon_core/tools/creator/window.py index 5bdc6da9b6..5d1c0a272a 100644 --- a/client/ayon_core/tools/creator/window.py +++ b/client/ayon_core/tools/creator/window.py @@ -492,7 +492,7 @@ def show(parent=None): try: module.window.close() - del(module.window) + del module.window except (AttributeError, RuntimeError): pass diff --git a/client/ayon_core/tools/experimental_tools/pyblish_debug_stepper.py b/client/ayon_core/tools/experimental_tools/pyblish_debug_stepper.py index 33de4bf036..a485c682a1 100644 --- a/client/ayon_core/tools/experimental_tools/pyblish_debug_stepper.py +++ b/client/ayon_core/tools/experimental_tools/pyblish_debug_stepper.py @@ -32,7 +32,7 @@ from qtpy import QtWidgets, QtCore, QtGui import pyblish.api from ayon_core import style -TAB = 4* " " +TAB = 4 * " " HEADER_SIZE = "15px" KEY_COLOR = QtGui.QColor("#ffffff") @@ -243,7 +243,7 @@ class DebugUI(QtWidgets.QDialog): self._set_window_title(plugin=result["plugin"]) - print(10*"<", result["plugin"].__name__, 10*">") + print(10 * "<", result["plugin"].__name__, 10 * ">") plugin_order = result["plugin"].order plugin_name = result["plugin"].__name__ diff --git a/client/ayon_core/tools/loader/ui/window.py b/client/ayon_core/tools/loader/ui/window.py index 3d2e15c630..b70f5554c7 100644 --- a/client/ayon_core/tools/loader/ui/window.py +++ b/client/ayon_core/tools/loader/ui/window.py @@ -519,9 +519,9 @@ class LoaderWindow(QtWidgets.QWidget): thumbnail_paths.discard(None) if thumbnail_paths: - self._thumbnails_widget.set_current_thumbnail_paths( - thumbnail_paths - ) + self._thumbnails_widget.set_current_thumbnail_paths( + thumbnail_paths + ) else: self._thumbnails_widget.set_current_thumbnails(None) diff --git a/client/ayon_core/tools/publisher/models/create.py b/client/ayon_core/tools/publisher/models/create.py index 9644af43e0..900168eaef 100644 --- a/client/ayon_core/tools/publisher/models/create.py +++ b/client/ayon_core/tools/publisher/models/create.py @@ -461,19 +461,19 @@ class CreateModel: self._create_context.add_instances_added_callback( self._cc_added_instance ) - self._create_context.add_instances_removed_callback ( + self._create_context.add_instances_removed_callback( self._cc_removed_instance ) self._create_context.add_value_changed_callback( self._cc_value_changed ) - self._create_context.add_pre_create_attr_defs_change_callback ( + self._create_context.add_pre_create_attr_defs_change_callback( self._cc_pre_create_attr_changed ) - self._create_context.add_create_attr_defs_change_callback ( + self._create_context.add_create_attr_defs_change_callback( self._cc_create_attr_changed ) - self._create_context.add_publish_attr_defs_change_callback ( + self._create_context.add_publish_attr_defs_change_callback( self._cc_publish_attr_changed ) From b0ac87b7b12006dc8b3fffe441b91a884cc65ce5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 14 Apr 2025 17:56:21 +0200 Subject: [PATCH 055/153] handle unused variables --- client/ayon_core/lib/vendor_bin_utils.py | 4 ++-- client/ayon_core/pipeline/delivery.py | 4 ++-- client/ayon_core/pipeline/farm/pyblish_functions.py | 2 +- client/ayon_core/pipeline/schema/__init__.py | 2 +- client/ayon_core/pipeline/workfile/path_resolving.py | 4 ++-- client/ayon_core/plugins/publish/integrate.py | 2 +- client/ayon_core/scripts/slates/slate_base/items.py | 12 ++++++------ client/ayon_core/tools/publisher/models/publish.py | 2 +- client/ayon_core/tools/utils/tasks_widget.py | 2 +- client/ayon_core/tools/workfiles/models/workfiles.py | 2 +- 10 files changed, 18 insertions(+), 18 deletions(-) diff --git a/client/ayon_core/lib/vendor_bin_utils.py b/client/ayon_core/lib/vendor_bin_utils.py index 41654476c2..412a9292cc 100644 --- a/client/ayon_core/lib/vendor_bin_utils.py +++ b/client/ayon_core/lib/vendor_bin_utils.py @@ -162,7 +162,7 @@ def find_tool_in_custom_paths(paths, tool, validation_func=None): # Handle cases when path is just an executable # - it allows to use executable from PATH # - basename must match 'tool' value (without extension) - extless_path, ext = os.path.splitext(path) + extless_path, _ext = os.path.splitext(path) if extless_path == tool: executable_path = find_executable(tool) if executable_path and ( @@ -181,7 +181,7 @@ def find_tool_in_custom_paths(paths, tool, validation_func=None): # If path is a file validate it if os.path.isfile(normalized): - basename, ext = os.path.splitext(os.path.basename(path)) + basename, _ext = os.path.splitext(os.path.basename(path)) # Check if the filename has actually the sane bane as 'tool' if basename == tool: executable_path = find_executable(normalized) diff --git a/client/ayon_core/pipeline/delivery.py b/client/ayon_core/pipeline/delivery.py index 55c840f3a5..e686b739ae 100644 --- a/client/ayon_core/pipeline/delivery.py +++ b/client/ayon_core/pipeline/delivery.py @@ -255,7 +255,7 @@ def deliver_sequence( report_items[""].append(msg) return report_items, 0 - dir_path, file_name = os.path.split(str(src_path)) + dir_path, _file_name = os.path.split(str(src_path)) context = repre["context"] ext = context.get("ext", context.get("representation")) @@ -270,7 +270,7 @@ def deliver_sequence( # context.representation could be .psd ext = ext.replace("..", ".") - src_collections, remainder = clique.assemble(os.listdir(dir_path)) + src_collections, _remainder = clique.assemble(os.listdir(dir_path)) src_collection = None for col in src_collections: if col.tail != ext: diff --git a/client/ayon_core/pipeline/farm/pyblish_functions.py b/client/ayon_core/pipeline/farm/pyblish_functions.py index 77a2f1d4c0..812e9b0321 100644 --- a/client/ayon_core/pipeline/farm/pyblish_functions.py +++ b/client/ayon_core/pipeline/farm/pyblish_functions.py @@ -1168,7 +1168,7 @@ def prepare_cache_representations(skeleton_data, exp_files, anatomy): """ representations = [] - collections, remainders = clique.assemble(exp_files) + collections, _remainders = clique.assemble(exp_files) log = Logger.get_logger("farm_publishing") diff --git a/client/ayon_core/pipeline/schema/__init__.py b/client/ayon_core/pipeline/schema/__init__.py index d16755696d..5e4e8a668d 100644 --- a/client/ayon_core/pipeline/schema/__init__.py +++ b/client/ayon_core/pipeline/schema/__init__.py @@ -41,7 +41,7 @@ def validate(data, schema=None): if not _CACHED: _precache() - root, schema = data["schema"].rsplit(":", 1) + _root, schema = data["schema"].rsplit(":", 1) if isinstance(schema, str): schema = _cache[schema + ".json"] diff --git a/client/ayon_core/pipeline/workfile/path_resolving.py b/client/ayon_core/pipeline/workfile/path_resolving.py index 61c6e5b876..9b2fe25199 100644 --- a/client/ayon_core/pipeline/workfile/path_resolving.py +++ b/client/ayon_core/pipeline/workfile/path_resolving.py @@ -329,9 +329,9 @@ def get_last_workfile( Returns: str: Last or first workfile as filename of full path to filename. - """ - filename, version = get_last_workfile_with_version( + """ + filename, _version = get_last_workfile_with_version( workdir, file_template, fill_data, extensions ) if filename is None: diff --git a/client/ayon_core/plugins/publish/integrate.py b/client/ayon_core/plugins/publish/integrate.py index 8e57980ba6..f1e066018c 100644 --- a/client/ayon_core/plugins/publish/integrate.py +++ b/client/ayon_core/plugins/publish/integrate.py @@ -683,7 +683,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): elif is_sequence_representation: # Collection of files (sequence) - src_collections, remainders = clique.assemble(files) + src_collections, _remainders = clique.assemble(files) src_collection = src_collections[0] destination_indexes = list(src_collection.indexes) diff --git a/client/ayon_core/scripts/slates/slate_base/items.py b/client/ayon_core/scripts/slates/slate_base/items.py index ec3358ed5e..eb7859a6e1 100644 --- a/client/ayon_core/scripts/slates/slate_base/items.py +++ b/client/ayon_core/scripts/slates/slate_base/items.py @@ -282,7 +282,7 @@ class ItemTable(BaseItem): value.draw(image, drawer) def value_width(self): - row_heights, col_widths = self.size_values + _row_heights, col_widths = self.size_values width = 0 for _width in col_widths: width += _width @@ -292,7 +292,7 @@ class ItemTable(BaseItem): return width def value_height(self): - row_heights, col_widths = self.size_values + row_heights, _col_widths = self.size_values height = 0 for _height in row_heights: height += _height @@ -569,21 +569,21 @@ class TableField(BaseItem): @property def item_pos_x(self): - pos_x, pos_y, width, height = ( + pos_x, _pos_y, _width, _height = ( self.parent.content_pos_info_by_cord(self.row_idx, self.col_idx) ) return pos_x @property def item_pos_y(self): - pos_x, pos_y, width, height = ( + _pos_x, pos_y, _width, _height = ( self.parent.content_pos_info_by_cord(self.row_idx, self.col_idx) ) return pos_y @property def value_pos_x(self): - pos_x, pos_y, width, height = ( + pos_x, _pos_y, width, _height = ( self.parent.content_pos_info_by_cord(self.row_idx, self.col_idx) ) alignment_hor = self.style["alignment-horizontal"].lower() @@ -605,7 +605,7 @@ class TableField(BaseItem): @property def value_pos_y(self): - pos_x, pos_y, width, height = ( + _pos_x, pos_y, _width, height = ( self.parent.content_pos_info_by_cord(self.row_idx, self.col_idx) ) diff --git a/client/ayon_core/tools/publisher/models/publish.py b/client/ayon_core/tools/publisher/models/publish.py index 97a956b18f..97070d106f 100644 --- a/client/ayon_core/tools/publisher/models/publish.py +++ b/client/ayon_core/tools/publisher/models/publish.py @@ -358,7 +358,7 @@ class PublishReportMaker: exception = result.get("error") if exception: - fname, line_no, func, exc = exception.traceback + fname, line_no, func, _ = exception.traceback # Conversion of exception into string may crash try: diff --git a/client/ayon_core/tools/utils/tasks_widget.py b/client/ayon_core/tools/utils/tasks_widget.py index 9118611c23..744eb6060a 100644 --- a/client/ayon_core/tools/utils/tasks_widget.py +++ b/client/ayon_core/tools/utils/tasks_widget.py @@ -575,7 +575,7 @@ class TasksWidget(QtWidgets.QWidget): if self._tasks_model.is_refreshing: return - parent_id, task_id, task_name, _ = self._get_selected_item_ids() + _parent_id, task_id, task_name, _ = self._get_selected_item_ids() self._controller.set_selected_task(task_id, task_name) self.selection_changed.emit() diff --git a/client/ayon_core/tools/workfiles/models/workfiles.py b/client/ayon_core/tools/workfiles/models/workfiles.py index c621a44937..cc034571f3 100644 --- a/client/ayon_core/tools/workfiles/models/workfiles.py +++ b/client/ayon_core/tools/workfiles/models/workfiles.py @@ -462,7 +462,7 @@ class WorkfileEntitiesModel: anatomy = self._controller.project_anatomy workdir, filename = os.path.split(filepath) - success, rootless_dir = anatomy.find_root_template_from_path(workdir) + _, rootless_dir = anatomy.find_root_template_from_path(workdir) return "/".join([ os.path.normpath(rootless_dir).replace("\\", "/"), filename From dc6bb42013b83c68328205bae226d048db6febb5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 14 Apr 2025 17:56:33 +0200 Subject: [PATCH 056/153] define docstyle convention --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 89f1d2a2cf..4c4272cc30 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -84,6 +84,7 @@ 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 = [] From c82537008f2439fc4591475939d281b651246ee6 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 15 Apr 2025 13:56:43 +0200 Subject: [PATCH 057/153] 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 058/153] 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 059/153] 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 060/153] 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 061/153] 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 062/153] 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 063/153] 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 4f9f7724accd32b762f1b9d145b9aec4b2c27413 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 16 Apr 2025 10:35:41 +0200 Subject: [PATCH 064/153] fix original intnetion of thumbnail created logging --- client/ayon_core/plugins/publish/extract_thumbnail.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_thumbnail.py b/client/ayon_core/plugins/publish/extract_thumbnail.py index 18393022ed..b72862ea22 100644 --- a/client/ayon_core/plugins/publish/extract_thumbnail.py +++ b/client/ayon_core/plugins/publish/extract_thumbnail.py @@ -164,7 +164,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): instance.context.data["cleanupFullPaths"].append(dst_staging) oiio_supported = is_oiio_supported() - repre_thumb_created = False + thumbnail_created = False for repre in filtered_repres: # Reset for each iteration to handle cases where multiple # reviewable thumbnails are needed @@ -241,6 +241,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): if not repre_thumb_created: continue + thumbnail_created = True if len(explicit_repres) > 1: repre_name = "thumbnail_{}".format(repre["outputName"]) else: @@ -294,7 +295,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # There is no need to create more then one thumbnail break - if not repre_thumb_created: + if not thumbnail_created: self.log.warning("Thumbnail has not been created.") def _is_review_instance(self, instance): 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 065/153] 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 8d253033332e55b46ca0bcbeca59da50d078cdd7 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 16 Apr 2025 21:53:17 +0200 Subject: [PATCH 066/153] Revert defaults so removal of rendered files is disabled by default --- client/ayon_core/plugins/publish/collect_rendered_files.py | 2 +- server/settings/publish_plugins.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/plugins/publish/collect_rendered_files.py b/client/ayon_core/plugins/publish/collect_rendered_files.py index c69dddd6ec..5c68af888f 100644 --- a/client/ayon_core/plugins/publish/collect_rendered_files.py +++ b/client/ayon_core/plugins/publish/collect_rendered_files.py @@ -33,7 +33,7 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): label = "Collect rendered frames" settings_category = "core" - remove_files = True + remove_files = False _context = None diff --git a/server/settings/publish_plugins.py b/server/settings/publish_plugins.py index e6a146a9d8..5f5891e4f4 100644 --- a/server/settings/publish_plugins.py +++ b/server/settings/publish_plugins.py @@ -931,7 +931,7 @@ class IntegrateHeroVersionModel(BaseSettingsModel): class CollectRenderedFilesModel(BaseSettingsModel): remove_files: bool = SettingsField( - True, + False, title="Remove rendered files", description=( "Remove rendered files and metadata json on publish.\n\n" @@ -1447,7 +1447,7 @@ DEFAULT_PUBLISH_VALUES = { "enabled": True, }, "CollectRenderedFiles": { - "remove_files": True + "remove_files": False }, "CleanUp": { "paterns": [], # codespell:ignore paterns From 644765cb85b5c786a3a7a59af4a00a623587eb38 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 16 Apr 2025 22:03:02 +0200 Subject: [PATCH 067/153] Remove redundant duplicate lines of code --- client/ayon_core/pipeline/farm/pyblish_functions.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/client/ayon_core/pipeline/farm/pyblish_functions.py b/client/ayon_core/pipeline/farm/pyblish_functions.py index c6f3ae7115..e10bcc9bb1 100644 --- a/client/ayon_core/pipeline/farm/pyblish_functions.py +++ b/client/ayon_core/pipeline/farm/pyblish_functions.py @@ -660,14 +660,6 @@ def _get_legacy_product_name_and_group( warnings.warn("Using legacy product name for renders", DeprecationWarning) - if not source_product_name.startswith(product_type): - resulting_group_name = '{}{}{}{}{}'.format( - product_type, - task_name[0].upper(), task_name[1:], - source_product_name[0].upper(), source_product_name[1:]) - else: - resulting_group_name = source_product_name - # create product name `` if not source_product_name.startswith(product_type): resulting_group_name = '{}{}{}{}{}'.format( From ca12d13a40a8c3b03117c60a211828064e989bb2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 17 Apr 2025 12:50:28 +0200 Subject: [PATCH 068/153] Improves thumbnail extraction reliability Enhances thumbnail extraction by retrying without seeking if the initial attempt fails. This addresses issues where the generated thumbnail file is either missing or empty. It also calculates the seek position more accurately and avoid seeking for very short videos. --- .../plugins/publish/extract_thumbnail.py | 65 +++++++++++++++---- 1 file changed, 51 insertions(+), 14 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_thumbnail.py b/client/ayon_core/plugins/publish/extract_thumbnail.py index b72862ea22..89bb9a90ab 100644 --- a/client/ayon_core/plugins/publish/extract_thumbnail.py +++ b/client/ayon_core/plugins/publish/extract_thumbnail.py @@ -486,25 +486,37 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # Set video input attributes max_int = str(2147483647) video_data = get_ffprobe_data(video_file_path, logger=self.log) - # Use duration of the individual streams since it is returned with - # higher decimal precision than 'format.duration'. We need this - # more precise value for calculating the correct amount of frames - # for higher FPS ranges or decimal ranges, e.g. 29.97 FPS - duration = max( - float(stream.get("duration", 0)) - for stream in video_data["streams"] - if stream.get("codec_type") == "video" - ) + # Get duration or use a safe default (single frame) + duration = 0 + for stream in video_data["streams"]: + if stream.get("codec_type") == "video": + stream_duration = float(stream.get("duration", 0)) + if stream_duration > duration: + duration = stream_duration + + # For very short videos, just use the first frame + # Calculate seek position safely + seek_position = 0 + if duration > 0.1: # Only use timestamp calculation for videos longer than 0.1 seconds + seek_position = duration * self.duration_split + + # Build command args cmd_args = [ "-y", - "-ss", str(duration * self.duration_split), "-i", video_file_path, "-analyzeduration", max_int, "-probesize", max_int, - "-frames:v", "1" ] + # Only add -ss if we're seeking to a specific position + if seek_position > 0: + cmd_args.insert(1, "-ss") + cmd_args.insert(2, str(seek_position)) + + # Ensure we extract exactly one frame + cmd_args.extend(["-frames:v", "1"]) + # add output file path cmd_args.append(output_thumb_file_path) @@ -517,9 +529,34 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # run subprocess self.log.debug("Executing: {}".format(" ".join(cmd))) run_subprocess(cmd, logger=self.log) - self.log.debug( - "Thumbnail created: {}".format(output_thumb_file_path)) - return output_thumb_file_path + + # Verify the output file was created + if os.path.exists(output_thumb_file_path) and os.path.getsize(output_thumb_file_path) > 0: + self.log.debug( + "Thumbnail created: {}".format(output_thumb_file_path)) + return output_thumb_file_path + else: + self.log.warning( + "Output file was not created or is empty: {}".format(output_thumb_file_path)) + + # Fallback to extracting the first frame without seeking + if "-ss" in cmd_args: + self.log.debug("Trying fallback without seeking") + # Remove -ss and its value + ss_index = cmd_args.index("-ss") + cmd_args.pop(ss_index) # Remove -ss + cmd_args.pop(ss_index) # Remove the timestamp value + + # Create new command and try again + cmd = get_ffmpeg_tool_args("ffmpeg", *cmd_args) + self.log.debug("Fallback command: {}".format(" ".join(cmd))) + run_subprocess(cmd, logger=self.log) + + if os.path.exists(output_thumb_file_path) and os.path.getsize(output_thumb_file_path) > 0: + self.log.debug("Fallback thumbnail created: {}".format(output_thumb_file_path)) + return output_thumb_file_path + + return None except RuntimeError as error: self.log.warning( "Failed intermediate thumb source using ffmpeg: {}".format( From bf44622c057fd6d45c8a270ae24ee0f71eb89abc Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 17 Apr 2025 12:55:02 +0200 Subject: [PATCH 069/153] Improves thumbnail extraction reliability Ensures thumbnail extraction falls back to the first frame if the initial attempt fails, handling potential issues with seek position calculation or output file creation. This enhances the robustness of the thumbnail creation process. --- .../plugins/publish/extract_thumbnail.py | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_thumbnail.py b/client/ayon_core/plugins/publish/extract_thumbnail.py index 89bb9a90ab..e5108444f7 100644 --- a/client/ayon_core/plugins/publish/extract_thumbnail.py +++ b/client/ayon_core/plugins/publish/extract_thumbnail.py @@ -498,7 +498,8 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # For very short videos, just use the first frame # Calculate seek position safely seek_position = 0 - if duration > 0.1: # Only use timestamp calculation for videos longer than 0.1 seconds + # Only use timestamp calculation for videos longer than 0.1 seconds + if duration > 0.1: seek_position = duration * self.duration_split # Build command args @@ -531,13 +532,17 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): run_subprocess(cmd, logger=self.log) # Verify the output file was created - if os.path.exists(output_thumb_file_path) and os.path.getsize(output_thumb_file_path) > 0: + if ( + os.path.exists(output_thumb_file_path) + and os.path.getsize(output_thumb_file_path) > 0 + ): self.log.debug( "Thumbnail created: {}".format(output_thumb_file_path)) return output_thumb_file_path else: self.log.warning( - "Output file was not created or is empty: {}".format(output_thumb_file_path)) + "Output file was not created or is empty: {}".format( + output_thumb_file_path)) # Fallback to extracting the first frame without seeking if "-ss" in cmd_args: @@ -549,11 +554,18 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # Create new command and try again cmd = get_ffmpeg_tool_args("ffmpeg", *cmd_args) - self.log.debug("Fallback command: {}".format(" ".join(cmd))) + self.log.debug("Fallback command: {}".format( + " ".join(cmd))) run_subprocess(cmd, logger=self.log) - if os.path.exists(output_thumb_file_path) and os.path.getsize(output_thumb_file_path) > 0: - self.log.debug("Fallback thumbnail created: {}".format(output_thumb_file_path)) + if ( + os.path.exists(output_thumb_file_path) + and os.path.getsize(output_thumb_file_path) > 0 + ): + self.log.debug( + "Fallback thumbnail created: {}".format( + output_thumb_file_path) + ) return output_thumb_file_path return None From ba80b3b9b459d74d5aface7b39509316cd6d808b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 17 Apr 2025 17:31:08 +0200 Subject: [PATCH 070/153] 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 a39462497423327691675d0735ef9ac3ecd6e5f5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 22 Apr 2025 14:28:51 +0200 Subject: [PATCH 071/153] don't skip other validations for string match --- client/ayon_core/tools/utils/projects_widget.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/tools/utils/projects_widget.py b/client/ayon_core/tools/utils/projects_widget.py index 88d8a6c9f5..b88b2f410a 100644 --- a/client/ayon_core/tools/utils/projects_widget.py +++ b/client/ayon_core/tools/utils/projects_widget.py @@ -351,13 +351,14 @@ class ProjectSortFilterProxy(QtCore.QSortFilterProxyModel): return True string_pattern = self.filterRegularExpression().pattern() - if string_pattern: - return string_pattern.lower() in project_name.lower() + if ( + string_pattern + and string_pattern.lower() not in project_name.lower() + ): + return False # Current project keep always visible - default = super(ProjectSortFilterProxy, self).filterAcceptsRow( - source_row, source_parent - ) + default = super().filterAcceptsRow(source_row, source_parent) if not default: return default From 922d19137c0e7df497fc7be53fcf7472da8fd7b5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 22 Apr 2025 15:19:48 +0200 Subject: [PATCH 072/153] change order of filters --- client/ayon_core/tools/utils/projects_widget.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/client/ayon_core/tools/utils/projects_widget.py b/client/ayon_core/tools/utils/projects_widget.py index b88b2f410a..c340be2f83 100644 --- a/client/ayon_core/tools/utils/projects_widget.py +++ b/client/ayon_core/tools/utils/projects_widget.py @@ -350,6 +350,14 @@ class ProjectSortFilterProxy(QtCore.QSortFilterProxyModel): if project_name is None: return True + # Make sure current project is visible + if index.data(PROJECT_IS_CURRENT_ROLE): + return True + + default = super().filterAcceptsRow(source_row, source_parent) + if not default: + return default + string_pattern = self.filterRegularExpression().pattern() if ( string_pattern @@ -357,15 +365,6 @@ class ProjectSortFilterProxy(QtCore.QSortFilterProxyModel): ): return False - # Current project keep always visible - default = super().filterAcceptsRow(source_row, source_parent) - if not default: - return default - - # Make sure current project is visible - if index.data(PROJECT_IS_CURRENT_ROLE): - return True - if ( self._filter_inactive and not index.data(PROJECT_IS_ACTIVE_ROLE) From 71dc3650ace89b6ee77583ad43dd05690be32fa9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 23 Apr 2025 10:25:51 +0200 Subject: [PATCH 073/153] 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 4141ce30b403e40dddda5eb288c3a79ef3c4f45d Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 23 Apr 2025 10:39:14 +0200 Subject: [PATCH 074/153] add compatible harmony addon version to package.py --- package.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.py b/package.py index a4ffe1a20d..0358c2f4cd 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 = { + "harmony": ">0.4.0", +} From 71d37d8b59ee3be0b5dcb784b4b478d40c5d2288 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 23 Apr 2025 14:04:41 +0200 Subject: [PATCH 075/153] 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 076/153] 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 077/153] 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 078/153] 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 079/153] 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 080/153] 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 081/153] 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 082/153] 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 083/153] 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 084/153] 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 085/153] 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 086/153] 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 80e0fa6b17052cfdafc7635ae53cf2d5d1d4dec6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 23 Apr 2025 20:05:59 +0200 Subject: [PATCH 087/153] Correctly check whether sequence has any frames in the returned holes collection. As per [`clique.Collection.holes` documentation](https://clique.readthedocs.io/en/stable/api_reference/collection.html#clique.collection.Collection.holes): > Return Collection of missing indexes. --- client/ayon_core/plugins/publish/extract_color_transcode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/extract_color_transcode.py b/client/ayon_core/plugins/publish/extract_color_transcode.py index 1f2c2a89af..6cf30857a4 100644 --- a/client/ayon_core/plugins/publish/extract_color_transcode.py +++ b/client/ayon_core/plugins/publish/extract_color_transcode.py @@ -280,7 +280,7 @@ class ExtractOIIOTranscode(publish.Extractor): collection = collections[0] frames = list(collection.indexes) - if collection.holes(): + if collection.holes().indexes: return files_to_convert frame_str = "{}-{}#".format(frames[0], frames[-1]) From bc509fcf0084d465ff4d51f0f3391a03298e799f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 24 Apr 2025 11:37:26 +0200 Subject: [PATCH 088/153] 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 089/153] 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 090/153] 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 091/153] 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 092/153] 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 093/153] 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 094/153] 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 095/153] 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 096/153] 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 097/153] 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 098/153] 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 099/153] 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 100/153] 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 101/153] 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 102/153] 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 103/153] 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 104/153] 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 105/153] 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 106/153] 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 107/153] 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 108/153] 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 109/153] 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 110/153] 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 111/153] 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 112/153] [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 113/153] 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 114/153] 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 115/153] 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 116/153] :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 117/153] :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 118/153] 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 119/153] 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 120/153] :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 121/153] :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 122/153] [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 123/153] [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 124/153] 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 125/153] 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 126/153] 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 127/153] 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 128/153] 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 129/153] 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 130/153] 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 131/153] 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 132/153] 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 133/153] 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 0d7ced6fc660facf17b3afd8136a10d25a85fb0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Fri, 9 May 2025 16:44:55 +0200 Subject: [PATCH 134/153] Update client/ayon_core/plugins/publish/integrate_hero_version.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/plugins/publish/integrate_hero_version.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/plugins/publish/integrate_hero_version.py b/client/ayon_core/plugins/publish/integrate_hero_version.py index 69864cce8a..1315c114c3 100644 --- a/client/ayon_core/plugins/publish/integrate_hero_version.py +++ b/client/ayon_core/plugins/publish/integrate_hero_version.py @@ -427,9 +427,9 @@ class IntegrateHeroVersion( with ThreadPoolExecutor(max_workers=8) as executor: futures = [ executor.submit(self.copy_file, src_path, dst_path) - for src_path, dst_path - in itertools.chain(src_to_dst_file_paths, - other_file_paths_mapping) + for src_path, dst_path in itertools.chain( + src_to_dst_file_paths, other_file_paths_mapping + ) ] as_completed_stop_and_raise_on_error(executor, futures) From ee7d045413a59ac6c9be922fee1c979d345e087d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Fri, 9 May 2025 17:15:38 +0200 Subject: [PATCH 135/153] :recycle: refactor function name and remove speedcopy fallback --- client/ayon_core/lib/file_transaction.py | 17 ++++++----------- .../plugins/publish/integrate_hero_version.py | 12 ++++-------- 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/client/ayon_core/lib/file_transaction.py b/client/ayon_core/lib/file_transaction.py index bf206b535c..d720ff8d30 100644 --- a/client/ayon_core/lib/file_transaction.py +++ b/client/ayon_core/lib/file_transaction.py @@ -1,18 +1,13 @@ import concurrent.futures import os import logging -import sys import errno from concurrent.futures import ThreadPoolExecutor, Future from typing import List, Optional from ayon_core.lib import create_hard_link -# this is needed until speedcopy for linux is fixed -if sys.platform == "win32": - from speedcopy import copyfile -else: - from shutil import copyfile +from speedcopy import copyfile class DuplicateDestinationError(ValueError): @@ -118,7 +113,7 @@ class FileTransaction: executor.submit(self._backup_file, dst, src) for dst, (src, _) in self._transfers.items() ] - as_completed_stop_and_raise_on_error( + wait_for_future_errors( executor, backup_futures, logger=self.log) # Submit transfer tasks @@ -126,7 +121,7 @@ class FileTransaction: executor.submit(self._transfer_file, dst, src, opts) for dst, (src, opts) in self._transfers.items() ] - as_completed_stop_and_raise_on_error( + wait_for_future_errors( executor, transfer_futures, logger=self.log) def _backup_file(self, dst, src): @@ -228,7 +223,7 @@ class FileTransaction: return src == dst -def as_completed_stop_and_raise_on_error( +def wait_for_future_errors( executor: ThreadPoolExecutor, futures: List[Future], logger: Optional[logging.Logger] = None): @@ -237,7 +232,7 @@ def as_completed_stop_and_raise_on_error( The ThreadPoolExecutor only cancels pending futures on exception but will still complete those that are running - each which also themselves could - fail. We log all exceptions, but re-raise the last exception only. + fail. We log all exceptions but re-raise the last exception only. """ if logger is None: logger = logging.getLogger(__name__) @@ -246,7 +241,7 @@ def as_completed_stop_and_raise_on_error( exception = future.exception() if exception: # As soon as an error occurs, stop executing more futures. - # Running workers however, will still complete so we also want + # Running workers, however, will still be complete, so we also want # to log those errors if any occurred on them. executor.shutdown(wait=True, cancel_futures=True) break diff --git a/client/ayon_core/plugins/publish/integrate_hero_version.py b/client/ayon_core/plugins/publish/integrate_hero_version.py index 1315c114c3..43f93da293 100644 --- a/client/ayon_core/plugins/publish/integrate_hero_version.py +++ b/client/ayon_core/plugins/publish/integrate_hero_version.py @@ -3,13 +3,9 @@ import copy import errno import itertools import shutil -import sys from concurrent.futures import ThreadPoolExecutor -# this is needed until speedcopy for linux is fixed -if sys.platform == "win32": - from speedcopy import copyfile -else: - from shutil import copyfile + +from speedcopy import copyfile import clique import pyblish.api @@ -21,7 +17,7 @@ from ayon_api.operations import ( from ayon_api.utils import create_entity_id from ayon_core.lib import create_hard_link, source_hash -from ayon_core.lib.file_transaction import as_completed_stop_and_raise_on_error +from ayon_core.lib.file_transaction import wait_for_future_errors from ayon_core.pipeline.publish import ( get_publish_template_name, OptionalPyblishPluginMixin, @@ -431,7 +427,7 @@ class IntegrateHeroVersion( src_to_dst_file_paths, other_file_paths_mapping ) ] - as_completed_stop_and_raise_on_error(executor, futures) + wait_for_future_errors(executor, futures) # Update prepared representation etity data with files # and integrate it to server. From 57b808e92477042a2caef41517dbd2284669c0cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Mon, 12 May 2025 13:46:54 +0200 Subject: [PATCH 136/153] Apply suggestions from code review Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../plugins/publish/extract_thumbnail.py | 53 +++++++++---------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_thumbnail.py b/client/ayon_core/plugins/publish/extract_thumbnail.py index e5108444f7..3626c5f381 100644 --- a/client/ayon_core/plugins/publish/extract_thumbnail.py +++ b/client/ayon_core/plugins/publish/extract_thumbnail.py @@ -497,7 +497,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # For very short videos, just use the first frame # Calculate seek position safely - seek_position = 0 + seek_position = 0.0 # Only use timestamp calculation for videos longer than 0.1 seconds if duration > 0.1: seek_position = duration * self.duration_split @@ -539,36 +539,31 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): self.log.debug( "Thumbnail created: {}".format(output_thumb_file_path)) return output_thumb_file_path - else: - self.log.warning( - "Output file was not created or is empty: {}".format( - output_thumb_file_path)) - - # Fallback to extracting the first frame without seeking - if "-ss" in cmd_args: - self.log.debug("Trying fallback without seeking") - # Remove -ss and its value - ss_index = cmd_args.index("-ss") - cmd_args.pop(ss_index) # Remove -ss - cmd_args.pop(ss_index) # Remove the timestamp value - - # Create new command and try again - cmd = get_ffmpeg_tool_args("ffmpeg", *cmd_args) - self.log.debug("Fallback command: {}".format( - " ".join(cmd))) - run_subprocess(cmd, logger=self.log) - - if ( - os.path.exists(output_thumb_file_path) - and os.path.getsize(output_thumb_file_path) > 0 - ): - self.log.debug( - "Fallback thumbnail created: {}".format( - output_thumb_file_path) - ) - return output_thumb_file_path + self.log.warning("Output file was not created or is empty") + # Try to create thumbnail without offset + # - skip if offset did not happen + if "-ss" not in cmd_args: return None + + self.log.debug("Trying fallback without offset") + # Remove -ss and its value + ss_index = cmd_args.index("-ss") + cmd_args.pop(ss_index) # Remove -ss + cmd_args.pop(ss_index) # Remove the timestamp value + + # Create new command and try again + cmd = get_ffmpeg_tool_args("ffmpeg", *cmd_args) + self.log.debug("Fallback command: {}".format(" ".join(cmd))) + run_subprocess(cmd, logger=self.log) + + if ( + os.path.exists(output_thumb_file_path) + and os.path.getsize(output_thumb_file_path) > 0 + ): + self.log.debug(f"Fallback thumbnail created") + return output_thumb_file_path + return None except RuntimeError as error: self.log.warning( "Failed intermediate thumb source using ffmpeg: {}".format( From fe3995f07de627e9866e913b1054290c10d24847 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 12 May 2025 13:56:43 +0200 Subject: [PATCH 137/153] Applied suggestions from @iLLicit Simplifies the ffmpeg command construction by moving the seek position argument to the beginning of the command list if a seek position is specified, leading to a clearer and more maintainable structure. This also ensures that the output path is always the last argument passed to ffmpeg. --- .../plugins/publish/extract_thumbnail.py | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_thumbnail.py b/client/ayon_core/plugins/publish/extract_thumbnail.py index 3626c5f381..9f58be7d94 100644 --- a/client/ayon_core/plugins/publish/extract_thumbnail.py +++ b/client/ayon_core/plugins/publish/extract_thumbnail.py @@ -503,20 +503,19 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): seek_position = duration * self.duration_split # Build command args - cmd_args = [ - "-y", + cmd_args = [] + if seek_position > 0.0: + cmd_args.extend(["--ss", str(seek_position)]) + + # Add generic ffmpeg commands + cmd_args.extend([ "-i", video_file_path, "-analyzeduration", max_int, "-probesize", max_int, - ] - - # Only add -ss if we're seeking to a specific position - if seek_position > 0: - cmd_args.insert(1, "-ss") - cmd_args.insert(2, str(seek_position)) - - # Ensure we extract exactly one frame - cmd_args.extend(["-frames:v", "1"]) + "-y", + "-frames:v", "1", + output_thumb_file_path + ]) # add output file path cmd_args.append(output_thumb_file_path) From f0be8cd87704fb82ff946b50dde8418a88a95f5d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 12 May 2025 13:56:43 +0200 Subject: [PATCH 138/153] Applied suggestions from @iLLiCiTiT Simplifies the ffmpeg command construction by moving the seek position argument to the beginning of the command list if a seek position is specified, leading to a clearer and more maintainable structure. This also ensures that the output path is always the last argument passed to ffmpeg. --- .../plugins/publish/extract_thumbnail.py | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_thumbnail.py b/client/ayon_core/plugins/publish/extract_thumbnail.py index 3626c5f381..9f58be7d94 100644 --- a/client/ayon_core/plugins/publish/extract_thumbnail.py +++ b/client/ayon_core/plugins/publish/extract_thumbnail.py @@ -503,20 +503,19 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): seek_position = duration * self.duration_split # Build command args - cmd_args = [ - "-y", + cmd_args = [] + if seek_position > 0.0: + cmd_args.extend(["--ss", str(seek_position)]) + + # Add generic ffmpeg commands + cmd_args.extend([ "-i", video_file_path, "-analyzeduration", max_int, "-probesize", max_int, - ] - - # Only add -ss if we're seeking to a specific position - if seek_position > 0: - cmd_args.insert(1, "-ss") - cmd_args.insert(2, str(seek_position)) - - # Ensure we extract exactly one frame - cmd_args.extend(["-frames:v", "1"]) + "-y", + "-frames:v", "1", + output_thumb_file_path + ]) # add output file path cmd_args.append(output_thumb_file_path) From 9791fda4f6a4e8b898227700f9df4a119845c97b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 12 May 2025 14:02:45 +0200 Subject: [PATCH 139/153] Applied suggestions from @iLLiCiTiT Ensures that any generated thumbnail files that are empty are removed to prevent issues with subsequent processing or storage. --- client/ayon_core/plugins/publish/extract_thumbnail.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/client/ayon_core/plugins/publish/extract_thumbnail.py b/client/ayon_core/plugins/publish/extract_thumbnail.py index 9f58be7d94..4b93b6514e 100644 --- a/client/ayon_core/plugins/publish/extract_thumbnail.py +++ b/client/ayon_core/plugins/publish/extract_thumbnail.py @@ -569,6 +569,13 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): error) ) return None + finally: + # Remove output file if is empty + if ( + os.path.exists(output_thumb_file_path) + and os.path.getsize(output_thumb_file_path) == 0 + ): + os.remove(output_thumb_file_path) def _get_resolution_arg( self, 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 140/153] 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 141/153] 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 142/153] 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 From d01afd073a7501152bf0a94dd81b997bde5defde Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 13 May 2025 16:06:53 +0200 Subject: [PATCH 143/153] Simplifies debug log message Removes unnecessary f-string formatting in a debug log message within the thumbnail extraction process. This simplifies the log output and improves readability. --- client/ayon_core/plugins/publish/extract_thumbnail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/extract_thumbnail.py b/client/ayon_core/plugins/publish/extract_thumbnail.py index 7f698d3b2b..2b08d663de 100644 --- a/client/ayon_core/plugins/publish/extract_thumbnail.py +++ b/client/ayon_core/plugins/publish/extract_thumbnail.py @@ -580,7 +580,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): os.path.exists(output_thumb_file_path) and os.path.getsize(output_thumb_file_path) > 0 ): - self.log.debug(f"Fallback thumbnail created") + self.log.debug("Fallback thumbnail created") return output_thumb_file_path return None except RuntimeError as error: From f5f145287222802155088755269866e6bb42ce11 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 14 May 2025 10:20:21 +0200 Subject: [PATCH 144/153] Fixes ffmpeg seek argument Corrects the ffmpeg command-line argument for specifying the seek position. It changes from '--ss' to '-ss', which is the correct flag. --- 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 2b08d663de..69bb9007f9 100644 --- a/client/ayon_core/plugins/publish/extract_thumbnail.py +++ b/client/ayon_core/plugins/publish/extract_thumbnail.py @@ -525,7 +525,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): # Build command args cmd_args = [] if seek_position > 0.0: - cmd_args.extend(["--ss", str(seek_position)]) + cmd_args.extend(["-ss", str(seek_position)]) # Add generic ffmpeg commands cmd_args.extend([ @@ -537,9 +537,6 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): output_thumb_file_path ]) - # add output file path - cmd_args.append(output_thumb_file_path) - # create ffmpeg command cmd = get_ffmpeg_tool_args( "ffmpeg", From 9b8229fa811777c1794063de63ea7be8866b57ba Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 14 May 2025 16:29:49 +0200 Subject: [PATCH 145/153] Fixes: Uses correct fallback data key The code now uses the correct key ("fallback_type") to access the fallback type from the configuration data, ensuring the correct config path is retrieved when no product is found. --- client/ayon_core/pipeline/colorspace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/colorspace.py b/client/ayon_core/pipeline/colorspace.py index 8c4f97ab1c..4b1d14d570 100644 --- a/client/ayon_core/pipeline/colorspace.py +++ b/client/ayon_core/pipeline/colorspace.py @@ -834,7 +834,7 @@ def _get_global_config_data( if not product_entities_by_name: # in case no product was found we need to use fallback - fallback_type = fallback_data["type"] + fallback_type = fallback_data["fallback_type"] return _get_config_path_from_profile_data( fallback_data, fallback_type, template_data ) From 7a5e7a96f1aef43463c86957f93ecfee8319d602 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 16 May 2025 17:50:26 +0200 Subject: [PATCH 146/153] Fixes frame string formatting for transcoding Ensures the frame string used for transcoding includes the correct padding, based on the collection's padding attribute. This resolves issues where the output file sequence name was not correctly formatted, leading to transcoding failures. --- client/ayon_core/plugins/publish/extract_color_transcode.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/extract_color_transcode.py b/client/ayon_core/plugins/publish/extract_color_transcode.py index 6cf30857a4..1e86b91484 100644 --- a/client/ayon_core/plugins/publish/extract_color_transcode.py +++ b/client/ayon_core/plugins/publish/extract_color_transcode.py @@ -283,7 +283,11 @@ class ExtractOIIOTranscode(publish.Extractor): if collection.holes().indexes: return files_to_convert - frame_str = "{}-{}#".format(frames[0], frames[-1]) + # Get the padding from the collection + # This is the number of digits used in the frame numbers + padding = collection.padding + + frame_str = "{}-{}%0{}d".format(frames[0], frames[-1], padding) file_name = "{}{}{}".format(collection.head, frame_str, collection.tail) From 0e49ada807336dacdb88c85df5d83c3e695e3afb Mon Sep 17 00:00:00 2001 From: Ynbot Date: Wed, 21 May 2025 07:25:28 +0000 Subject: [PATCH 147/153] [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 4fd7bde336..533862fa9a 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+dev" +__version__ = "1.3.0" diff --git a/package.py b/package.py index 601d703857..7406e70aec 100644 --- a/package.py +++ b/package.py @@ -1,6 +1,6 @@ name = "core" title = "Core" -version = "1.2.0+dev" +version = "1.3.0" client_dir = "ayon_core" diff --git a/pyproject.toml b/pyproject.toml index c7e2bb5000..fda22073ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ [tool.poetry] name = "ayon-core" -version = "1.2.0+dev" +version = "1.3.0" description = "" authors = ["Ynput Team "] readme = "README.md" From 3df127ec3b57d2619fc66db79e6f5f035c815a76 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Wed, 21 May 2025 07:26:06 +0000 Subject: [PATCH 148/153] [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 533862fa9a..64842b5976 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.3.0" +__version__ = "1.3.0+dev" diff --git a/package.py b/package.py index 7406e70aec..32fedd859b 100644 --- a/package.py +++ b/package.py @@ -1,6 +1,6 @@ name = "core" title = "Core" -version = "1.3.0" +version = "1.3.0+dev" client_dir = "ayon_core" diff --git a/pyproject.toml b/pyproject.toml index fda22073ff..4034d6c0c6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ [tool.poetry] name = "ayon-core" -version = "1.3.0" +version = "1.3.0+dev" description = "" authors = ["Ynput Team "] readme = "README.md" From c020f821dca71138d48225d8cd32fa51bd8b1835 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 21 May 2025 07:27:00 +0000 Subject: [PATCH 149/153] 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 c1e18faf55..9dbe32b018 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.3.0 - 1.2.0 - 1.1.9 - 1.1.8 From 46f6198ba36c23448c16f7133982753e16b58a55 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Thu, 22 May 2025 12:31:33 +0000 Subject: [PATCH 150/153] [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 64842b5976..1b2f0defaa 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.3.0+dev" +__version__ = "1.3.1" diff --git a/package.py b/package.py index 32fedd859b..8161d63b1c 100644 --- a/package.py +++ b/package.py @@ -1,6 +1,6 @@ name = "core" title = "Core" -version = "1.3.0+dev" +version = "1.3.1" client_dir = "ayon_core" diff --git a/pyproject.toml b/pyproject.toml index 4034d6c0c6..79158e1010 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ [tool.poetry] name = "ayon-core" -version = "1.3.0+dev" +version = "1.3.1" description = "" authors = ["Ynput Team "] readme = "README.md" From 6379f6726271ce848bc21c130bf28e30f8cdb302 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Thu, 22 May 2025 12:32:10 +0000 Subject: [PATCH 151/153] [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 1b2f0defaa..9c43e80bf1 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.3.1" +__version__ = "1.3.1+dev" diff --git a/package.py b/package.py index 8161d63b1c..47e3b39083 100644 --- a/package.py +++ b/package.py @@ -1,6 +1,6 @@ name = "core" title = "Core" -version = "1.3.1" +version = "1.3.1+dev" client_dir = "ayon_core" diff --git a/pyproject.toml b/pyproject.toml index 79158e1010..f919a9589b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ [tool.poetry] name = "ayon-core" -version = "1.3.1" +version = "1.3.1+dev" description = "" authors = ["Ynput Team "] readme = "README.md" From 1fe653c8b7323a1e84d48e6dd5684543b8b7793c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 22 May 2025 12:33:06 +0000 Subject: [PATCH 152/153] 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 9dbe32b018..f71c6e2c29 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.3.1 - 1.3.0 - 1.2.0 - 1.1.9 From 3184b5a72d87e359b7e50e55d5a6b184e6c25a50 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 26 May 2025 14:58:05 +0200 Subject: [PATCH 153/153] use new 'get_project_root_overrides_by_site_id' --- client/ayon_core/pipeline/anatomy/anatomy.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/pipeline/anatomy/anatomy.py b/client/ayon_core/pipeline/anatomy/anatomy.py index 98bbaa9bdc..9885e383b7 100644 --- a/client/ayon_core/pipeline/anatomy/anatomy.py +++ b/client/ayon_core/pipeline/anatomy/anatomy.py @@ -462,8 +462,8 @@ class Anatomy(BaseAnatomy): Union[Dict[str, str], None]): Local root overrides. """ if not project_name: - return - return ayon_api.get_project_roots_for_site( + return None + return ayon_api.get_project_root_overrides_by_site_id( project_name, get_local_site_id() )