mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-26 13:52:15 +01:00
Merge branch 'develop' into feature/AY-5235_Delivery-Farm-Publishing
This commit is contained in:
commit
44dcbd776b
105 changed files with 9637 additions and 1589 deletions
12
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
12
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
|
@ -1,6 +1,6 @@
|
|||
name: Bug Report
|
||||
description: File a bug report
|
||||
title: 'Your issue title here'
|
||||
title: Your issue title here
|
||||
labels:
|
||||
- 'type: bug'
|
||||
body:
|
||||
|
|
@ -36,6 +36,16 @@ body:
|
|||
description: What version are you running? Look to AYON Tray
|
||||
options:
|
||||
- 1.0.0
|
||||
- 0.4.4
|
||||
- 0.4.3
|
||||
- 0.4.2
|
||||
- 0.4.1
|
||||
- 0.4.0
|
||||
- 0.3.2
|
||||
- 0.3.1
|
||||
- 0.3.0
|
||||
- 0.2.1
|
||||
- 0.2.0
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
|
|
|
|||
12
.github/workflows/release_trigger.yml
vendored
Normal file
12
.github/workflows/release_trigger.yml
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
name: 🚀 Release Trigger
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
call-release-trigger:
|
||||
uses: ynput/ops-repo-automation/.github/workflows/release_trigger.yml@main
|
||||
secrets:
|
||||
token: ${{ secrets.YNPUT_BOT_TOKEN }}
|
||||
email: ${{ secrets.CI_EMAIL }}
|
||||
user: ${{ secrets.CI_USER }}
|
||||
|
|
@ -9,10 +9,6 @@ AYON_CORE_ROOT = os.path.dirname(os.path.abspath(__file__))
|
|||
# -------------------------
|
||||
PACKAGE_DIR = AYON_CORE_ROOT
|
||||
PLUGINS_DIR = os.path.join(AYON_CORE_ROOT, "plugins")
|
||||
AYON_SERVER_ENABLED = True
|
||||
|
||||
# Indicate if AYON entities should be used instead of OpenPype entities
|
||||
USE_AYON_ENTITIES = True
|
||||
# -------------------------
|
||||
|
||||
|
||||
|
|
@ -23,6 +19,4 @@ __all__ = (
|
|||
"AYON_CORE_ROOT",
|
||||
"PACKAGE_DIR",
|
||||
"PLUGINS_DIR",
|
||||
"AYON_SERVER_ENABLED",
|
||||
"USE_AYON_ENTITIES",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -36,9 +36,6 @@ IGNORED_FILENAMES = {
|
|||
# Files ignored on addons import from "./ayon_core/modules"
|
||||
IGNORED_DEFAULT_FILENAMES = {
|
||||
"__init__.py",
|
||||
"base.py",
|
||||
"interfaces.py",
|
||||
"click_wrap.py",
|
||||
}
|
||||
|
||||
# When addon was moved from ayon-core codebase
|
||||
|
|
@ -124,77 +121,10 @@ class ProcessContext:
|
|||
print(f"Unknown keys in ProcessContext: {unknown_keys}")
|
||||
|
||||
|
||||
# Inherit from `object` for Python 2 hosts
|
||||
class _ModuleClass(object):
|
||||
"""Fake module class for storing AYON addons.
|
||||
|
||||
Object of this class can be stored to `sys.modules` and used for storing
|
||||
dynamically imported modules.
|
||||
"""
|
||||
|
||||
def __init__(self, name):
|
||||
# Call setattr on super class
|
||||
super(_ModuleClass, self).__setattr__("name", name)
|
||||
super(_ModuleClass, self).__setattr__("__name__", name)
|
||||
|
||||
# Where modules and interfaces are stored
|
||||
super(_ModuleClass, self).__setattr__("__attributes__", dict())
|
||||
super(_ModuleClass, self).__setattr__("__defaults__", set())
|
||||
|
||||
super(_ModuleClass, self).__setattr__("_log", None)
|
||||
|
||||
def __getattr__(self, attr_name):
|
||||
if attr_name not in self.__attributes__:
|
||||
if attr_name in ("__path__", "__file__"):
|
||||
return None
|
||||
raise AttributeError("'{}' has not attribute '{}'".format(
|
||||
self.name, attr_name
|
||||
))
|
||||
return self.__attributes__[attr_name]
|
||||
|
||||
def __iter__(self):
|
||||
for module in self.values():
|
||||
yield module
|
||||
|
||||
def __setattr__(self, attr_name, value):
|
||||
if attr_name in self.__attributes__:
|
||||
self.log.warning(
|
||||
"Duplicated name \"{}\" in {}. Overriding.".format(
|
||||
attr_name, self.name
|
||||
)
|
||||
)
|
||||
self.__attributes__[attr_name] = value
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.__setattr__(key, value)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return getattr(self, key)
|
||||
|
||||
@property
|
||||
def log(self):
|
||||
if self._log is None:
|
||||
super(_ModuleClass, self).__setattr__(
|
||||
"_log", Logger.get_logger(self.name)
|
||||
)
|
||||
return self._log
|
||||
|
||||
def get(self, key, default=None):
|
||||
return self.__attributes__.get(key, default)
|
||||
|
||||
def keys(self):
|
||||
return self.__attributes__.keys()
|
||||
|
||||
def values(self):
|
||||
return self.__attributes__.values()
|
||||
|
||||
def items(self):
|
||||
return self.__attributes__.items()
|
||||
|
||||
|
||||
class _LoadCache:
|
||||
addons_lock = threading.Lock()
|
||||
addons_loaded = False
|
||||
addon_modules = []
|
||||
|
||||
|
||||
def load_addons(force=False):
|
||||
|
|
@ -308,7 +238,7 @@ def _handle_moved_addons(addon_name, milestone_version, log):
|
|||
return addon_dir
|
||||
|
||||
|
||||
def _load_ayon_addons(openpype_modules, modules_key, log):
|
||||
def _load_ayon_addons(log):
|
||||
"""Load AYON addons based on information from server.
|
||||
|
||||
This function should not trigger downloading of any addons but only use
|
||||
|
|
@ -316,23 +246,14 @@ def _load_ayon_addons(openpype_modules, modules_key, log):
|
|||
development).
|
||||
|
||||
Args:
|
||||
openpype_modules (_ModuleClass): Module object where modules are
|
||||
stored.
|
||||
modules_key (str): Key under which will be modules imported in
|
||||
`sys.modules`.
|
||||
log (logging.Logger): Logger object.
|
||||
|
||||
Returns:
|
||||
List[str]: List of v3 addons to skip to load because v4 alternative is
|
||||
imported.
|
||||
"""
|
||||
|
||||
addons_to_skip_in_core = []
|
||||
|
||||
all_addon_modules = []
|
||||
bundle_info = _get_ayon_bundle_data()
|
||||
addons_info = _get_ayon_addons_information(bundle_info)
|
||||
if not addons_info:
|
||||
return addons_to_skip_in_core
|
||||
return all_addon_modules
|
||||
|
||||
addons_dir = os.environ.get("AYON_ADDONS_DIR")
|
||||
if not addons_dir:
|
||||
|
|
@ -355,7 +276,7 @@ def _load_ayon_addons(openpype_modules, modules_key, log):
|
|||
addon_version = addon_info["version"]
|
||||
|
||||
# core addon does not have any addon object
|
||||
if addon_name in ("openpype", "core"):
|
||||
if addon_name == "core":
|
||||
continue
|
||||
|
||||
dev_addon_info = dev_addons_info.get(addon_name, {})
|
||||
|
|
@ -394,7 +315,7 @@ def _load_ayon_addons(openpype_modules, modules_key, log):
|
|||
continue
|
||||
|
||||
sys.path.insert(0, addon_dir)
|
||||
imported_modules = []
|
||||
addon_modules = []
|
||||
for name in os.listdir(addon_dir):
|
||||
# Ignore of files is implemented to be able to run code from code
|
||||
# where usually is more files than just the addon
|
||||
|
|
@ -421,7 +342,7 @@ def _load_ayon_addons(openpype_modules, modules_key, log):
|
|||
inspect.isclass(attr)
|
||||
and issubclass(attr, AYONAddon)
|
||||
):
|
||||
imported_modules.append(mod)
|
||||
addon_modules.append(mod)
|
||||
break
|
||||
|
||||
except BaseException:
|
||||
|
|
@ -430,50 +351,37 @@ def _load_ayon_addons(openpype_modules, modules_key, log):
|
|||
exc_info=True
|
||||
)
|
||||
|
||||
if not imported_modules:
|
||||
if not addon_modules:
|
||||
log.warning("Addon {} {} has no content to import".format(
|
||||
addon_name, addon_version
|
||||
))
|
||||
continue
|
||||
|
||||
if len(imported_modules) > 1:
|
||||
if len(addon_modules) > 1:
|
||||
log.warning((
|
||||
"Skipping addon '{}'."
|
||||
" Multiple modules were found ({}) in dir {}."
|
||||
"Multiple modules ({}) were found in addon '{}' in dir {}."
|
||||
).format(
|
||||
", ".join([m.__name__ for m in addon_modules]),
|
||||
addon_name,
|
||||
", ".join([m.__name__ for m in imported_modules]),
|
||||
addon_dir,
|
||||
))
|
||||
continue
|
||||
all_addon_modules.extend(addon_modules)
|
||||
|
||||
mod = imported_modules[0]
|
||||
addon_alias = getattr(mod, "V3_ALIAS", None)
|
||||
if not addon_alias:
|
||||
addon_alias = addon_name
|
||||
addons_to_skip_in_core.append(addon_alias)
|
||||
new_import_str = "{}.{}".format(modules_key, addon_alias)
|
||||
|
||||
sys.modules[new_import_str] = mod
|
||||
setattr(openpype_modules, addon_alias, mod)
|
||||
|
||||
return addons_to_skip_in_core
|
||||
return all_addon_modules
|
||||
|
||||
|
||||
def _load_addons_in_core(
|
||||
ignore_addon_names, openpype_modules, modules_key, log
|
||||
):
|
||||
def _load_addons_in_core(log):
|
||||
# Add current directory at first place
|
||||
# - has small differences in import logic
|
||||
addon_modules = []
|
||||
modules_dir = os.path.join(AYON_CORE_ROOT, "modules")
|
||||
if not os.path.exists(modules_dir):
|
||||
log.warning(
|
||||
f"Could not find path when loading AYON addons \"{modules_dir}\""
|
||||
)
|
||||
return
|
||||
return addon_modules
|
||||
|
||||
ignored_filenames = IGNORED_FILENAMES | IGNORED_DEFAULT_FILENAMES
|
||||
|
||||
for filename in os.listdir(modules_dir):
|
||||
# Ignore filenames
|
||||
if filename in ignored_filenames:
|
||||
|
|
@ -482,9 +390,6 @@ def _load_addons_in_core(
|
|||
fullpath = os.path.join(modules_dir, filename)
|
||||
basename, ext = os.path.splitext(filename)
|
||||
|
||||
if basename in ignore_addon_names:
|
||||
continue
|
||||
|
||||
# Validations
|
||||
if os.path.isdir(fullpath):
|
||||
# Check existence of init file
|
||||
|
|
@ -503,69 +408,43 @@ def _load_addons_in_core(
|
|||
# - check manifest and content of manifest
|
||||
try:
|
||||
# Don't import dynamically current directory modules
|
||||
new_import_str = f"{modules_key}.{basename}"
|
||||
|
||||
import_str = f"ayon_core.modules.{basename}"
|
||||
default_module = __import__(import_str, fromlist=("", ))
|
||||
sys.modules[new_import_str] = default_module
|
||||
setattr(openpype_modules, basename, default_module)
|
||||
addon_modules.append(default_module)
|
||||
|
||||
except Exception:
|
||||
log.error(
|
||||
f"Failed to import in-core addon '{basename}'.",
|
||||
exc_info=True
|
||||
)
|
||||
return addon_modules
|
||||
|
||||
|
||||
def _load_addons():
|
||||
# Key under which will be modules imported in `sys.modules`
|
||||
modules_key = "openpype_modules"
|
||||
|
||||
# Change `sys.modules`
|
||||
sys.modules[modules_key] = openpype_modules = _ModuleClass(modules_key)
|
||||
|
||||
log = Logger.get_logger("AddonsLoader")
|
||||
|
||||
ignore_addon_names = _load_ayon_addons(
|
||||
openpype_modules, modules_key, log
|
||||
)
|
||||
_load_addons_in_core(
|
||||
ignore_addon_names, openpype_modules, modules_key, log
|
||||
)
|
||||
addon_modules = _load_ayon_addons(log)
|
||||
# All addon in 'modules' folder are tray actions and should be moved
|
||||
# to tray tool.
|
||||
# TODO remove
|
||||
addon_modules.extend(_load_addons_in_core(log))
|
||||
|
||||
|
||||
_MARKING_ATTR = "_marking"
|
||||
def mark_func(func):
|
||||
"""Mark function to be used in report.
|
||||
|
||||
Args:
|
||||
func (Callable): Function to mark.
|
||||
|
||||
Returns:
|
||||
Callable: Marked function.
|
||||
"""
|
||||
|
||||
setattr(func, _MARKING_ATTR, True)
|
||||
return func
|
||||
|
||||
|
||||
def is_func_marked(func):
|
||||
return getattr(func, _MARKING_ATTR, False)
|
||||
# Store modules to local cache
|
||||
_LoadCache.addon_modules = addon_modules
|
||||
|
||||
|
||||
class AYONAddon(ABC):
|
||||
"""Base class of AYON addon.
|
||||
|
||||
Attributes:
|
||||
id (UUID): Addon object id.
|
||||
enabled (bool): Is addon enabled.
|
||||
name (str): Addon name.
|
||||
|
||||
Args:
|
||||
manager (AddonsManager): Manager object who discovered addon.
|
||||
settings (dict[str, Any]): AYON settings.
|
||||
"""
|
||||
|
||||
"""
|
||||
enabled = True
|
||||
_id = None
|
||||
|
||||
|
|
@ -585,8 +464,8 @@ class AYONAddon(ABC):
|
|||
|
||||
Returns:
|
||||
str: Object id.
|
||||
"""
|
||||
|
||||
"""
|
||||
if self._id is None:
|
||||
self._id = uuid4()
|
||||
return self._id
|
||||
|
|
@ -598,8 +477,8 @@ class AYONAddon(ABC):
|
|||
|
||||
Returns:
|
||||
str: Addon name.
|
||||
"""
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
@property
|
||||
|
|
@ -630,16 +509,16 @@ class AYONAddon(ABC):
|
|||
|
||||
Args:
|
||||
settings (dict[str, Any]): Settings.
|
||||
"""
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
@mark_func
|
||||
def connect_with_addons(self, enabled_addons):
|
||||
"""Connect with other enabled addons.
|
||||
|
||||
Args:
|
||||
enabled_addons (list[AYONAddon]): Addons that are enabled.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
|
|
@ -673,8 +552,8 @@ class AYONAddon(ABC):
|
|||
|
||||
Returns:
|
||||
dict[str, str]: Environment variables.
|
||||
"""
|
||||
|
||||
"""
|
||||
return {}
|
||||
|
||||
def modify_application_launch_arguments(self, application, env):
|
||||
|
|
@ -686,8 +565,8 @@ class AYONAddon(ABC):
|
|||
Args:
|
||||
application (Application): Application that is launched.
|
||||
env (dict[str, str]): Current environment variables.
|
||||
"""
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
def on_host_install(self, host, host_name, project_name):
|
||||
|
|
@ -706,8 +585,8 @@ class AYONAddon(ABC):
|
|||
host_name (str): Name of host.
|
||||
project_name (str): Project name which is main part of host
|
||||
context.
|
||||
"""
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
def cli(self, addon_click_group):
|
||||
|
|
@ -734,31 +613,11 @@ class AYONAddon(ABC):
|
|||
Args:
|
||||
addon_click_group (click.Group): Group to which can be added
|
||||
commands.
|
||||
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class OpenPypeModule(AYONAddon):
|
||||
"""Base class of OpenPype module.
|
||||
|
||||
Deprecated:
|
||||
Use `AYONAddon` instead.
|
||||
|
||||
Args:
|
||||
manager (AddonsManager): Manager object who discovered addon.
|
||||
settings (dict[str, Any]): Module settings (OpenPype settings).
|
||||
"""
|
||||
|
||||
# Disable by default
|
||||
enabled = False
|
||||
|
||||
|
||||
class OpenPypeAddOn(OpenPypeModule):
|
||||
# Enable Addon by default
|
||||
enabled = True
|
||||
|
||||
|
||||
class _AddonReportInfo:
|
||||
def __init__(
|
||||
self, class_name, name, version, report_value_by_label
|
||||
|
|
@ -790,8 +649,8 @@ class AddonsManager:
|
|||
settings (Optional[dict[str, Any]]): AYON studio settings.
|
||||
initialize (Optional[bool]): Initialize addons on init.
|
||||
True by default.
|
||||
"""
|
||||
|
||||
"""
|
||||
# Helper attributes for report
|
||||
_report_total_key = "Total"
|
||||
_log = None
|
||||
|
|
@ -827,8 +686,8 @@ class AddonsManager:
|
|||
|
||||
Returns:
|
||||
Union[AYONAddon, Any]: Addon found by name or `default`.
|
||||
"""
|
||||
|
||||
"""
|
||||
return self._addons_by_name.get(addon_name, default)
|
||||
|
||||
@property
|
||||
|
|
@ -855,8 +714,8 @@ class AddonsManager:
|
|||
|
||||
Returns:
|
||||
Union[AYONAddon, None]: Enabled addon found by name or None.
|
||||
"""
|
||||
|
||||
"""
|
||||
addon = self.get(addon_name)
|
||||
if addon is not None and addon.enabled:
|
||||
return addon
|
||||
|
|
@ -867,8 +726,8 @@ class AddonsManager:
|
|||
|
||||
Returns:
|
||||
list[AYONAddon]: Initialized and enabled addons.
|
||||
"""
|
||||
|
||||
"""
|
||||
return [
|
||||
addon
|
||||
for addon in self._addons
|
||||
|
|
@ -880,8 +739,6 @@ class AddonsManager:
|
|||
# Make sure modules are loaded
|
||||
load_addons()
|
||||
|
||||
import openpype_modules
|
||||
|
||||
self.log.debug("*** AYON addons initialization.")
|
||||
|
||||
# Prepare settings for addons
|
||||
|
|
@ -889,14 +746,12 @@ class AddonsManager:
|
|||
if settings is None:
|
||||
settings = get_studio_settings()
|
||||
|
||||
modules_settings = {}
|
||||
|
||||
report = {}
|
||||
time_start = time.time()
|
||||
prev_start_time = time_start
|
||||
|
||||
addon_classes = []
|
||||
for module in openpype_modules:
|
||||
for module in _LoadCache.addon_modules:
|
||||
# Go through globals in `ayon_core.modules`
|
||||
for name in dir(module):
|
||||
modules_item = getattr(module, name, None)
|
||||
|
|
@ -905,8 +760,6 @@ class AddonsManager:
|
|||
if (
|
||||
not inspect.isclass(modules_item)
|
||||
or modules_item is AYONAddon
|
||||
or modules_item is OpenPypeModule
|
||||
or modules_item is OpenPypeAddOn
|
||||
or not issubclass(modules_item, AYONAddon)
|
||||
):
|
||||
continue
|
||||
|
|
@ -932,33 +785,14 @@ class AddonsManager:
|
|||
|
||||
addon_classes.append(modules_item)
|
||||
|
||||
aliased_names = []
|
||||
for addon_cls in addon_classes:
|
||||
name = addon_cls.__name__
|
||||
if issubclass(addon_cls, OpenPypeModule):
|
||||
# TODO change to warning
|
||||
self.log.debug((
|
||||
"Addon '{}' is inherited from 'OpenPypeModule'."
|
||||
" Please use 'AYONAddon'."
|
||||
).format(name))
|
||||
|
||||
try:
|
||||
# Try initialize module
|
||||
if issubclass(addon_cls, OpenPypeModule):
|
||||
addon = addon_cls(self, modules_settings)
|
||||
else:
|
||||
addon = addon_cls(self, settings)
|
||||
addon = addon_cls(self, settings)
|
||||
# Store initialized object
|
||||
self._addons.append(addon)
|
||||
self._addons_by_id[addon.id] = addon
|
||||
self._addons_by_name[addon.name] = addon
|
||||
# NOTE This will be removed with release 1.0.0 of ayon-core
|
||||
# please use carefully.
|
||||
# Gives option to use alias name for addon for cases when
|
||||
# name in OpenPype was not the same as in AYON.
|
||||
name_alias = getattr(addon, "openpype_alias", None)
|
||||
if name_alias:
|
||||
aliased_names.append((name_alias, addon))
|
||||
|
||||
now = time.time()
|
||||
report[addon.__class__.__name__] = now - prev_start_time
|
||||
|
|
@ -977,17 +811,6 @@ class AddonsManager:
|
|||
f"[{enabled_str}] {addon.name} ({addon.version})"
|
||||
)
|
||||
|
||||
for item in aliased_names:
|
||||
name_alias, addon = item
|
||||
if name_alias not in self._addons_by_name:
|
||||
self._addons_by_name[name_alias] = addon
|
||||
continue
|
||||
self.log.warning(
|
||||
"Alias name '{}' of addon '{}' is already assigned.".format(
|
||||
name_alias, addon.name
|
||||
)
|
||||
)
|
||||
|
||||
if self._report is not None:
|
||||
report[self._report_total_key] = time.time() - time_start
|
||||
self._report["Initialization"] = report
|
||||
|
|
@ -1004,16 +827,7 @@ class AddonsManager:
|
|||
self.log.debug("Has {} enabled addons.".format(len(enabled_addons)))
|
||||
for addon in enabled_addons:
|
||||
try:
|
||||
if not is_func_marked(addon.connect_with_addons):
|
||||
addon.connect_with_addons(enabled_addons)
|
||||
|
||||
elif hasattr(addon, "connect_with_modules"):
|
||||
self.log.warning((
|
||||
"DEPRECATION WARNING: Addon '{}' still uses"
|
||||
" 'connect_with_modules' method. Please switch to use"
|
||||
" 'connect_with_addons' method."
|
||||
).format(addon.name))
|
||||
addon.connect_with_modules(enabled_addons)
|
||||
addon.connect_with_addons(enabled_addons)
|
||||
|
||||
except Exception:
|
||||
self.log.error(
|
||||
|
|
@ -1362,56 +1176,3 @@ class AddonsManager:
|
|||
# Join rows with newline char and add new line at the end
|
||||
output = "\n".join(formatted_rows) + "\n"
|
||||
print(output)
|
||||
|
||||
# DEPRECATED - Module compatibility
|
||||
@property
|
||||
def modules(self):
|
||||
self.log.warning(
|
||||
"DEPRECATION WARNING: Used deprecated property"
|
||||
" 'modules' please use 'addons' instead."
|
||||
)
|
||||
return self.addons
|
||||
|
||||
@property
|
||||
def modules_by_id(self):
|
||||
self.log.warning(
|
||||
"DEPRECATION WARNING: Used deprecated property"
|
||||
" 'modules_by_id' please use 'addons_by_id' instead."
|
||||
)
|
||||
return self.addons_by_id
|
||||
|
||||
@property
|
||||
def modules_by_name(self):
|
||||
self.log.warning(
|
||||
"DEPRECATION WARNING: Used deprecated property"
|
||||
" 'modules_by_name' please use 'addons_by_name' instead."
|
||||
)
|
||||
return self.addons_by_name
|
||||
|
||||
def get_enabled_module(self, *args, **kwargs):
|
||||
self.log.warning(
|
||||
"DEPRECATION WARNING: Used deprecated method"
|
||||
" 'get_enabled_module' please use 'get_enabled_addon' instead."
|
||||
)
|
||||
return self.get_enabled_addon(*args, **kwargs)
|
||||
|
||||
def initialize_modules(self):
|
||||
self.log.warning(
|
||||
"DEPRECATION WARNING: Used deprecated method"
|
||||
" 'initialize_modules' please use 'initialize_addons' instead."
|
||||
)
|
||||
self.initialize_addons()
|
||||
|
||||
def get_enabled_modules(self):
|
||||
self.log.warning(
|
||||
"DEPRECATION WARNING: Used deprecated method"
|
||||
" 'get_enabled_modules' please use 'get_enabled_addons' instead."
|
||||
)
|
||||
return self.get_enabled_addons()
|
||||
|
||||
def get_host_module(self, host_name):
|
||||
self.log.warning(
|
||||
"DEPRECATION WARNING: Used deprecated method"
|
||||
" 'get_host_module' please use 'get_host_addon' instead."
|
||||
)
|
||||
return self.get_host_addon(host_name)
|
||||
|
|
|
|||
|
|
@ -21,21 +21,7 @@ from ayon_core.lib import (
|
|||
|
||||
|
||||
|
||||
class AliasedGroup(click.Group):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._aliases = {}
|
||||
|
||||
def set_alias(self, src_name, dst_name):
|
||||
self._aliases[dst_name] = src_name
|
||||
|
||||
def get_command(self, ctx, cmd_name):
|
||||
if cmd_name in self._aliases:
|
||||
cmd_name = self._aliases[cmd_name]
|
||||
return super().get_command(ctx, cmd_name)
|
||||
|
||||
|
||||
@click.group(cls=AliasedGroup, invoke_without_command=True)
|
||||
@click.group(invoke_without_command=True)
|
||||
@click.pass_context
|
||||
@click.option("--use-staging", is_flag=True,
|
||||
expose_value=False, help="use staging variants")
|
||||
|
|
@ -86,10 +72,6 @@ def addon(ctx):
|
|||
pass
|
||||
|
||||
|
||||
# Add 'addon' as alias for module
|
||||
main_cli.set_alias("addon", "module")
|
||||
|
||||
|
||||
@main_cli.command()
|
||||
@click.pass_context
|
||||
@click.argument("output_json_path")
|
||||
|
|
|
|||
|
|
@ -94,4 +94,4 @@ class GlobalHostDataHook(PreLaunchHook):
|
|||
task_entity = get_task_by_name(
|
||||
project_name, folder_entity["id"], task_name
|
||||
)
|
||||
self.data["task_entity"] = task_entity
|
||||
self.data["task_entity"] = task_entity
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ class OCIOEnvHook(PreLaunchHook):
|
|||
"nuke",
|
||||
"hiero",
|
||||
"resolve",
|
||||
"openrv"
|
||||
"openrv",
|
||||
"cinema4d"
|
||||
}
|
||||
launch_types = set()
|
||||
|
||||
|
|
|
|||
|
|
@ -7,13 +7,10 @@ from .local_settings import (
|
|||
JSONSettingRegistry,
|
||||
AYONSecureRegistry,
|
||||
AYONSettingsRegistry,
|
||||
OpenPypeSecureRegistry,
|
||||
OpenPypeSettingsRegistry,
|
||||
get_launcher_local_dir,
|
||||
get_launcher_storage_dir,
|
||||
get_local_site_id,
|
||||
get_ayon_username,
|
||||
get_openpype_username,
|
||||
)
|
||||
from .ayon_connection import initialize_ayon_connection
|
||||
from .cache import (
|
||||
|
|
@ -59,13 +56,11 @@ from .env_tools import (
|
|||
from .terminal import Terminal
|
||||
from .execute import (
|
||||
get_ayon_launcher_args,
|
||||
get_openpype_execute_args,
|
||||
get_linux_launcher_args,
|
||||
execute,
|
||||
run_subprocess,
|
||||
run_detached_process,
|
||||
run_ayon_launcher_process,
|
||||
run_openpype_process,
|
||||
path_to_subprocess_arg,
|
||||
CREATE_NO_WINDOW
|
||||
)
|
||||
|
|
@ -145,13 +140,10 @@ __all__ = [
|
|||
"JSONSettingRegistry",
|
||||
"AYONSecureRegistry",
|
||||
"AYONSettingsRegistry",
|
||||
"OpenPypeSecureRegistry",
|
||||
"OpenPypeSettingsRegistry",
|
||||
"get_launcher_local_dir",
|
||||
"get_launcher_storage_dir",
|
||||
"get_local_site_id",
|
||||
"get_ayon_username",
|
||||
"get_openpype_username",
|
||||
|
||||
"initialize_ayon_connection",
|
||||
|
||||
|
|
@ -162,13 +154,11 @@ __all__ = [
|
|||
"register_event_callback",
|
||||
|
||||
"get_ayon_launcher_args",
|
||||
"get_openpype_execute_args",
|
||||
"get_linux_launcher_args",
|
||||
"execute",
|
||||
"run_subprocess",
|
||||
"run_detached_process",
|
||||
"run_ayon_launcher_process",
|
||||
"run_openpype_process",
|
||||
"path_to_subprocess_arg",
|
||||
"CREATE_NO_WINDOW",
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import collections
|
|||
import uuid
|
||||
import json
|
||||
import copy
|
||||
from abc import ABCMeta, abstractmethod, abstractproperty
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
import clique
|
||||
|
||||
|
|
@ -16,7 +16,7 @@ _attr_defs_by_type = {}
|
|||
def register_attr_def_class(cls):
|
||||
"""Register attribute definition.
|
||||
|
||||
Currently are registered definitions used to deserialize data to objects.
|
||||
Currently registered definitions are used to deserialize data to objects.
|
||||
|
||||
Attrs:
|
||||
cls (AbstractAttrDef): Non-abstract class to be registered with unique
|
||||
|
|
@ -60,7 +60,7 @@ def get_default_values(attribute_definitions):
|
|||
for which default values should be collected.
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: Default values for passet attribute definitions.
|
||||
Dict[str, Any]: Default values for passed attribute definitions.
|
||||
"""
|
||||
|
||||
output = {}
|
||||
|
|
@ -75,13 +75,13 @@ def get_default_values(attribute_definitions):
|
|||
|
||||
|
||||
class AbstractAttrDefMeta(ABCMeta):
|
||||
"""Metaclass to validate existence of 'key' attribute.
|
||||
"""Metaclass to validate the existence of 'key' attribute.
|
||||
|
||||
Each object of `AbstractAttrDef` mus have defined 'key' attribute.
|
||||
Each object of `AbstractAttrDef` must have defined 'key' attribute.
|
||||
"""
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
obj = super(AbstractAttrDefMeta, self).__call__(*args, **kwargs)
|
||||
def __call__(cls, *args, **kwargs):
|
||||
obj = super(AbstractAttrDefMeta, cls).__call__(*args, **kwargs)
|
||||
init_class = getattr(obj, "__init__class__", None)
|
||||
if init_class is not AbstractAttrDef:
|
||||
raise TypeError("{} super was not called in __init__.".format(
|
||||
|
|
@ -162,7 +162,8 @@ class AbstractAttrDef(metaclass=AbstractAttrDefMeta):
|
|||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
@abstractproperty
|
||||
@property
|
||||
@abstractmethod
|
||||
def type(self):
|
||||
"""Attribute definition type also used as identifier of class.
|
||||
|
||||
|
|
@ -215,7 +216,7 @@ class AbstractAttrDef(metaclass=AbstractAttrDefMeta):
|
|||
|
||||
|
||||
# -----------------------------------------
|
||||
# UI attribute definitoins won't hold value
|
||||
# UI attribute definitions won't hold value
|
||||
# -----------------------------------------
|
||||
|
||||
class UIDef(AbstractAttrDef):
|
||||
|
|
@ -245,7 +246,7 @@ class UILabelDef(UIDef):
|
|||
|
||||
|
||||
# ---------------------------------------
|
||||
# Attribute defintioins should hold value
|
||||
# Attribute definitions should hold value
|
||||
# ---------------------------------------
|
||||
|
||||
class UnknownDef(AbstractAttrDef):
|
||||
|
|
@ -311,7 +312,7 @@ class NumberDef(AbstractAttrDef):
|
|||
):
|
||||
minimum = 0 if minimum is None else minimum
|
||||
maximum = 999999 if maximum is None else maximum
|
||||
# Swap min/max when are passed in opposited order
|
||||
# Swap min/max when are passed in opposite order
|
||||
if minimum > maximum:
|
||||
maximum, minimum = minimum, maximum
|
||||
|
||||
|
|
@ -364,10 +365,10 @@ class NumberDef(AbstractAttrDef):
|
|||
class TextDef(AbstractAttrDef):
|
||||
"""Text definition.
|
||||
|
||||
Text can have multiline option so endline characters are allowed regex
|
||||
Text can have multiline option so end-line characters are allowed regex
|
||||
validation can be applied placeholder for UI purposes and default value.
|
||||
|
||||
Regex validation is not part of attribute implemntentation.
|
||||
Regex validation is not part of attribute implementation.
|
||||
|
||||
Args:
|
||||
multiline(bool): Text has single or multiline support.
|
||||
|
|
@ -949,7 +950,8 @@ def deserialize_attr_def(attr_def_data):
|
|||
"""Deserialize attribute definition from data.
|
||||
|
||||
Args:
|
||||
attr_def (Dict[str, Any]): Attribute definition data to deserialize.
|
||||
attr_def_data (Dict[str, Any]): Attribute definition data to
|
||||
deserialize.
|
||||
"""
|
||||
|
||||
attr_type = attr_def_data.pop("type")
|
||||
|
|
|
|||
|
|
@ -235,26 +235,6 @@ def run_ayon_launcher_process(*args, add_sys_paths=False, **kwargs):
|
|||
return run_subprocess(args, env=env, **kwargs)
|
||||
|
||||
|
||||
def run_openpype_process(*args, **kwargs):
|
||||
"""Execute AYON process with passed arguments and wait.
|
||||
|
||||
Wrapper for 'run_process' which prepends AYON executable arguments
|
||||
before passed arguments and define environments if are not passed.
|
||||
|
||||
Values from 'os.environ' are used for environments if are not passed.
|
||||
They are cleaned using 'clean_envs_for_ayon_process' function.
|
||||
|
||||
Example:
|
||||
>>> run_openpype_process("version")
|
||||
|
||||
Args:
|
||||
*args (tuple): AYON cli arguments.
|
||||
**kwargs (dict): Keyword arguments for subprocess.Popen.
|
||||
|
||||
"""
|
||||
return run_ayon_launcher_process(*args, **kwargs)
|
||||
|
||||
|
||||
def run_detached_process(args, **kwargs):
|
||||
"""Execute process with passed arguments as separated process.
|
||||
|
||||
|
|
@ -341,14 +321,12 @@ def path_to_subprocess_arg(path):
|
|||
|
||||
|
||||
def get_ayon_launcher_args(*args):
|
||||
"""Arguments to run ayon-launcher process.
|
||||
"""Arguments to run AYON launcher process.
|
||||
|
||||
Arguments for subprocess when need to spawn new pype process. Which may be
|
||||
needed when new python process for pype scripts must be executed in build
|
||||
pype.
|
||||
Arguments for subprocess when need to spawn new AYON launcher process.
|
||||
|
||||
Reasons:
|
||||
Ayon-launcher started from code has different executable set to
|
||||
AYON launcher started from code has different executable set to
|
||||
virtual env python and must have path to script as first argument
|
||||
which is not needed for built application.
|
||||
|
||||
|
|
@ -356,7 +334,8 @@ def get_ayon_launcher_args(*args):
|
|||
*args (str): Any arguments that will be added after executables.
|
||||
|
||||
Returns:
|
||||
list[str]: List of arguments to run ayon-launcher process.
|
||||
list[str]: List of arguments to run AYON launcher process.
|
||||
|
||||
"""
|
||||
executable = os.environ["AYON_EXECUTABLE"]
|
||||
launch_args = [executable]
|
||||
|
|
@ -414,21 +393,3 @@ def get_linux_launcher_args(*args):
|
|||
launch_args.extend(args)
|
||||
|
||||
return launch_args
|
||||
|
||||
|
||||
def get_openpype_execute_args(*args):
|
||||
"""Arguments to run pype command.
|
||||
|
||||
Arguments for subprocess when need to spawn new pype process. Which may be
|
||||
needed when new python process for pype scripts must be executed in build
|
||||
pype.
|
||||
|
||||
## Why is this needed?
|
||||
Pype executed from code has different executable set to virtual env python
|
||||
and must have path to script as first argument which is not needed for
|
||||
build pype.
|
||||
|
||||
It is possible to pass any arguments that will be added after pype
|
||||
executables.
|
||||
"""
|
||||
return get_ayon_launcher_args(*args)
|
||||
|
|
|
|||
|
|
@ -584,11 +584,3 @@ def get_ayon_username():
|
|||
|
||||
"""
|
||||
return ayon_api.get_user()["name"]
|
||||
|
||||
|
||||
def get_openpype_username():
|
||||
return get_ayon_username()
|
||||
|
||||
|
||||
OpenPypeSecureRegistry = AYONSecureRegistry
|
||||
OpenPypeSettingsRegistry = AYONSettingsRegistry
|
||||
|
|
|
|||
|
|
@ -503,7 +503,7 @@ class FormattingPart:
|
|||
# ensure key is properly formed [({})] properly closed.
|
||||
if not self.validate_key_is_matched(key):
|
||||
result.add_missing_key(key)
|
||||
result.add_output(self.template)
|
||||
result.add_output(self.template)
|
||||
return result
|
||||
|
||||
# check if key expects subdictionary keys (e.g. project[name])
|
||||
|
|
|
|||
|
|
@ -81,7 +81,10 @@ def collect_frames(files):
|
|||
dict: {'/folder/product_v001.0001.png': '0001', ....}
|
||||
"""
|
||||
|
||||
patterns = [clique.PATTERNS["frames"]]
|
||||
# clique.PATTERNS["frames"] supports only `.1001.exr` not `_1001.exr` so
|
||||
# we use a customized pattern.
|
||||
pattern = "[_.](?P<index>(?P<padding>0*)\\d+)\\.\\D+\\d?$"
|
||||
patterns = [pattern]
|
||||
collections, remainder = clique.assemble(
|
||||
files, minimum_items=1, patterns=patterns)
|
||||
|
||||
|
|
|
|||
|
|
@ -1152,9 +1152,7 @@ def convert_colorspace(
|
|||
input_arg, input_path,
|
||||
# Tell oiiotool which channels should be put to top stack
|
||||
# (and output)
|
||||
"--ch", channels_arg,
|
||||
# Use first subimage
|
||||
"--subimage", "0"
|
||||
"--ch", channels_arg
|
||||
])
|
||||
|
||||
if all([target_colorspace, view, display]):
|
||||
|
|
@ -1168,12 +1166,12 @@ def convert_colorspace(
|
|||
oiio_cmd.extend(additional_command_args)
|
||||
|
||||
if target_colorspace:
|
||||
oiio_cmd.extend(["--colorconvert",
|
||||
oiio_cmd.extend(["--colorconvert:subimages=0",
|
||||
source_colorspace,
|
||||
target_colorspace])
|
||||
if view and display:
|
||||
oiio_cmd.extend(["--iscolorspace", source_colorspace])
|
||||
oiio_cmd.extend(["--ociodisplay", display, view])
|
||||
oiio_cmd.extend(["--ociodisplay:subimages=0", display, view])
|
||||
|
||||
oiio_cmd.extend(["-o", output_path])
|
||||
|
||||
|
|
|
|||
|
|
@ -1,40 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from . import click_wrap
|
||||
from .interfaces import (
|
||||
IPluginPaths,
|
||||
ITrayAddon,
|
||||
ITrayModule,
|
||||
ITrayAction,
|
||||
ITrayService,
|
||||
IHostAddon,
|
||||
)
|
||||
|
||||
from .base import (
|
||||
AYONAddon,
|
||||
OpenPypeModule,
|
||||
OpenPypeAddOn,
|
||||
|
||||
load_modules,
|
||||
|
||||
ModulesManager,
|
||||
)
|
||||
|
||||
|
||||
__all__ = (
|
||||
"click_wrap",
|
||||
|
||||
"IPluginPaths",
|
||||
"ITrayAddon",
|
||||
"ITrayModule",
|
||||
"ITrayAction",
|
||||
"ITrayService",
|
||||
"IHostAddon",
|
||||
|
||||
"AYONAddon",
|
||||
"OpenPypeModule",
|
||||
"OpenPypeAddOn",
|
||||
|
||||
"load_modules",
|
||||
|
||||
"ModulesManager",
|
||||
)
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
# Backwards compatibility support
|
||||
# - TODO should be removed before release 1.0.0
|
||||
from ayon_core.addon import (
|
||||
AYONAddon,
|
||||
AddonsManager,
|
||||
load_addons,
|
||||
)
|
||||
from ayon_core.addon.base import (
|
||||
OpenPypeModule,
|
||||
OpenPypeAddOn,
|
||||
)
|
||||
|
||||
ModulesManager = AddonsManager
|
||||
load_modules = load_addons
|
||||
|
||||
|
||||
__all__ = (
|
||||
"AYONAddon",
|
||||
"AddonsManager",
|
||||
"load_addons",
|
||||
"OpenPypeModule",
|
||||
"OpenPypeAddOn",
|
||||
"ModulesManager",
|
||||
"load_modules",
|
||||
)
|
||||
|
|
@ -1 +0,0 @@
|
|||
from ayon_core.addon.click_wrap import *
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
from ayon_core.addon.interfaces import (
|
||||
IPluginPaths,
|
||||
ITrayAddon,
|
||||
ITrayAction,
|
||||
ITrayService,
|
||||
IHostAddon,
|
||||
)
|
||||
|
||||
ITrayModule = ITrayAddon
|
||||
ILaunchHookPaths = object
|
||||
|
||||
|
||||
__all__ = (
|
||||
"IPluginPaths",
|
||||
"ITrayAddon",
|
||||
"ITrayAction",
|
||||
"ITrayService",
|
||||
"IHostAddon",
|
||||
"ITrayModule",
|
||||
"ILaunchHookPaths",
|
||||
)
|
||||
|
|
@ -3,7 +3,6 @@ from .constants import (
|
|||
AVALON_INSTANCE_ID,
|
||||
AYON_CONTAINER_ID,
|
||||
AYON_INSTANCE_ID,
|
||||
HOST_WORKFILE_EXTENSIONS,
|
||||
)
|
||||
|
||||
from .anatomy import Anatomy
|
||||
|
|
@ -51,11 +50,11 @@ from .load import (
|
|||
)
|
||||
|
||||
from .publish import (
|
||||
KnownPublishError,
|
||||
PublishError,
|
||||
PublishValidationError,
|
||||
PublishXmlValidationError,
|
||||
KnownPublishError,
|
||||
AYONPyblishPluginMixin,
|
||||
OpenPypePyblishPluginMixin,
|
||||
OptionalPyblishPluginMixin,
|
||||
)
|
||||
|
||||
|
|
@ -77,7 +76,6 @@ from .actions import (
|
|||
|
||||
from .context_tools import (
|
||||
install_ayon_plugins,
|
||||
install_openpype_plugins,
|
||||
install_host,
|
||||
uninstall_host,
|
||||
is_installed,
|
||||
|
|
@ -115,7 +113,6 @@ __all__ = (
|
|||
"AVALON_INSTANCE_ID",
|
||||
"AYON_CONTAINER_ID",
|
||||
"AYON_INSTANCE_ID",
|
||||
"HOST_WORKFILE_EXTENSIONS",
|
||||
|
||||
# --- Anatomy ---
|
||||
"Anatomy",
|
||||
|
|
@ -164,11 +161,11 @@ __all__ = (
|
|||
"get_repres_contexts",
|
||||
|
||||
# --- Publish ---
|
||||
"KnownPublishError",
|
||||
"PublishError",
|
||||
"PublishValidationError",
|
||||
"PublishXmlValidationError",
|
||||
"KnownPublishError",
|
||||
"AYONPyblishPluginMixin",
|
||||
"OpenPypePyblishPluginMixin",
|
||||
"OptionalPyblishPluginMixin",
|
||||
|
||||
# --- Actions ---
|
||||
|
|
@ -187,7 +184,6 @@ __all__ = (
|
|||
|
||||
# --- Process context ---
|
||||
"install_ayon_plugins",
|
||||
"install_openpype_plugins",
|
||||
"install_host",
|
||||
"uninstall_host",
|
||||
"is_installed",
|
||||
|
|
|
|||
|
|
@ -699,6 +699,34 @@ def get_ocio_config_views(config_path):
|
|||
)
|
||||
|
||||
|
||||
def _get_config_path_from_profile_data(
|
||||
profile, profile_type, template_data
|
||||
):
|
||||
"""Get config path from profile data.
|
||||
|
||||
Args:
|
||||
profile (dict[str, Any]): Profile data.
|
||||
profile_type (str): Profile type.
|
||||
template_data (dict[str, Any]): Template data.
|
||||
|
||||
Returns:
|
||||
dict[str, str]: Config data with path and template.
|
||||
"""
|
||||
template = profile[profile_type]
|
||||
result = StringTemplate.format_strict_template(
|
||||
template, template_data
|
||||
)
|
||||
normalized_path = str(result.normalized())
|
||||
if not os.path.exists(normalized_path):
|
||||
log.warning(f"Path was not found '{normalized_path}'.")
|
||||
return None
|
||||
|
||||
return {
|
||||
"path": normalized_path,
|
||||
"template": template
|
||||
}
|
||||
|
||||
|
||||
def _get_global_config_data(
|
||||
project_name,
|
||||
host_name,
|
||||
|
|
@ -717,7 +745,7 @@ def _get_global_config_data(
|
|||
2. Custom path to ocio config.
|
||||
3. Path to 'ocioconfig' representation on product. Name of product can be
|
||||
defined in settings. Product name can be regex but exact match is
|
||||
always preferred.
|
||||
always preferred. Fallback can be defined in case no product is found.
|
||||
|
||||
None is returned when no profile is found, when path
|
||||
|
||||
|
|
@ -755,30 +783,36 @@ def _get_global_config_data(
|
|||
|
||||
profile_type = profile["type"]
|
||||
if profile_type in ("builtin_path", "custom_path"):
|
||||
template = profile[profile_type]
|
||||
result = StringTemplate.format_strict_template(
|
||||
template, template_data
|
||||
)
|
||||
normalized_path = str(result.normalized())
|
||||
if not os.path.exists(normalized_path):
|
||||
log.warning(f"Path was not found '{normalized_path}'.")
|
||||
return None
|
||||
|
||||
return {
|
||||
"path": normalized_path,
|
||||
"template": template
|
||||
}
|
||||
return _get_config_path_from_profile_data(
|
||||
profile, profile_type, template_data)
|
||||
|
||||
# TODO decide if this is the right name for representation
|
||||
repre_name = "ocioconfig"
|
||||
|
||||
published_product_data = profile["published_product"]
|
||||
product_name = published_product_data["product_name"]
|
||||
fallback_data = published_product_data["fallback"]
|
||||
|
||||
if product_name == "":
|
||||
log.error(
|
||||
"Colorspace OCIO config path cannot be set. "
|
||||
"Profile is set to published product but `Product name` is empty."
|
||||
)
|
||||
return None
|
||||
|
||||
folder_info = template_data.get("folder")
|
||||
if not folder_info:
|
||||
log.warning("Folder info is missing.")
|
||||
return None
|
||||
|
||||
log.info("Using fallback data for ocio config path.")
|
||||
# in case no product was found we need to use fallback
|
||||
fallback_type = fallback_data["fallback_type"]
|
||||
return _get_config_path_from_profile_data(
|
||||
fallback_data, fallback_type, template_data
|
||||
)
|
||||
|
||||
folder_path = folder_info["path"]
|
||||
|
||||
product_name = profile["product_name"]
|
||||
if folder_id is None:
|
||||
folder_entity = ayon_api.get_folder_by_path(
|
||||
project_name, folder_path, fields={"id"}
|
||||
|
|
@ -797,12 +831,13 @@ def _get_global_config_data(
|
|||
fields={"id", "name"}
|
||||
)
|
||||
}
|
||||
|
||||
if not product_entities_by_name:
|
||||
log.debug(
|
||||
f"No product entities were found for folder '{folder_path}' with"
|
||||
f" product name filter '{product_name}'."
|
||||
# in case no product was found we need to use fallback
|
||||
fallback_type = fallback_data["type"]
|
||||
return _get_config_path_from_profile_data(
|
||||
fallback_data, fallback_type, template_data
|
||||
)
|
||||
return None
|
||||
|
||||
# Try to use exact match first, otherwise use first available product
|
||||
product_entity = product_entities_by_name.get(product_name)
|
||||
|
|
@ -837,6 +872,7 @@ def _get_global_config_data(
|
|||
|
||||
path = get_representation_path_with_anatomy(repre_entity, anatomy)
|
||||
template = repre_entity["attrib"]["template"]
|
||||
|
||||
return {
|
||||
"path": path,
|
||||
"template": template,
|
||||
|
|
|
|||
|
|
@ -4,20 +4,3 @@ AYON_INSTANCE_ID = "ayon.create.instance"
|
|||
# Backwards compatibility
|
||||
AVALON_CONTAINER_ID = "pyblish.avalon.container"
|
||||
AVALON_INSTANCE_ID = "pyblish.avalon.instance"
|
||||
|
||||
# TODO get extensions from host implementations
|
||||
HOST_WORKFILE_EXTENSIONS = {
|
||||
"blender": [".blend"],
|
||||
"celaction": [".scn"],
|
||||
"tvpaint": [".tvpp"],
|
||||
"fusion": [".comp"],
|
||||
"harmony": [".zip"],
|
||||
"houdini": [".hip", ".hiplc", ".hipnc"],
|
||||
"maya": [".ma", ".mb"],
|
||||
"nuke": [".nk"],
|
||||
"hiero": [".hrox"],
|
||||
"photoshop": [".psd", ".psb"],
|
||||
"premiere": [".prproj"],
|
||||
"resolve": [".drp"],
|
||||
"aftereffects": [".aep"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,7 +132,10 @@ def install_host(host):
|
|||
|
||||
def modified_emit(obj, record):
|
||||
"""Method replacing `emit` in Pyblish's MessageHandler."""
|
||||
record.msg = record.getMessage()
|
||||
try:
|
||||
record.msg = record.getMessage()
|
||||
except Exception:
|
||||
record.msg = str(record.msg)
|
||||
obj.records.append(record)
|
||||
|
||||
MessageHandler.emit = modified_emit
|
||||
|
|
@ -234,16 +237,6 @@ def install_ayon_plugins(project_name=None, host_name=None):
|
|||
register_inventory_action_path(path)
|
||||
|
||||
|
||||
def install_openpype_plugins(project_name=None, host_name=None):
|
||||
"""Install AYON core plugins and make sure the core is initialized.
|
||||
|
||||
Deprecated:
|
||||
Use `install_ayon_plugins` instead.
|
||||
|
||||
"""
|
||||
install_ayon_plugins(project_name, host_name)
|
||||
|
||||
|
||||
def uninstall_host():
|
||||
"""Undo all of what `install()` did"""
|
||||
host = registered_host()
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ import traceback
|
|||
import collections
|
||||
import inspect
|
||||
from contextlib import contextmanager
|
||||
from typing import Optional
|
||||
import typing
|
||||
from typing import Optional, Iterable, Dict
|
||||
|
||||
import pyblish.logic
|
||||
import pyblish.api
|
||||
|
|
@ -31,13 +32,15 @@ from .exceptions import (
|
|||
HostMissRequiredMethod,
|
||||
)
|
||||
from .changes import TrackChangesItem
|
||||
from .structures import PublishAttributes, ConvertorItem
|
||||
from .structures import PublishAttributes, ConvertorItem, InstanceContextInfo
|
||||
from .creator_plugins import (
|
||||
Creator,
|
||||
AutoCreator,
|
||||
discover_creator_plugins,
|
||||
discover_convertor_plugins,
|
||||
)
|
||||
if typing.TYPE_CHECKING:
|
||||
from .structures import CreatedInstance
|
||||
|
||||
# Import of functions and classes that were moved to different file
|
||||
# TODO Should be removed in future release - Added 24/08/28, 0.4.3-dev.1
|
||||
|
|
@ -183,6 +186,10 @@ class CreateContext:
|
|||
# Shared data across creators during collection phase
|
||||
self._collection_shared_data = None
|
||||
|
||||
# Context validation cache
|
||||
self._folder_id_by_folder_path = {}
|
||||
self._task_names_by_folder_path = {}
|
||||
|
||||
self.thumbnail_paths_by_instance_id = {}
|
||||
|
||||
# Trigger reset if was enabled
|
||||
|
|
@ -202,17 +209,19 @@ class CreateContext:
|
|||
"""Access to global publish attributes."""
|
||||
return self._publish_attributes
|
||||
|
||||
def get_instance_by_id(self, instance_id):
|
||||
def get_instance_by_id(
|
||||
self, instance_id: str
|
||||
) -> Optional["CreatedInstance"]:
|
||||
"""Receive instance by id.
|
||||
|
||||
Args:
|
||||
instance_id (str): Instance id.
|
||||
|
||||
Returns:
|
||||
Union[CreatedInstance, None]: Instance or None if instance with
|
||||
Optional[CreatedInstance]: Instance or None if instance with
|
||||
given id is not available.
|
||||
"""
|
||||
|
||||
"""
|
||||
return self._instances_by_id.get(instance_id)
|
||||
|
||||
def get_sorted_creators(self, identifiers=None):
|
||||
|
|
@ -224,8 +233,8 @@ class CreateContext:
|
|||
|
||||
Returns:
|
||||
List[BaseCreator]: Sorted creator plugins by 'order' value.
|
||||
"""
|
||||
|
||||
"""
|
||||
if identifiers is not None:
|
||||
identifiers = set(identifiers)
|
||||
creators = [
|
||||
|
|
@ -491,6 +500,8 @@ class CreateContext:
|
|||
|
||||
# Give ability to store shared data for collection phase
|
||||
self._collection_shared_data = {}
|
||||
self._folder_id_by_folder_path = {}
|
||||
self._task_names_by_folder_path = {}
|
||||
|
||||
def reset_finalization(self):
|
||||
"""Cleanup of attributes after reset."""
|
||||
|
|
@ -715,7 +726,7 @@ class CreateContext:
|
|||
self._original_context_data, self.context_data_to_store()
|
||||
)
|
||||
|
||||
def creator_adds_instance(self, instance):
|
||||
def creator_adds_instance(self, instance: "CreatedInstance"):
|
||||
"""Creator adds new instance to context.
|
||||
|
||||
Instances should be added only from creators.
|
||||
|
|
@ -942,7 +953,7 @@ class CreateContext:
|
|||
def _remove_instance(self, instance):
|
||||
self._instances_by_id.pop(instance.id, None)
|
||||
|
||||
def creator_removed_instance(self, instance):
|
||||
def creator_removed_instance(self, instance: "CreatedInstance"):
|
||||
"""When creator removes instance context should be acknowledged.
|
||||
|
||||
If creator removes instance conext should know about it to avoid
|
||||
|
|
@ -990,7 +1001,7 @@ class CreateContext:
|
|||
[],
|
||||
self._bulk_instances_to_process
|
||||
)
|
||||
self.validate_instances_context(instances_to_validate)
|
||||
self.get_instances_context_info(instances_to_validate)
|
||||
|
||||
def reset_instances(self):
|
||||
"""Reload instances"""
|
||||
|
|
@ -1079,26 +1090,70 @@ class CreateContext:
|
|||
if failed_info:
|
||||
raise CreatorsCreateFailed(failed_info)
|
||||
|
||||
def validate_instances_context(self, instances=None):
|
||||
"""Validate 'folder' and 'task' instance context."""
|
||||
def get_instances_context_info(
|
||||
self, instances: Optional[Iterable["CreatedInstance"]] = None
|
||||
) -> Dict[str, InstanceContextInfo]:
|
||||
"""Validate 'folder' and 'task' instance context.
|
||||
|
||||
Args:
|
||||
instances (Optional[Iterable[CreatedInstance]]): Instances to
|
||||
validate. If not provided all instances are validated.
|
||||
|
||||
Returns:
|
||||
Dict[str, InstanceContextInfo]: Validation results by instance id.
|
||||
|
||||
"""
|
||||
# Use all instances from context if 'instances' are not passed
|
||||
if instances is None:
|
||||
instances = tuple(self._instances_by_id.values())
|
||||
instances = self._instances_by_id.values()
|
||||
instances = tuple(instances)
|
||||
info_by_instance_id = {
|
||||
instance.id: InstanceContextInfo(
|
||||
instance.get("folderPath"),
|
||||
instance.get("task"),
|
||||
False,
|
||||
False,
|
||||
)
|
||||
for instance in instances
|
||||
}
|
||||
|
||||
# Skip if instances are empty
|
||||
if not instances:
|
||||
return
|
||||
if not info_by_instance_id:
|
||||
return info_by_instance_id
|
||||
|
||||
project_name = self.project_name
|
||||
|
||||
task_names_by_folder_path = {}
|
||||
to_validate = []
|
||||
task_names_by_folder_path = collections.defaultdict(set)
|
||||
for instance in instances:
|
||||
folder_path = instance.get("folderPath")
|
||||
task_name = instance.get("task")
|
||||
if folder_path:
|
||||
task_names_by_folder_path[folder_path] = set()
|
||||
if task_name:
|
||||
task_names_by_folder_path[folder_path].add(task_name)
|
||||
context_info = info_by_instance_id[instance.id]
|
||||
if instance.has_promised_context:
|
||||
context_info.folder_is_valid = True
|
||||
context_info.task_is_valid = True
|
||||
continue
|
||||
# TODO allow context promise
|
||||
folder_path = context_info.folder_path
|
||||
if not folder_path:
|
||||
continue
|
||||
|
||||
if folder_path in self._folder_id_by_folder_path:
|
||||
folder_id = self._folder_id_by_folder_path[folder_path]
|
||||
if folder_id is None:
|
||||
continue
|
||||
context_info.folder_is_valid = True
|
||||
|
||||
task_name = context_info.task_name
|
||||
if task_name is not None:
|
||||
tasks_cache = self._task_names_by_folder_path.get(folder_path)
|
||||
if tasks_cache is not None:
|
||||
context_info.task_is_valid = task_name in tasks_cache
|
||||
continue
|
||||
|
||||
to_validate.append(instance)
|
||||
task_names_by_folder_path[folder_path].add(task_name)
|
||||
|
||||
if not to_validate:
|
||||
return info_by_instance_id
|
||||
|
||||
# Backwards compatibility for cases where folder name is set instead
|
||||
# of folder path
|
||||
|
|
@ -1120,7 +1175,9 @@ class CreateContext:
|
|||
fields={"id", "path"}
|
||||
):
|
||||
folder_id = folder_entity["id"]
|
||||
folder_paths_by_id[folder_id] = folder_entity["path"]
|
||||
folder_path = folder_entity["path"]
|
||||
folder_paths_by_id[folder_id] = folder_path
|
||||
self._folder_id_by_folder_path[folder_path] = folder_id
|
||||
|
||||
folder_entities_by_name = collections.defaultdict(list)
|
||||
if folder_names:
|
||||
|
|
@ -1131,8 +1188,10 @@ class CreateContext:
|
|||
):
|
||||
folder_id = folder_entity["id"]
|
||||
folder_name = folder_entity["name"]
|
||||
folder_paths_by_id[folder_id] = folder_entity["path"]
|
||||
folder_path = folder_entity["path"]
|
||||
folder_paths_by_id[folder_id] = folder_path
|
||||
folder_entities_by_name[folder_name].append(folder_entity)
|
||||
self._folder_id_by_folder_path[folder_path] = folder_id
|
||||
|
||||
tasks_entities = ayon_api.get_tasks(
|
||||
project_name,
|
||||
|
|
@ -1145,12 +1204,11 @@ class CreateContext:
|
|||
folder_id = task_entity["folderId"]
|
||||
folder_path = folder_paths_by_id[folder_id]
|
||||
task_names_by_folder_path[folder_path].add(task_entity["name"])
|
||||
self._task_names_by_folder_path.update(task_names_by_folder_path)
|
||||
|
||||
for instance in instances:
|
||||
if not instance.has_valid_folder or not instance.has_valid_task:
|
||||
continue
|
||||
|
||||
for instance in to_validate:
|
||||
folder_path = instance["folderPath"]
|
||||
task_name = instance.get("task")
|
||||
if folder_path and "/" not in folder_path:
|
||||
folder_entities = folder_entities_by_name.get(folder_path)
|
||||
if len(folder_entities) == 1:
|
||||
|
|
@ -1158,15 +1216,16 @@ class CreateContext:
|
|||
instance["folderPath"] = folder_path
|
||||
|
||||
if folder_path not in task_names_by_folder_path:
|
||||
instance.set_folder_invalid(True)
|
||||
continue
|
||||
context_info = info_by_instance_id[instance.id]
|
||||
context_info.folder_is_valid = True
|
||||
|
||||
task_name = instance["task"]
|
||||
if not task_name:
|
||||
continue
|
||||
|
||||
if task_name not in task_names_by_folder_path[folder_path]:
|
||||
instance.set_task_invalid(True)
|
||||
if (
|
||||
not task_name
|
||||
or task_name in task_names_by_folder_path[folder_path]
|
||||
):
|
||||
context_info.task_is_valid = True
|
||||
return info_by_instance_id
|
||||
|
||||
def save_changes(self):
|
||||
"""Save changes. Update all changed values."""
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import copy
|
||||
import collections
|
||||
from uuid import uuid4
|
||||
from typing import Optional
|
||||
|
||||
from ayon_core.lib.attribute_definitions import (
|
||||
UnknownDef,
|
||||
|
|
@ -396,6 +397,24 @@ class PublishAttributes:
|
|||
)
|
||||
|
||||
|
||||
class InstanceContextInfo:
|
||||
def __init__(
|
||||
self,
|
||||
folder_path: Optional[str],
|
||||
task_name: Optional[str],
|
||||
folder_is_valid: bool,
|
||||
task_is_valid: bool,
|
||||
):
|
||||
self.folder_path: Optional[str] = folder_path
|
||||
self.task_name: Optional[str] = task_name
|
||||
self.folder_is_valid: bool = folder_is_valid
|
||||
self.task_is_valid: bool = task_is_valid
|
||||
|
||||
@property
|
||||
def is_valid(self) -> bool:
|
||||
return self.folder_is_valid and self.task_is_valid
|
||||
|
||||
|
||||
class CreatedInstance:
|
||||
"""Instance entity with data that will be stored to workfile.
|
||||
|
||||
|
|
@ -528,9 +547,6 @@ class CreatedInstance:
|
|||
if not self._data.get("instance_id"):
|
||||
self._data["instance_id"] = str(uuid4())
|
||||
|
||||
self._folder_is_valid = self.has_set_folder
|
||||
self._task_is_valid = self.has_set_task
|
||||
|
||||
def __str__(self):
|
||||
return (
|
||||
"<CreatedInstance {product[name]}"
|
||||
|
|
@ -699,6 +715,17 @@ class CreatedInstance:
|
|||
def publish_attributes(self):
|
||||
return self._data["publish_attributes"]
|
||||
|
||||
@property
|
||||
def has_promised_context(self) -> bool:
|
||||
"""Get context data that are promised to be set by creator.
|
||||
|
||||
Returns:
|
||||
bool: Has context that won't bo validated. Artist can't change
|
||||
value when set to True.
|
||||
|
||||
"""
|
||||
return self._transient_data.get("has_promised_context", False)
|
||||
|
||||
def data_to_store(self):
|
||||
"""Collect data that contain json parsable types.
|
||||
|
||||
|
|
@ -826,46 +853,3 @@ class CreatedInstance:
|
|||
obj.publish_attributes.deserialize_attributes(publish_attributes)
|
||||
|
||||
return obj
|
||||
|
||||
# Context validation related methods/properties
|
||||
@property
|
||||
def has_set_folder(self):
|
||||
"""Folder path is set in data."""
|
||||
|
||||
return "folderPath" in self._data
|
||||
|
||||
@property
|
||||
def has_set_task(self):
|
||||
"""Task name is set in data."""
|
||||
|
||||
return "task" in self._data
|
||||
|
||||
@property
|
||||
def has_valid_context(self):
|
||||
"""Context data are valid for publishing."""
|
||||
|
||||
return self.has_valid_folder and self.has_valid_task
|
||||
|
||||
@property
|
||||
def has_valid_folder(self):
|
||||
"""Folder set in context exists in project."""
|
||||
|
||||
if not self.has_set_folder:
|
||||
return False
|
||||
return self._folder_is_valid
|
||||
|
||||
@property
|
||||
def has_valid_task(self):
|
||||
"""Task set in context exists in project."""
|
||||
|
||||
if not self.has_set_task:
|
||||
return False
|
||||
return self._task_is_valid
|
||||
|
||||
def set_folder_invalid(self, invalid):
|
||||
# TODO replace with `set_folder_path`
|
||||
self._folder_is_valid = not invalid
|
||||
|
||||
def set_task_invalid(self, invalid):
|
||||
# TODO replace with `set_task_name`
|
||||
self._task_is_valid = not invalid
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ def trim_media_range(media_range, source_range):
|
|||
|
||||
"""
|
||||
rw_media_start = _ot.RationalTime(
|
||||
media_range.start_time.value + source_range.start_time.value,
|
||||
source_range.start_time.value,
|
||||
media_range.start_time.rate
|
||||
)
|
||||
rw_media_duration = _ot.RationalTime(
|
||||
|
|
@ -173,11 +173,145 @@ def _sequence_resize(source, length):
|
|||
yield (1 - ratio) * source[int(low)] + ratio * source[int(high)]
|
||||
|
||||
|
||||
def is_clip_from_media_sequence(otio_clip):
|
||||
"""
|
||||
Args:
|
||||
otio_clip (otio.schema.Clip): The OTIO clip to check.
|
||||
|
||||
Returns:
|
||||
bool. Is the provided clip from an input media sequence ?
|
||||
"""
|
||||
media_ref = otio_clip.media_reference
|
||||
metadata = media_ref.metadata
|
||||
|
||||
# OpenTimelineIO 0.13 and newer
|
||||
is_input_sequence = (
|
||||
hasattr(otio.schema, "ImageSequenceReference") and
|
||||
isinstance(media_ref, otio.schema.ImageSequenceReference)
|
||||
)
|
||||
|
||||
# OpenTimelineIO 0.12 and older
|
||||
is_input_sequence_legacy = bool(metadata.get("padding"))
|
||||
|
||||
return is_input_sequence or is_input_sequence_legacy
|
||||
|
||||
|
||||
def remap_range_on_file_sequence(otio_clip, in_out_range):
|
||||
"""
|
||||
Args:
|
||||
otio_clip (otio.schema.Clip): The OTIO clip to check.
|
||||
in_out_range (tuple[float, float]): The in-out range to remap.
|
||||
|
||||
Returns:
|
||||
tuple(int, int): The remapped range as discrete frame number.
|
||||
|
||||
Raises:
|
||||
ValueError. When the otio_clip or provided range is invalid.
|
||||
"""
|
||||
if not is_clip_from_media_sequence(otio_clip):
|
||||
raise ValueError(f"Cannot map on non-file sequence clip {otio_clip}.")
|
||||
|
||||
try:
|
||||
media_in_trimmed, media_out_trimmed = in_out_range
|
||||
|
||||
except ValueError as error:
|
||||
raise ValueError("Invalid in_out_range provided.") from error
|
||||
|
||||
media_ref = otio_clip.media_reference
|
||||
available_range = otio_clip.available_range()
|
||||
source_range = otio_clip.source_range
|
||||
available_range_rate = available_range.start_time.rate
|
||||
media_in = available_range.start_time.value
|
||||
|
||||
# Temporary.
|
||||
# Some AYON custom OTIO exporter were implemented with relative
|
||||
# source range for image sequence. Following code maintain
|
||||
# backward-compatibility by adjusting media_in
|
||||
# while we are updating those.
|
||||
if (
|
||||
is_clip_from_media_sequence(otio_clip)
|
||||
and otio_clip.available_range().start_time.to_frames() == media_ref.start_frame
|
||||
and source_range.start_time.to_frames() < media_ref.start_frame
|
||||
):
|
||||
media_in = 0
|
||||
|
||||
frame_in = otio.opentime.RationalTime.from_frames(
|
||||
media_in_trimmed - media_in + media_ref.start_frame,
|
||||
rate=available_range_rate,
|
||||
).to_frames()
|
||||
frame_out = otio.opentime.RationalTime.from_frames(
|
||||
media_out_trimmed - media_in + media_ref.start_frame,
|
||||
rate=available_range_rate,
|
||||
).to_frames()
|
||||
|
||||
return frame_in, frame_out
|
||||
|
||||
|
||||
def get_media_range_with_retimes(otio_clip, handle_start, handle_end):
|
||||
source_range = otio_clip.source_range
|
||||
available_range = otio_clip.available_range()
|
||||
media_in = available_range.start_time.value
|
||||
media_out = available_range.end_time_inclusive().value
|
||||
available_range_rate = available_range.start_time.rate
|
||||
|
||||
# If media source is an image sequence, returned
|
||||
# mediaIn/mediaOut have to correspond
|
||||
# to frame numbers from source sequence.
|
||||
media_ref = otio_clip.media_reference
|
||||
is_input_sequence = is_clip_from_media_sequence(otio_clip)
|
||||
|
||||
# Temporary.
|
||||
# Some AYON custom OTIO exporter were implemented with relative
|
||||
# source range for image sequence. Following code maintain
|
||||
# backward-compatibility by adjusting available range
|
||||
# while we are updating those.
|
||||
if (
|
||||
is_input_sequence
|
||||
and available_range.start_time.to_frames() == media_ref.start_frame
|
||||
and source_range.start_time.to_frames() < media_ref.start_frame
|
||||
):
|
||||
available_range = _ot.TimeRange(
|
||||
_ot.RationalTime(0, rate=available_range_rate),
|
||||
available_range.duration,
|
||||
)
|
||||
|
||||
# Conform source range bounds to available range rate
|
||||
# .e.g. embedded TC of (3600 sec/ 1h), duration 100 frames
|
||||
#
|
||||
# available |----------------------------------------| 24fps
|
||||
# 86400 86500
|
||||
#
|
||||
#
|
||||
# 90010 90060
|
||||
# src |-----|______duration 2s___|----| 25fps
|
||||
# 90000
|
||||
#
|
||||
#
|
||||
# 86409.6 86466.8
|
||||
# conformed |-------|_____duration _2.38s____|-------| 24fps
|
||||
# 86400
|
||||
#
|
||||
# Note that 24fps is slower than 25fps hence extended duration
|
||||
# to preserve media range
|
||||
|
||||
# Compute new source range based on available rate.
|
||||
|
||||
# Backward-compatibility for Hiero OTIO exporter.
|
||||
# NTSC compatibility might introduce floating rates, when these are
|
||||
# not exactly the same (23.976 vs 23.976024627685547)
|
||||
# this will cause precision issue in computation.
|
||||
# Currently round to 2 decimals for comparison,
|
||||
# but this should always rescale after that.
|
||||
rounded_av_rate = round(available_range_rate, 2)
|
||||
rounded_src_rate = round(source_range.start_time.rate, 2)
|
||||
if rounded_av_rate != rounded_src_rate:
|
||||
conformed_src_in = source_range.start_time.rescaled_to(available_range_rate)
|
||||
conformed_src_duration = source_range.duration.rescaled_to(available_range_rate)
|
||||
conformed_source_range = otio.opentime.TimeRange(
|
||||
start_time=conformed_src_in,
|
||||
duration=conformed_src_duration
|
||||
)
|
||||
|
||||
else:
|
||||
conformed_source_range = source_range
|
||||
|
||||
# modifiers
|
||||
time_scalar = 1.
|
||||
|
|
@ -224,38 +358,51 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end):
|
|||
offset_in *= time_scalar
|
||||
offset_out *= time_scalar
|
||||
|
||||
# filip offset if reversed speed
|
||||
if time_scalar < 0:
|
||||
_offset_in = offset_out
|
||||
_offset_out = offset_in
|
||||
offset_in = _offset_in
|
||||
offset_out = _offset_out
|
||||
|
||||
# scale handles
|
||||
handle_start *= abs(time_scalar)
|
||||
handle_end *= abs(time_scalar)
|
||||
|
||||
# filip handles if reversed speed
|
||||
# flip offset and handles if reversed speed
|
||||
if time_scalar < 0:
|
||||
_handle_start = handle_end
|
||||
_handle_end = handle_start
|
||||
handle_start = _handle_start
|
||||
handle_end = _handle_end
|
||||
offset_in, offset_out = offset_out, offset_in
|
||||
handle_start, handle_end = handle_end, handle_start
|
||||
|
||||
source_in = source_range.start_time.value
|
||||
# compute retimed range
|
||||
media_in_trimmed = conformed_source_range.start_time.value + offset_in
|
||||
media_out_trimmed = media_in_trimmed + (
|
||||
(
|
||||
conformed_source_range.duration.value
|
||||
* abs(time_scalar)
|
||||
+ offset_out
|
||||
) - 1
|
||||
)
|
||||
|
||||
media_in_trimmed = (
|
||||
media_in + source_in + offset_in)
|
||||
media_out_trimmed = (
|
||||
media_in + source_in + (
|
||||
((source_range.duration.value - 1) * abs(
|
||||
time_scalar)) + offset_out))
|
||||
media_in = available_range.start_time.value
|
||||
media_out = available_range.end_time_inclusive().value
|
||||
|
||||
# calculate available handles
|
||||
# If media source is an image sequence, returned
|
||||
# mediaIn/mediaOut have to correspond
|
||||
# to frame numbers from source sequence.
|
||||
if is_input_sequence:
|
||||
# preserve discrete frame numbers
|
||||
media_in_trimmed, media_out_trimmed = remap_range_on_file_sequence(
|
||||
otio_clip,
|
||||
(media_in_trimmed, media_out_trimmed)
|
||||
)
|
||||
media_in = media_ref.start_frame
|
||||
media_out = media_in + available_range.duration.to_frames() - 1
|
||||
|
||||
# adjust available handles if needed
|
||||
if (media_in_trimmed - media_in) < handle_start:
|
||||
handle_start = (media_in_trimmed - media_in)
|
||||
handle_start = max(0, media_in_trimmed - media_in)
|
||||
if (media_out - media_out_trimmed) < handle_end:
|
||||
handle_end = (media_out - media_out_trimmed)
|
||||
handle_end = max(0, media_out - media_out_trimmed)
|
||||
|
||||
# FFmpeg extraction ignores embedded timecode
|
||||
# so substract to get a (mediaIn-mediaOut) range from 0.
|
||||
if not is_input_sequence:
|
||||
media_in_trimmed -= media_in
|
||||
media_out_trimmed -= media_in
|
||||
|
||||
# create version data
|
||||
version_data = {
|
||||
|
|
@ -263,16 +410,16 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end):
|
|||
"retime": True,
|
||||
"speed": time_scalar,
|
||||
"timewarps": time_warp_nodes,
|
||||
"handleStart": int(round(handle_start)),
|
||||
"handleEnd": int(round(handle_end))
|
||||
"handleStart": int(handle_start),
|
||||
"handleEnd": int(handle_end)
|
||||
}
|
||||
}
|
||||
|
||||
returning_dict = {
|
||||
"mediaIn": media_in_trimmed,
|
||||
"mediaOut": media_out_trimmed,
|
||||
"handleStart": int(round(handle_start)),
|
||||
"handleEnd": int(round(handle_end)),
|
||||
"handleStart": int(handle_start),
|
||||
"handleEnd": int(handle_end),
|
||||
"speed": time_scalar
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -788,6 +788,11 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data,
|
|||
colorspace = product.colorspace
|
||||
break
|
||||
|
||||
if isinstance(files, (list, tuple)):
|
||||
files = [os.path.basename(f) for f in files]
|
||||
else:
|
||||
files = os.path.basename(files)
|
||||
|
||||
rep = {
|
||||
"name": ext,
|
||||
"ext": ext,
|
||||
|
|
|
|||
|
|
@ -1,20 +1,22 @@
|
|||
# Publish
|
||||
AYON is using `pyblish` for publishing process which is a little bit extented and modified mainly for UI purposes. OpenPype's (new) publish UI does not allow to enable/disable instances or plugins that can be done during creation part. Also does support actions only for validators after validation exception.
|
||||
AYON is using `pyblish` for publishing process which is a little bit extented and modified mainly for UI purposes. AYON's (new) publish UI does not allow to enable/disable instances or plugins that can be done during creation part. Also does support actions only for validators after validation exception.
|
||||
|
||||
## Exceptions
|
||||
AYON define few specific exceptions that should be used in publish plugins.
|
||||
|
||||
### Publish error
|
||||
Exception `PublishError` can be raised on known error. The message is shown to artist.
|
||||
- **message** Error message.
|
||||
- **title** Short description of error (2-5 words). Title can be used for grouping of exceptions per plugin.
|
||||
- **description** Override of 'message' for UI, you can add markdown and html. By default, is filled with 'message'.
|
||||
- **detail** Additional detail message that is hidden under collapsed component.
|
||||
|
||||
Arguments `title`, `description` and `detail` are optional. Title is filled with generic message "This is not your fault" if is not passed.
|
||||
|
||||
### Validation exception
|
||||
Validation plugins should raise `PublishValidationError` to show to an artist what's wrong and give him actions to fix it. The exception says that error happened in plugin can be fixed by artist himself (with or without action on plugin). Any other errors will stop publishing immediately. Exception `PublishValidationError` raised after validation order has same effect as any other exception.
|
||||
|
||||
Exception `PublishValidationError` 3 arguments:
|
||||
- **message** Which is not used in UI but for headless publishing.
|
||||
- **title** Short description of error (2-5 words). Title is used for grouping of exceptions per plugin.
|
||||
- **description** Detailed description of happened issue where markdown and html can be used.
|
||||
|
||||
|
||||
### Known errors
|
||||
When there is a known error that can't be fixed by user (e.g. can't connect to deadline service, etc.) `KnownPublishError` should be raise. The only difference is that it's message is shown in UI to artist otherwise a neutral message without context is shown.
|
||||
Exception expect same arguments as `PublishError`. Value of `title` is filled with plugin label if is not passed.
|
||||
|
||||
## Plugin extension
|
||||
Publish plugins can be extended by additional logic when inherits from `AYONPyblishPluginMixin` which can be used as mixin (additional inheritance of class).
|
||||
|
|
|
|||
|
|
@ -9,11 +9,12 @@ from .publish_plugins import (
|
|||
AbstractMetaInstancePlugin,
|
||||
AbstractMetaContextPlugin,
|
||||
|
||||
KnownPublishError,
|
||||
PublishError,
|
||||
PublishValidationError,
|
||||
PublishXmlValidationError,
|
||||
KnownPublishError,
|
||||
|
||||
AYONPyblishPluginMixin,
|
||||
OpenPypePyblishPluginMixin,
|
||||
OptionalPyblishPluginMixin,
|
||||
|
||||
RepairAction,
|
||||
|
|
@ -62,11 +63,12 @@ __all__ = (
|
|||
"AbstractMetaInstancePlugin",
|
||||
"AbstractMetaContextPlugin",
|
||||
|
||||
"KnownPublishError",
|
||||
"PublishError",
|
||||
"PublishValidationError",
|
||||
"PublishXmlValidationError",
|
||||
"KnownPublishError",
|
||||
|
||||
"AYONPyblishPluginMixin",
|
||||
"OpenPypePyblishPluginMixin",
|
||||
"OptionalPyblishPluginMixin",
|
||||
|
||||
"RepairAction",
|
||||
|
|
|
|||
|
|
@ -379,7 +379,7 @@ def get_plugin_settings(plugin, project_settings, log, category=None):
|
|||
plugin_kind = split_path[-2]
|
||||
|
||||
# TODO: change after all plugins are moved one level up
|
||||
if category_from_file in ("ayon_core", "openpype"):
|
||||
if category_from_file == "ayon_core":
|
||||
category_from_file = "core"
|
||||
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -25,27 +25,52 @@ class AbstractMetaContextPlugin(ABCMeta, ExplicitMetaPlugin):
|
|||
pass
|
||||
|
||||
|
||||
class PublishValidationError(Exception):
|
||||
"""Validation error happened during publishing.
|
||||
class KnownPublishError(Exception):
|
||||
"""Publishing crashed because of known error.
|
||||
|
||||
This exception should be used when validation publishing failed.
|
||||
Artist can't affect source of the error.
|
||||
|
||||
Has additional UI specific attributes that may be handy for artist.
|
||||
Deprecated:
|
||||
Please use `PublishError` instead. Marked as deprecated 24/09/02.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class PublishError(Exception):
|
||||
"""Publishing crashed because of known error.
|
||||
|
||||
Message will be shown in UI for artist.
|
||||
|
||||
Args:
|
||||
message(str): Message of error. Short explanation an issue.
|
||||
title(str): Title showed in UI. All instances are grouped under
|
||||
single title.
|
||||
description(str): Detailed description of an error. It is possible
|
||||
to use Markdown syntax.
|
||||
"""
|
||||
message (str): Message of error. Short explanation an issue.
|
||||
title (Optional[str]): Title showed in UI.
|
||||
description (Optional[str]): Detailed description of an error.
|
||||
It is possible to use Markdown syntax.
|
||||
|
||||
"""
|
||||
def __init__(self, message, title=None, description=None, detail=None):
|
||||
self.message = message
|
||||
self.title = title
|
||||
self.description = description or message
|
||||
self.detail = detail
|
||||
super(PublishValidationError, self).__init__(message)
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class PublishValidationError(PublishError):
|
||||
"""Validation error happened during publishing.
|
||||
|
||||
This exception should be used when validation publishing failed.
|
||||
|
||||
Publishing does not stop during validation order if this
|
||||
exception is raised.
|
||||
|
||||
Has additional UI specific attributes that may be handy for artist.
|
||||
|
||||
Argument 'title' is used to group errors.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class PublishXmlValidationError(PublishValidationError):
|
||||
|
|
@ -68,15 +93,6 @@ class PublishXmlValidationError(PublishValidationError):
|
|||
)
|
||||
|
||||
|
||||
class KnownPublishError(Exception):
|
||||
"""Publishing crashed because of known error.
|
||||
|
||||
Message will be shown in UI for artist.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class AYONPyblishPluginMixin:
|
||||
# TODO
|
||||
# executable_in_thread = False
|
||||
|
|
@ -165,9 +181,6 @@ class AYONPyblishPluginMixin:
|
|||
return self.get_attr_values_from_data_for_plugin(self.__class__, data)
|
||||
|
||||
|
||||
OpenPypePyblishPluginMixin = AYONPyblishPluginMixin
|
||||
|
||||
|
||||
class OptionalPyblishPluginMixin(AYONPyblishPluginMixin):
|
||||
"""Prepare mixin for optional plugins.
|
||||
|
||||
|
|
|
|||
|
|
@ -25,13 +25,7 @@ def create_custom_tempdir(project_name, anatomy=None):
|
|||
"""
|
||||
env_tmpdir = os.getenv("AYON_TMPDIR")
|
||||
if not env_tmpdir:
|
||||
env_tmpdir = os.getenv("OPENPYPE_TMPDIR")
|
||||
if not env_tmpdir:
|
||||
return
|
||||
print(
|
||||
"DEPRECATION WARNING: Used 'OPENPYPE_TMPDIR' environment"
|
||||
" variable. Please use 'AYON_TMPDIR' instead."
|
||||
)
|
||||
return
|
||||
|
||||
custom_tempdir = None
|
||||
if "{" in env_tmpdir:
|
||||
|
|
|
|||
|
|
@ -506,55 +506,61 @@ class AbstractTemplateBuilder(ABC):
|
|||
keep_placeholders (bool): Add flag to placeholder data for
|
||||
hosts to decide if they want to remove
|
||||
placeholder after it is used.
|
||||
create_first_version (bool): create first version of a workfile
|
||||
workfile_creation_enabled (bool): If True, it might create
|
||||
first version but ignore
|
||||
process if version is created
|
||||
create_first_version (bool): Create first version of a workfile.
|
||||
When set to True, this option initiates the saving of the
|
||||
workfile for an initial version. It will skip saving if
|
||||
a version already exists.
|
||||
workfile_creation_enabled (bool): Whether the call is part of
|
||||
creating a new workfile.
|
||||
When True, we only build if the current file is not
|
||||
an existing saved workfile but a "new" file. Basically when
|
||||
enabled we assume the user tries to load it only into a
|
||||
"New File" (unsaved empty workfile).
|
||||
When False, the default value, we assume we explicitly want to
|
||||
build the template in our current scene regardless of current
|
||||
scene state.
|
||||
|
||||
"""
|
||||
if any(
|
||||
value is None
|
||||
for value in [
|
||||
template_path,
|
||||
keep_placeholders,
|
||||
create_first_version,
|
||||
]
|
||||
):
|
||||
template_preset = self.get_template_preset()
|
||||
if template_path is None:
|
||||
template_path = template_preset["path"]
|
||||
if keep_placeholders is None:
|
||||
keep_placeholders = template_preset["keep_placeholder"]
|
||||
if create_first_version is None:
|
||||
create_first_version = template_preset["create_first_version"]
|
||||
# More accurate variable name
|
||||
# - logic related to workfile creation should be moved out in future
|
||||
explicit_build_requested = not workfile_creation_enabled
|
||||
|
||||
# check if first version is created
|
||||
created_version_workfile = False
|
||||
if create_first_version:
|
||||
created_version_workfile = self.create_first_workfile_version()
|
||||
|
||||
# if first version is created, import template
|
||||
# and populate placeholders
|
||||
# Get default values if not provided
|
||||
if (
|
||||
create_first_version
|
||||
and workfile_creation_enabled
|
||||
and created_version_workfile
|
||||
template_path is None
|
||||
or keep_placeholders is None
|
||||
or create_first_version is None
|
||||
):
|
||||
preset = self.get_template_preset()
|
||||
template_path: str = template_path or preset["path"]
|
||||
if keep_placeholders is None:
|
||||
keep_placeholders: bool = preset["keep_placeholder"]
|
||||
if create_first_version is None:
|
||||
create_first_version: bool = preset["create_first_version"]
|
||||
|
||||
# Build the template if we are explicitly requesting it or if it's
|
||||
# an unsaved "new file".
|
||||
is_new_file = not self.host.get_current_workfile()
|
||||
if is_new_file or explicit_build_requested:
|
||||
self.log.info(f"Building the workfile template: {template_path}")
|
||||
self.import_template(template_path)
|
||||
self.populate_scene_placeholders(
|
||||
level_limit, keep_placeholders)
|
||||
|
||||
# save workfile after template is populated
|
||||
self.save_workfile(created_version_workfile)
|
||||
|
||||
# ignore process if first workfile is enabled
|
||||
# but a version is already created
|
||||
if workfile_creation_enabled:
|
||||
# Do not consider saving a first workfile version, if this is not set
|
||||
# to be a "workfile creation" or `create_first_version` is disabled.
|
||||
if explicit_build_requested or not create_first_version:
|
||||
return
|
||||
|
||||
self.import_template(template_path)
|
||||
self.populate_scene_placeholders(
|
||||
level_limit, keep_placeholders)
|
||||
# If there is no existing workfile, save the first version
|
||||
workfile_path = self.get_workfile_path()
|
||||
if not os.path.exists(workfile_path):
|
||||
self.log.info("Saving first workfile: %s", workfile_path)
|
||||
self.save_workfile(workfile_path)
|
||||
else:
|
||||
self.log.info(
|
||||
"A workfile already exists. Skipping save of workfile as "
|
||||
"initial version.")
|
||||
|
||||
def rebuild_template(self):
|
||||
"""Go through existing placeholders in scene and update them.
|
||||
|
|
@ -608,29 +614,16 @@ class AbstractTemplateBuilder(ABC):
|
|||
|
||||
pass
|
||||
|
||||
def create_first_workfile_version(self):
|
||||
"""
|
||||
Create first version of workfile.
|
||||
def get_workfile_path(self):
|
||||
"""Return last known workfile path or the first workfile path create.
|
||||
|
||||
Should load the content of template into scene so
|
||||
'populate_scene_placeholders' can be started.
|
||||
|
||||
Args:
|
||||
template_path (str): Fullpath for current task and
|
||||
host's template file.
|
||||
Return:
|
||||
str: Last workfile path, or first version to create if none exist.
|
||||
"""
|
||||
# AYON_LAST_WORKFILE will be set to the last existing workfile OR
|
||||
# if none exist it will be set to the first version.
|
||||
last_workfile_path = os.environ.get("AYON_LAST_WORKFILE")
|
||||
self.log.info("__ last_workfile_path: {}".format(last_workfile_path))
|
||||
if os.path.exists(last_workfile_path):
|
||||
# ignore in case workfile existence
|
||||
self.log.info("Workfile already exists, skipping creation.")
|
||||
return False
|
||||
|
||||
# Create first version
|
||||
self.log.info("Creating first version of workfile.")
|
||||
self.save_workfile(last_workfile_path)
|
||||
|
||||
# Confirm creation of first version
|
||||
return last_workfile_path
|
||||
|
||||
def save_workfile(self, workfile_path):
|
||||
|
|
@ -859,7 +852,7 @@ class AbstractTemplateBuilder(ABC):
|
|||
"Settings\\Profiles"
|
||||
).format(host_name.title()))
|
||||
|
||||
# Try fill path with environments and anatomy roots
|
||||
# Try to fill path with environments and anatomy roots
|
||||
anatomy = Anatomy(project_name)
|
||||
fill_data = {
|
||||
key: value
|
||||
|
|
@ -872,9 +865,7 @@ class AbstractTemplateBuilder(ABC):
|
|||
"code": anatomy.project_code,
|
||||
}
|
||||
|
||||
result = StringTemplate.format_template(path, fill_data)
|
||||
if result.solved:
|
||||
path = result.normalized()
|
||||
path = self.resolve_template_path(path, fill_data)
|
||||
|
||||
if path and os.path.exists(path):
|
||||
self.log.info("Found template at: '{}'".format(path))
|
||||
|
|
@ -914,6 +905,27 @@ class AbstractTemplateBuilder(ABC):
|
|||
"create_first_version": create_first_version
|
||||
}
|
||||
|
||||
def resolve_template_path(self, path, fill_data) -> str:
|
||||
"""Resolve the template path.
|
||||
|
||||
By default, this does nothing except returning the path directly.
|
||||
|
||||
This can be overridden in host integrations to perform additional
|
||||
resolving over the template. Like, `hou.text.expandString` in Houdini.
|
||||
|
||||
Arguments:
|
||||
path (str): The input path.
|
||||
fill_data (dict[str, str]): Data to use for template formatting.
|
||||
|
||||
Returns:
|
||||
str: The resolved path.
|
||||
|
||||
"""
|
||||
result = StringTemplate.format_template(path, fill_data)
|
||||
if result.solved:
|
||||
path = result.normalized()
|
||||
return path
|
||||
|
||||
def emit_event(self, topic, data=None, source=None) -> Event:
|
||||
return self._event_system.emit(topic, data, source)
|
||||
|
||||
|
|
@ -1519,9 +1531,10 @@ class PlaceholderLoadMixin(object):
|
|||
if "asset" in placeholder.data:
|
||||
return []
|
||||
|
||||
representation_name = placeholder.data["representation"]
|
||||
if not representation_name:
|
||||
return []
|
||||
representation_names = None
|
||||
representation_name: str = placeholder.data["representation"]
|
||||
if representation_name:
|
||||
representation_names = [representation_name]
|
||||
|
||||
project_name = self.builder.project_name
|
||||
current_folder_entity = self.builder.current_folder_entity
|
||||
|
|
@ -1578,7 +1591,7 @@ class PlaceholderLoadMixin(object):
|
|||
)
|
||||
return list(get_representations(
|
||||
project_name,
|
||||
representation_names={representation_name},
|
||||
representation_names=representation_names,
|
||||
version_ids=version_ids
|
||||
))
|
||||
|
||||
|
|
|
|||
87
client/ayon_core/plugins/actions/show_in_ayon.py
Normal file
87
client/ayon_core/plugins/actions/show_in_ayon.py
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import os
|
||||
import urllib.parse
|
||||
import webbrowser
|
||||
|
||||
from ayon_core.pipeline import LauncherAction
|
||||
from ayon_core.resources import get_ayon_icon_filepath
|
||||
import ayon_api
|
||||
|
||||
|
||||
def get_ayon_entity_uri(
|
||||
project_name,
|
||||
entity_id,
|
||||
entity_type,
|
||||
) -> str:
|
||||
"""Resolve AYON Entity URI from representation context.
|
||||
|
||||
Note:
|
||||
The representation context is the `get_representation_context` dict
|
||||
containing the `project`, `folder, `representation` and so forth.
|
||||
It is not the representation entity `context` key.
|
||||
|
||||
Arguments:
|
||||
project_name (str): The project name.
|
||||
entity_id (str): The entity UUID.
|
||||
entity_type (str): The entity type, like "folder" or"task".
|
||||
|
||||
Raises:
|
||||
RuntimeError: Unable to resolve to a single valid URI.
|
||||
|
||||
Returns:
|
||||
str: The AYON entity URI.
|
||||
|
||||
"""
|
||||
response = ayon_api.post(
|
||||
f"projects/{project_name}/uris",
|
||||
entityType=entity_type,
|
||||
ids=[entity_id])
|
||||
if response.status_code != 200:
|
||||
raise RuntimeError(
|
||||
f"Unable to resolve AYON entity URI for '{project_name}' "
|
||||
f"{entity_type} id '{entity_id}': {response.text}"
|
||||
)
|
||||
uris = response.data["uris"]
|
||||
if len(uris) != 1:
|
||||
raise RuntimeError(
|
||||
f"Unable to resolve AYON entity URI for '{project_name}' "
|
||||
f"{entity_type} id '{entity_id}' to single URI. "
|
||||
f"Received data: {response.data}"
|
||||
)
|
||||
return uris[0]["uri"]
|
||||
|
||||
|
||||
class ShowInAYON(LauncherAction):
|
||||
"""Open AYON browser page to the current context."""
|
||||
name = "showinayon"
|
||||
label = "Show in AYON"
|
||||
icon = get_ayon_icon_filepath()
|
||||
order = 999
|
||||
|
||||
def process(self, selection, **kwargs):
|
||||
url = os.environ["AYON_SERVER_URL"]
|
||||
if selection.is_project_selected:
|
||||
project_name = selection.project_name
|
||||
url += f"/projects/{project_name}/browser"
|
||||
|
||||
# Specify entity URI if task or folder is select
|
||||
entity = None
|
||||
entity_type = None
|
||||
if selection.is_task_selected:
|
||||
entity = selection.get_task_entity()
|
||||
entity_type = "task"
|
||||
elif selection.is_folder_selected:
|
||||
entity = selection.get_folder_entity()
|
||||
entity_type = "folder"
|
||||
|
||||
if entity and entity_type:
|
||||
uri = get_ayon_entity_uri(
|
||||
project_name,
|
||||
entity_id=entity["id"],
|
||||
entity_type=entity_type
|
||||
)
|
||||
uri_encoded = urllib.parse.quote_plus(uri)
|
||||
url += f"?uri={uri_encoded}"
|
||||
|
||||
# Open URL in webbrowser
|
||||
self.log.info(f"Opening URL: {url}")
|
||||
webbrowser.open_new_tab(url)
|
||||
|
|
@ -17,8 +17,7 @@ from ayon_core.pipeline.load import get_representation_path_with_anatomy
|
|||
from ayon_core.pipeline.delivery import (
|
||||
get_format_dict,
|
||||
check_destination_path,
|
||||
deliver_single_file,
|
||||
deliver_sequence,
|
||||
deliver_single_file
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -231,51 +230,55 @@ class DeliveryOptionsDialog(QtWidgets.QDialog):
|
|||
self.log
|
||||
]
|
||||
|
||||
if repre.get("files"):
|
||||
src_paths = []
|
||||
for repre_file in repre["files"]:
|
||||
src_path = self.anatomy.fill_root(repre_file["path"])
|
||||
src_paths.append(src_path)
|
||||
sources_and_frames = collect_frames(src_paths)
|
||||
# TODO: This will currently incorrectly detect 'resources'
|
||||
# that are published along with the publish, because those should
|
||||
# not adhere to the template directly but are ingested in a
|
||||
# customized way. For example, maya look textures or any publish
|
||||
# that directly adds files into `instance.data["transfers"]`
|
||||
src_paths = []
|
||||
for repre_file in repre["files"]:
|
||||
src_path = self.anatomy.fill_root(repre_file["path"])
|
||||
src_paths.append(src_path)
|
||||
sources_and_frames = collect_frames(src_paths)
|
||||
|
||||
frames = set(sources_and_frames.values())
|
||||
frames.discard(None)
|
||||
first_frame = None
|
||||
if frames:
|
||||
first_frame = min(frames)
|
||||
frames = set(sources_and_frames.values())
|
||||
frames.discard(None)
|
||||
first_frame = None
|
||||
if frames:
|
||||
first_frame = min(frames)
|
||||
|
||||
for src_path, frame in sources_and_frames.items():
|
||||
args[0] = src_path
|
||||
# Renumber frames
|
||||
if renumber_frame and frame is not None:
|
||||
# Calculate offset between
|
||||
# first frame and current frame
|
||||
# - '0' for first frame
|
||||
offset = frame_offset - int(first_frame)
|
||||
# Add offset to new frame start
|
||||
dst_frame = int(frame) + offset
|
||||
if dst_frame < 0:
|
||||
msg = "Renumber frame has a smaller number than original frame" # noqa
|
||||
report_items[msg].append(src_path)
|
||||
self.log.warning("{} <{}>".format(
|
||||
msg, dst_frame))
|
||||
continue
|
||||
frame = dst_frame
|
||||
for src_path, frame in sources_and_frames.items():
|
||||
args[0] = src_path
|
||||
# Renumber frames
|
||||
if renumber_frame and frame is not None:
|
||||
# Calculate offset between
|
||||
# first frame and current frame
|
||||
# - '0' for first frame
|
||||
offset = frame_offset - int(first_frame)
|
||||
# Add offset to new frame start
|
||||
dst_frame = int(frame) + offset
|
||||
if dst_frame < 0:
|
||||
msg = "Renumber frame has a smaller number than original frame" # noqa
|
||||
report_items[msg].append(src_path)
|
||||
self.log.warning("{} <{}>".format(
|
||||
msg, dst_frame))
|
||||
continue
|
||||
frame = dst_frame
|
||||
|
||||
if frame is not None:
|
||||
if frame is not None:
|
||||
if repre["context"].get("frame"):
|
||||
anatomy_data["frame"] = frame
|
||||
new_report_items, uploaded = deliver_single_file(*args)
|
||||
report_items.update(new_report_items)
|
||||
self._update_progress(uploaded)
|
||||
else: # fallback for Pype2 and representations without files
|
||||
frame = repre["context"].get("frame")
|
||||
if frame:
|
||||
repre["context"]["frame"] = len(str(frame)) * "#"
|
||||
|
||||
if not frame:
|
||||
new_report_items, uploaded = deliver_single_file(*args)
|
||||
else:
|
||||
new_report_items, uploaded = deliver_sequence(*args)
|
||||
elif repre["context"].get("udim"):
|
||||
anatomy_data["udim"] = frame
|
||||
else:
|
||||
# Fallback
|
||||
self.log.warning(
|
||||
"Representation context has no frame or udim"
|
||||
" data. Supplying sequence frame to '{frame}'"
|
||||
" formatting data."
|
||||
)
|
||||
anatomy_data["frame"] = frame
|
||||
new_report_items, uploaded = deliver_single_file(*args)
|
||||
report_items.update(new_report_items)
|
||||
self._update_progress(uploaded)
|
||||
|
||||
|
|
|
|||
591
client/ayon_core/plugins/load/export_otio.py
Normal file
591
client/ayon_core/plugins/load/export_otio.py
Normal file
|
|
@ -0,0 +1,591 @@
|
|||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
from collections import defaultdict
|
||||
|
||||
from qtpy import QtWidgets, QtCore, QtGui
|
||||
from ayon_api import get_representations
|
||||
|
||||
from ayon_core.pipeline import load, Anatomy
|
||||
from ayon_core import resources, style
|
||||
from ayon_core.lib.transcoding import (
|
||||
IMAGE_EXTENSIONS,
|
||||
get_oiio_info_for_input,
|
||||
)
|
||||
from ayon_core.lib import (
|
||||
get_ffprobe_data,
|
||||
is_oiio_supported,
|
||||
)
|
||||
from ayon_core.pipeline.load import get_representation_path_with_anatomy
|
||||
from ayon_core.tools.utils import show_message_dialog
|
||||
|
||||
OTIO = None
|
||||
FRAME_SPLITTER = "__frame_splitter__"
|
||||
|
||||
def _import_otio():
|
||||
global OTIO
|
||||
if OTIO is None:
|
||||
import opentimelineio
|
||||
OTIO = opentimelineio
|
||||
|
||||
|
||||
class ExportOTIO(load.ProductLoaderPlugin):
|
||||
"""Export selected versions to OpenTimelineIO."""
|
||||
|
||||
is_multiple_contexts_compatible = True
|
||||
sequence_splitter = "__sequence_splitter__"
|
||||
|
||||
representations = {"*"}
|
||||
product_types = {"*"}
|
||||
tool_names = ["library_loader"]
|
||||
|
||||
label = "Export OTIO"
|
||||
order = 35
|
||||
icon = "save"
|
||||
color = "#d8d8d8"
|
||||
|
||||
def load(self, contexts, name=None, namespace=None, options=None):
|
||||
_import_otio()
|
||||
try:
|
||||
dialog = ExportOTIOOptionsDialog(contexts, self.log)
|
||||
dialog.exec_()
|
||||
except Exception:
|
||||
self.log.error("Failed to export OTIO.", exc_info=True)
|
||||
|
||||
|
||||
class ExportOTIOOptionsDialog(QtWidgets.QDialog):
|
||||
"""Dialog to select template where to deliver selected representations."""
|
||||
|
||||
def __init__(self, contexts, log=None, parent=None):
|
||||
# Not all hosts have OpenTimelineIO available.
|
||||
self.log = log
|
||||
|
||||
super().__init__(parent=parent)
|
||||
|
||||
self.setWindowTitle("AYON - Export OTIO")
|
||||
icon = QtGui.QIcon(resources.get_ayon_icon_filepath())
|
||||
self.setWindowIcon(icon)
|
||||
|
||||
self.setWindowFlags(
|
||||
QtCore.Qt.WindowStaysOnTopHint
|
||||
| QtCore.Qt.WindowCloseButtonHint
|
||||
| QtCore.Qt.WindowMinimizeButtonHint
|
||||
)
|
||||
|
||||
project_name = contexts[0]["project"]["name"]
|
||||
versions_by_id = {
|
||||
context["version"]["id"]: context["version"]
|
||||
for context in contexts
|
||||
}
|
||||
repre_entities = list(get_representations(
|
||||
project_name, version_ids=set(versions_by_id)
|
||||
))
|
||||
version_by_representation_id = {
|
||||
repre_entity["id"]: versions_by_id[repre_entity["versionId"]]
|
||||
for repre_entity in repre_entities
|
||||
}
|
||||
version_path_by_id = {}
|
||||
representations_by_version_id = {}
|
||||
for context in contexts:
|
||||
version_id = context["version"]["id"]
|
||||
if version_id in version_path_by_id:
|
||||
continue
|
||||
representations_by_version_id[version_id] = []
|
||||
version_path_by_id[version_id] = "/".join([
|
||||
context["folder"]["path"],
|
||||
context["product"]["name"],
|
||||
context["version"]["name"]
|
||||
])
|
||||
|
||||
for repre_entity in repre_entities:
|
||||
representations_by_version_id[repre_entity["versionId"]].append(
|
||||
repre_entity
|
||||
)
|
||||
|
||||
all_representation_names = list(sorted({
|
||||
repo_entity["name"]
|
||||
for repo_entity in repre_entities
|
||||
}))
|
||||
|
||||
input_widget = QtWidgets.QWidget(self)
|
||||
input_layout = QtWidgets.QGridLayout(input_widget)
|
||||
input_layout.setContentsMargins(8, 8, 8, 8)
|
||||
|
||||
row = 0
|
||||
repres_label = QtWidgets.QLabel("Representations:", input_widget)
|
||||
input_layout.addWidget(repres_label, row, 0)
|
||||
repre_name_buttons = []
|
||||
for idx, name in enumerate(all_representation_names):
|
||||
repre_name_btn = QtWidgets.QPushButton(name, input_widget)
|
||||
input_layout.addWidget(
|
||||
repre_name_btn, row, idx + 1,
|
||||
alignment=QtCore.Qt.AlignCenter
|
||||
)
|
||||
repre_name_btn.clicked.connect(self._toggle_all)
|
||||
repre_name_buttons.append(repre_name_btn)
|
||||
|
||||
row += 1
|
||||
|
||||
representation_widgets = defaultdict(list)
|
||||
items = representations_by_version_id.items()
|
||||
for version_id, representations in items:
|
||||
version_path = version_path_by_id[version_id]
|
||||
label_widget = QtWidgets.QLabel(version_path, input_widget)
|
||||
input_layout.addWidget(label_widget, row, 0)
|
||||
|
||||
repres_by_name = {
|
||||
repre_entity["name"]: repre_entity
|
||||
for repre_entity in representations
|
||||
}
|
||||
radio_group = QtWidgets.QButtonGroup(input_widget)
|
||||
for idx, name in enumerate(all_representation_names):
|
||||
if name in repres_by_name:
|
||||
widget = QtWidgets.QRadioButton(input_widget)
|
||||
radio_group.addButton(widget)
|
||||
representation_widgets[name].append(
|
||||
{
|
||||
"widget": widget,
|
||||
"representation": repres_by_name[name]
|
||||
}
|
||||
)
|
||||
else:
|
||||
widget = QtWidgets.QLabel("x", input_widget)
|
||||
|
||||
input_layout.addWidget(
|
||||
widget, row, idx + 1, 1, 1,
|
||||
alignment=QtCore.Qt.AlignCenter
|
||||
)
|
||||
|
||||
row += 1
|
||||
|
||||
export_widget = QtWidgets.QWidget(self)
|
||||
|
||||
options_widget = QtWidgets.QWidget(export_widget)
|
||||
|
||||
uri_label = QtWidgets.QLabel("URI paths:", options_widget)
|
||||
uri_path_format = QtWidgets.QCheckBox(options_widget)
|
||||
uri_path_format.setToolTip(
|
||||
"Use URI paths (file:///) instead of absolute paths. "
|
||||
"This is useful when the OTIO file will be used on Foundry Hiero."
|
||||
)
|
||||
|
||||
button_output_path = QtWidgets.QPushButton(
|
||||
"Output Path:", options_widget
|
||||
)
|
||||
button_output_path.setToolTip(
|
||||
"Click to select the output path for the OTIO file."
|
||||
)
|
||||
|
||||
line_edit_output_path = QtWidgets.QLineEdit(
|
||||
(Path.home() / f"{project_name}.otio").as_posix(),
|
||||
options_widget
|
||||
)
|
||||
|
||||
options_layout = QtWidgets.QHBoxLayout(options_widget)
|
||||
options_layout.setContentsMargins(0, 0, 0, 0)
|
||||
options_layout.addWidget(uri_label)
|
||||
options_layout.addWidget(uri_path_format)
|
||||
options_layout.addWidget(button_output_path)
|
||||
options_layout.addWidget(line_edit_output_path)
|
||||
|
||||
button_export = QtWidgets.QPushButton("Export", export_widget)
|
||||
|
||||
export_layout = QtWidgets.QVBoxLayout(export_widget)
|
||||
export_layout.setContentsMargins(0, 0, 0, 0)
|
||||
export_layout.addWidget(options_widget, 0)
|
||||
export_layout.addWidget(button_export, 0)
|
||||
|
||||
main_layout = QtWidgets.QVBoxLayout(self)
|
||||
main_layout.setContentsMargins(8, 8, 8, 8)
|
||||
main_layout.addWidget(input_widget, 0)
|
||||
main_layout.addStretch(1)
|
||||
# TODO add line spacer?
|
||||
main_layout.addSpacing(30)
|
||||
main_layout.addWidget(export_widget, 0)
|
||||
|
||||
button_export.clicked.connect(self._on_export_click)
|
||||
button_output_path.clicked.connect(self._set_output_path)
|
||||
|
||||
self._project_name = project_name
|
||||
self._version_path_by_id = version_path_by_id
|
||||
self._version_by_representation_id = version_by_representation_id
|
||||
self._representation_widgets = representation_widgets
|
||||
self._repre_name_buttons = repre_name_buttons
|
||||
|
||||
self._uri_path_format = uri_path_format
|
||||
self._button_output_path = button_output_path
|
||||
self._line_edit_output_path = line_edit_output_path
|
||||
self._button_export = button_export
|
||||
|
||||
self._first_show = True
|
||||
|
||||
def showEvent(self, event):
|
||||
super().showEvent(event)
|
||||
if self._first_show:
|
||||
self._first_show = False
|
||||
self.setStyleSheet(style.load_stylesheet())
|
||||
|
||||
def _toggle_all(self):
|
||||
representation_name = self.sender().text()
|
||||
for item in self._representation_widgets[representation_name]:
|
||||
item["widget"].setChecked(True)
|
||||
|
||||
def _set_output_path(self):
|
||||
file_path, _ = QtWidgets.QFileDialog.getSaveFileName(
|
||||
None, "Save OTIO file.", "", "OTIO Files (*.otio)"
|
||||
)
|
||||
if file_path:
|
||||
self._line_edit_output_path.setText(file_path)
|
||||
|
||||
def _on_export_click(self):
|
||||
output_path = self._line_edit_output_path.text()
|
||||
# Validate output path is not empty.
|
||||
if not output_path:
|
||||
show_message_dialog(
|
||||
"Missing output path",
|
||||
(
|
||||
"Output path is empty. Please enter a path to export the "
|
||||
"OTIO file to."
|
||||
),
|
||||
level="critical",
|
||||
parent=self
|
||||
)
|
||||
return
|
||||
|
||||
# Validate output path ends with .otio.
|
||||
if not output_path.endswith(".otio"):
|
||||
show_message_dialog(
|
||||
"Wrong extension.",
|
||||
(
|
||||
"Output path needs to end with \".otio\"."
|
||||
),
|
||||
level="critical",
|
||||
parent=self
|
||||
)
|
||||
return
|
||||
|
||||
representations = []
|
||||
for name, items in self._representation_widgets.items():
|
||||
for item in items:
|
||||
if item["widget"].isChecked():
|
||||
representations.append(item["representation"])
|
||||
|
||||
anatomy = Anatomy(self._project_name)
|
||||
clips_data = {}
|
||||
for representation in representations:
|
||||
version = self._version_by_representation_id[
|
||||
representation["id"]
|
||||
]
|
||||
name = (
|
||||
f'{self._version_path_by_id[version["id"]]}'
|
||||
f'/{representation["name"]}'
|
||||
).replace("/", "_")
|
||||
|
||||
clips_data[name] = {
|
||||
"representation": representation,
|
||||
"anatomy": anatomy,
|
||||
"frames": (
|
||||
version["attrib"]["frameEnd"]
|
||||
- version["attrib"]["frameStart"]
|
||||
),
|
||||
"framerate": version["attrib"]["fps"],
|
||||
}
|
||||
|
||||
self.export_otio(clips_data, output_path)
|
||||
|
||||
# Feedback about success.
|
||||
show_message_dialog(
|
||||
"Success!",
|
||||
"Export was successful.",
|
||||
level="info",
|
||||
parent=self
|
||||
)
|
||||
|
||||
self.close()
|
||||
|
||||
def create_clip(self, name, clip_data, timeline_framerate):
|
||||
representation = clip_data["representation"]
|
||||
anatomy = clip_data["anatomy"]
|
||||
frames = clip_data["frames"]
|
||||
framerate = clip_data["framerate"]
|
||||
|
||||
# Get path to representation with correct frame number
|
||||
repre_path = get_representation_path_with_anatomy(
|
||||
representation, anatomy)
|
||||
|
||||
media_start_frame = clip_start_frame = 0
|
||||
media_framerate = framerate
|
||||
if file_metadata := get_image_info_metadata(
|
||||
repre_path, ["timecode", "duration", "framerate"], self.log
|
||||
):
|
||||
# get media framerate and convert to float with 3 decimal places
|
||||
media_framerate = file_metadata["framerate"]
|
||||
media_framerate = float(f"{media_framerate:.4f}")
|
||||
framerate = float(f"{timeline_framerate:.4f}")
|
||||
|
||||
media_start_frame = self.get_timecode_start_frame(
|
||||
media_framerate, file_metadata
|
||||
)
|
||||
clip_start_frame = self.get_timecode_start_frame(
|
||||
timeline_framerate, file_metadata
|
||||
)
|
||||
|
||||
if "duration" in file_metadata:
|
||||
frames = int(float(file_metadata["duration"]) * framerate)
|
||||
|
||||
repre_path = Path(repre_path)
|
||||
|
||||
first_frame = representation["context"].get("frame")
|
||||
if first_frame is None:
|
||||
media_range = OTIO.opentime.TimeRange(
|
||||
start_time=OTIO.opentime.RationalTime(
|
||||
media_start_frame, media_framerate
|
||||
),
|
||||
duration=OTIO.opentime.RationalTime(
|
||||
frames, media_framerate),
|
||||
)
|
||||
clip_range = OTIO.opentime.TimeRange(
|
||||
start_time=OTIO.opentime.RationalTime(
|
||||
clip_start_frame, timeline_framerate
|
||||
),
|
||||
duration=OTIO.opentime.RationalTime(
|
||||
frames, timeline_framerate),
|
||||
)
|
||||
|
||||
# Use 'repre_path' as single file
|
||||
media_reference = OTIO.schema.ExternalReference(
|
||||
available_range=media_range,
|
||||
target_url=self.convert_to_uri_or_posix(repre_path),
|
||||
)
|
||||
else:
|
||||
# This is sequence
|
||||
repre_files = [
|
||||
file["path"].format(root=anatomy.roots)
|
||||
for file in representation["files"]
|
||||
]
|
||||
# Change frame in representation context to get path with frame
|
||||
# splitter.
|
||||
representation["context"]["frame"] = FRAME_SPLITTER
|
||||
frame_repre_path = get_representation_path_with_anatomy(
|
||||
representation, anatomy
|
||||
)
|
||||
frame_repre_path = Path(frame_repre_path)
|
||||
repre_dir, repre_filename = (
|
||||
frame_repre_path.parent, frame_repre_path.name)
|
||||
# Get sequence prefix and suffix
|
||||
file_prefix, file_suffix = repre_filename.split(FRAME_SPLITTER)
|
||||
# Get frame number from path as string to get frame padding
|
||||
frame_str = str(repre_path)[len(file_prefix):][:len(file_suffix)]
|
||||
frame_padding = len(frame_str)
|
||||
|
||||
media_range = OTIO.opentime.TimeRange(
|
||||
start_time=OTIO.opentime.RationalTime(
|
||||
media_start_frame, media_framerate
|
||||
),
|
||||
duration=OTIO.opentime.RationalTime(
|
||||
len(repre_files), media_framerate
|
||||
),
|
||||
)
|
||||
clip_range = OTIO.opentime.TimeRange(
|
||||
start_time=OTIO.opentime.RationalTime(
|
||||
clip_start_frame, timeline_framerate
|
||||
),
|
||||
duration=OTIO.opentime.RationalTime(
|
||||
len(repre_files), timeline_framerate
|
||||
),
|
||||
)
|
||||
|
||||
media_reference = OTIO.schema.ImageSequenceReference(
|
||||
available_range=media_range,
|
||||
start_frame=int(first_frame),
|
||||
frame_step=1,
|
||||
rate=framerate,
|
||||
target_url_base=f"{self.convert_to_uri_or_posix(repre_dir)}/",
|
||||
name_prefix=file_prefix,
|
||||
name_suffix=file_suffix,
|
||||
frame_zero_padding=frame_padding,
|
||||
)
|
||||
|
||||
return OTIO.schema.Clip(
|
||||
name=name, media_reference=media_reference, source_range=clip_range
|
||||
)
|
||||
|
||||
def convert_to_uri_or_posix(self, path: Path) -> str:
|
||||
"""Convert path to URI or Posix path.
|
||||
|
||||
Args:
|
||||
path (Path): Path to convert.
|
||||
|
||||
Returns:
|
||||
str: Path as URI or Posix path.
|
||||
"""
|
||||
if self._uri_path_format.isChecked():
|
||||
return path.as_uri()
|
||||
|
||||
return path.as_posix()
|
||||
|
||||
def get_timecode_start_frame(self, framerate, file_metadata):
|
||||
# use otio to convert timecode into frame number
|
||||
timecode_start_frame = OTIO.opentime.from_timecode(
|
||||
file_metadata["timecode"], framerate)
|
||||
return timecode_start_frame.to_frames()
|
||||
|
||||
def export_otio(self, clips_data, output_path):
|
||||
# first find the highest framerate and set it as default framerate
|
||||
# for the timeline
|
||||
timeline_framerate = 0
|
||||
for clip_data in clips_data.values():
|
||||
framerate = clip_data["framerate"]
|
||||
if framerate > timeline_framerate:
|
||||
timeline_framerate = framerate
|
||||
|
||||
# reduce decimal places to 3 - otio does not like more
|
||||
timeline_framerate = float(f"{timeline_framerate:.4f}")
|
||||
|
||||
# create clips from the representations
|
||||
clips = [
|
||||
self.create_clip(name, clip_data, timeline_framerate)
|
||||
for name, clip_data in clips_data.items()
|
||||
]
|
||||
timeline = OTIO.schema.timeline_from_clips(clips)
|
||||
|
||||
# set the timeline framerate to the highest framerate
|
||||
timeline.global_start_time = OTIO.opentime.RationalTime(
|
||||
0, timeline_framerate)
|
||||
|
||||
OTIO.adapters.write_to_file(timeline, output_path)
|
||||
|
||||
|
||||
def get_image_info_metadata(
|
||||
path_to_file,
|
||||
keys=None,
|
||||
logger=None,
|
||||
):
|
||||
"""Get flattened metadata from image file
|
||||
|
||||
With combined approach via FFMPEG and OIIOTool.
|
||||
|
||||
At first it will try to detect if the image input is supported by
|
||||
OpenImageIO. If it is then it gets the metadata from the image using
|
||||
OpenImageIO. If it is not supported by OpenImageIO then it will try to
|
||||
get the metadata using FFprobe.
|
||||
|
||||
Args:
|
||||
path_to_file (str): Path to image file.
|
||||
keys (list[str]): List of keys that should be returned. If None then
|
||||
all keys are returned. Keys are expected all lowercase.
|
||||
Additional keys are:
|
||||
- "framerate" - will be created from "r_frame_rate" or
|
||||
"framespersecond" and evaluated to float value.
|
||||
logger (logging.Logger): Logger used for logging.
|
||||
"""
|
||||
if logger is None:
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def _ffprobe_metadata_conversion(metadata):
|
||||
"""Convert ffprobe metadata unified format."""
|
||||
output = {}
|
||||
for key, val in metadata.items():
|
||||
if key in ("tags", "disposition"):
|
||||
output.update(val)
|
||||
else:
|
||||
output[key] = val
|
||||
return output
|
||||
|
||||
def _get_video_metadata_from_ffprobe(ffprobe_stream):
|
||||
"""Extract video metadata from ffprobe stream.
|
||||
|
||||
Args:
|
||||
ffprobe_stream (dict): Stream data obtained from ffprobe.
|
||||
|
||||
Returns:
|
||||
dict: Video metadata extracted from the ffprobe stream.
|
||||
"""
|
||||
video_stream = None
|
||||
for stream in ffprobe_stream["streams"]:
|
||||
if stream["codec_type"] == "video":
|
||||
video_stream = stream
|
||||
break
|
||||
metadata_stream = _ffprobe_metadata_conversion(video_stream)
|
||||
return metadata_stream
|
||||
|
||||
metadata_stream = None
|
||||
ext = os.path.splitext(path_to_file)[-1].lower()
|
||||
if ext not in IMAGE_EXTENSIONS:
|
||||
logger.info(
|
||||
(
|
||||
'File extension "{}" is not supported by OpenImageIO.'
|
||||
" Trying to get metadata using FFprobe."
|
||||
).format(ext)
|
||||
)
|
||||
ffprobe_stream = get_ffprobe_data(path_to_file, logger)
|
||||
if "streams" in ffprobe_stream and len(ffprobe_stream["streams"]) > 0:
|
||||
metadata_stream = _get_video_metadata_from_ffprobe(ffprobe_stream)
|
||||
|
||||
if not metadata_stream and is_oiio_supported():
|
||||
oiio_stream = get_oiio_info_for_input(path_to_file, logger=logger)
|
||||
if "attribs" in (oiio_stream or {}):
|
||||
metadata_stream = {}
|
||||
for key, val in oiio_stream["attribs"].items():
|
||||
if "smpte:" in key.lower():
|
||||
key = key.replace("smpte:", "")
|
||||
metadata_stream[key.lower()] = val
|
||||
for key, val in oiio_stream.items():
|
||||
if key == "attribs":
|
||||
continue
|
||||
metadata_stream[key] = val
|
||||
else:
|
||||
logger.info(
|
||||
(
|
||||
"OpenImageIO is not supported on this system."
|
||||
" Trying to get metadata using FFprobe."
|
||||
)
|
||||
)
|
||||
ffprobe_stream = get_ffprobe_data(path_to_file, logger)
|
||||
if "streams" in ffprobe_stream and len(ffprobe_stream["streams"]) > 0:
|
||||
metadata_stream = _get_video_metadata_from_ffprobe(ffprobe_stream)
|
||||
|
||||
if not metadata_stream:
|
||||
logger.warning("Failed to get metadata from image file.")
|
||||
return {}
|
||||
|
||||
if keys is None:
|
||||
return metadata_stream
|
||||
|
||||
# create framerate key from available ffmpeg:r_frame_rate
|
||||
# or oiiotool:framespersecond and evaluate its string expression
|
||||
# value into flaot value
|
||||
if (
|
||||
"r_frame_rate" in metadata_stream
|
||||
or "framespersecond" in metadata_stream
|
||||
):
|
||||
rate_info = metadata_stream.get("r_frame_rate")
|
||||
if rate_info is None:
|
||||
rate_info = metadata_stream.get("framespersecond")
|
||||
|
||||
# calculate framerate from string expression
|
||||
if "/" in str(rate_info):
|
||||
time, frame = str(rate_info).split("/")
|
||||
rate_info = float(time) / float(frame)
|
||||
|
||||
try:
|
||||
metadata_stream["framerate"] = float(str(rate_info))
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
"Failed to evaluate '{}' value to framerate. Error: {}".format(
|
||||
rate_info, e
|
||||
)
|
||||
)
|
||||
|
||||
# aggregate all required metadata from prepared metadata stream
|
||||
output = {}
|
||||
for key in keys:
|
||||
for k, v in metadata_stream.items():
|
||||
if key == k:
|
||||
output[key] = v
|
||||
break
|
||||
if isinstance(v, dict) and key in v:
|
||||
output[key] = v[key]
|
||||
break
|
||||
|
||||
return output
|
||||
|
|
@ -15,5 +15,3 @@ class CollectAddons(pyblish.api.ContextPlugin):
|
|||
manager = AddonsManager()
|
||||
context.data["ayonAddonsManager"] = manager
|
||||
context.data["ayonAddons"] = manager.addons_by_name
|
||||
# Backwards compatibility - remove
|
||||
context.data["openPypeModules"] = manager.addons_by_name
|
||||
|
|
|
|||
|
|
@ -217,9 +217,8 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin):
|
|||
joined_paths = ", ".join(
|
||||
["\"{}\"".format(path) for path in not_found_task_paths]
|
||||
)
|
||||
self.log.warning((
|
||||
"Not found task entities with paths \"{}\"."
|
||||
).format(joined_paths))
|
||||
self.log.warning(
|
||||
f"Not found task entities with paths {joined_paths}.")
|
||||
|
||||
def fill_latest_versions(self, context, project_name):
|
||||
"""Try to find latest version for each instance's product name.
|
||||
|
|
|
|||
|
|
@ -53,8 +53,9 @@ class CollectContextEntities(pyblish.api.ContextPlugin):
|
|||
|
||||
context.data["folderEntity"] = folder_entity
|
||||
context.data["taskEntity"] = task_entity
|
||||
|
||||
folder_attributes = folder_entity["attrib"]
|
||||
context_attributes = (
|
||||
task_entity["attrib"] if task_entity else folder_entity["attrib"]
|
||||
)
|
||||
|
||||
# Task type
|
||||
task_type = None
|
||||
|
|
@ -63,12 +64,12 @@ class CollectContextEntities(pyblish.api.ContextPlugin):
|
|||
|
||||
context.data["taskType"] = task_type
|
||||
|
||||
frame_start = folder_attributes.get("frameStart")
|
||||
frame_start = context_attributes.get("frameStart")
|
||||
if frame_start is None:
|
||||
frame_start = 1
|
||||
self.log.warning("Missing frame start. Defaulting to 1.")
|
||||
|
||||
frame_end = folder_attributes.get("frameEnd")
|
||||
frame_end = context_attributes.get("frameEnd")
|
||||
if frame_end is None:
|
||||
frame_end = 2
|
||||
self.log.warning("Missing frame end. Defaulting to 2.")
|
||||
|
|
@ -76,8 +77,8 @@ class CollectContextEntities(pyblish.api.ContextPlugin):
|
|||
context.data["frameStart"] = frame_start
|
||||
context.data["frameEnd"] = frame_end
|
||||
|
||||
handle_start = folder_attributes.get("handleStart") or 0
|
||||
handle_end = folder_attributes.get("handleEnd") or 0
|
||||
handle_start = context_attributes.get("handleStart") or 0
|
||||
handle_end = context_attributes.get("handleEnd") or 0
|
||||
|
||||
context.data["handleStart"] = int(handle_start)
|
||||
context.data["handleEnd"] = int(handle_end)
|
||||
|
|
@ -87,7 +88,7 @@ class CollectContextEntities(pyblish.api.ContextPlugin):
|
|||
context.data["frameStartHandle"] = frame_start_h
|
||||
context.data["frameEndHandle"] = frame_end_h
|
||||
|
||||
context.data["fps"] = folder_attributes["fps"]
|
||||
context.data["fps"] = context_attributes["fps"]
|
||||
|
||||
def _get_folder_entity(self, project_name, folder_path):
|
||||
if not folder_path:
|
||||
|
|
@ -113,4 +114,4 @@ class CollectContextEntities(pyblish.api.ContextPlugin):
|
|||
"Task '{}' was not found in project '{}'.".format(
|
||||
task_path, project_name)
|
||||
)
|
||||
return task_entity
|
||||
return task_entity
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ class CollectInputRepresentationsToVersions(pyblish.api.ContextPlugin):
|
|||
"""Converts collected input representations to input versions.
|
||||
|
||||
Any data in `instance.data["inputRepresentations"]` gets converted into
|
||||
`instance.data["inputVersions"]` as supported in OpenPype v3.
|
||||
`instance.data["inputVersions"]` as supported in OpenPype.
|
||||
|
||||
"""
|
||||
# This is a ContextPlugin because then we can query the database only once
|
||||
|
|
|
|||
|
|
@ -138,10 +138,7 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin):
|
|||
def process(self, context):
|
||||
self._context = context
|
||||
|
||||
publish_data_paths = (
|
||||
os.environ.get("AYON_PUBLISH_DATA")
|
||||
or os.environ.get("OPENPYPE_PUBLISH_DATA")
|
||||
)
|
||||
publish_data_paths = os.environ.get("AYON_PUBLISH_DATA")
|
||||
if not publish_data_paths:
|
||||
raise KnownPublishError("Missing `AYON_PUBLISH_DATA`")
|
||||
|
||||
|
|
|
|||
|
|
@ -200,7 +200,7 @@ class ExtractBurnin(publish.Extractor):
|
|||
if not burnins_per_repres:
|
||||
self.log.debug(
|
||||
"Skipped instance. No representations found matching a burnin"
|
||||
"definition in: %s", burnin_defs
|
||||
" definition in: %s", burnin_defs
|
||||
)
|
||||
return
|
||||
|
||||
|
|
@ -400,7 +400,7 @@ class ExtractBurnin(publish.Extractor):
|
|||
|
||||
add_repre_files_for_cleanup(instance, new_repre)
|
||||
|
||||
# Cleanup temp staging dir after procesisng of output definitions
|
||||
# Cleanup temp staging dir after processing of output definitions
|
||||
if do_convert:
|
||||
temp_dir = repre["stagingDir"]
|
||||
shutil.rmtree(temp_dir)
|
||||
|
|
@ -421,6 +421,12 @@ class ExtractBurnin(publish.Extractor):
|
|||
self.log.debug("Removed: \"{}\"".format(filepath))
|
||||
|
||||
def _get_burnin_options(self):
|
||||
"""Get the burnin options from `ExtractBurnin` settings.
|
||||
|
||||
Returns:
|
||||
dict[str, Any]: Burnin options.
|
||||
|
||||
"""
|
||||
# Prepare burnin options
|
||||
burnin_options = copy.deepcopy(self.default_options)
|
||||
if self.options:
|
||||
|
|
@ -697,7 +703,7 @@ class ExtractBurnin(publish.Extractor):
|
|||
"""Prepare data for representation.
|
||||
|
||||
Args:
|
||||
instance (Instance): Currently processed Instance.
|
||||
instance (pyblish.api.Instance): Currently processed Instance.
|
||||
repre (dict): Currently processed representation.
|
||||
burnin_data (dict): Copy of basic burnin data based on instance
|
||||
data.
|
||||
|
|
@ -753,9 +759,11 @@ class ExtractBurnin(publish.Extractor):
|
|||
|
||||
Args:
|
||||
profile (dict): Profile from presets matching current context.
|
||||
instance (pyblish.api.Instance): Publish instance.
|
||||
|
||||
Returns:
|
||||
list: Contain all valid output definitions.
|
||||
list[dict[str, Any]]: Contain all valid output definitions.
|
||||
|
||||
"""
|
||||
filtered_burnin_defs = []
|
||||
|
||||
|
|
@ -774,12 +782,11 @@ class ExtractBurnin(publish.Extractor):
|
|||
if not self.families_filter_validation(
|
||||
families, families_filters
|
||||
):
|
||||
self.log.debug((
|
||||
"Skipped burnin definition \"{}\". Family"
|
||||
" filters ({}) does not match current instance families: {}"
|
||||
).format(
|
||||
filename_suffix, str(families_filters), str(families)
|
||||
))
|
||||
self.log.debug(
|
||||
f"Skipped burnin definition \"{filename_suffix}\"."
|
||||
f" Family filters ({families_filters}) does not match"
|
||||
f" current instance families: {families}"
|
||||
)
|
||||
continue
|
||||
|
||||
# Burnin values
|
||||
|
|
|
|||
|
|
@ -122,13 +122,22 @@ class ExtractOIIOTranscode(publish.Extractor):
|
|||
transcoding_type = output_def["transcoding_type"]
|
||||
|
||||
target_colorspace = view = display = None
|
||||
# NOTE: we use colorspace_data as the fallback values for
|
||||
# the target colorspace.
|
||||
if transcoding_type == "colorspace":
|
||||
# TODO: Should we fallback to the colorspace
|
||||
# (which used as source above) ?
|
||||
# or should we compute the target colorspace from
|
||||
# current view and display ?
|
||||
target_colorspace = (output_def["colorspace"] or
|
||||
colorspace_data.get("colorspace"))
|
||||
else:
|
||||
view = output_def["view"] or colorspace_data.get("view")
|
||||
display = (output_def["display"] or
|
||||
colorspace_data.get("display"))
|
||||
elif transcoding_type == "display_view":
|
||||
display_view = output_def["display_view"]
|
||||
view = display_view["view"] or colorspace_data.get("view")
|
||||
display = (
|
||||
display_view["display"]
|
||||
or colorspace_data.get("display")
|
||||
)
|
||||
|
||||
# both could be already collected by DCC,
|
||||
# but could be overwritten when transcoding
|
||||
|
|
@ -192,7 +201,7 @@ class ExtractOIIOTranscode(publish.Extractor):
|
|||
new_repre["files"] = new_repre["files"][0]
|
||||
|
||||
# If the source representation has "review" tag, but its not
|
||||
# part of the output defintion tags, then both the
|
||||
# part of the output definition tags, then both the
|
||||
# representations will be transcoded in ExtractReview and
|
||||
# their outputs will clash in integration.
|
||||
if "review" in repre.get("tags", []):
|
||||
|
|
|
|||
|
|
@ -49,7 +49,6 @@ class ExtractOTIOReview(publish.Extractor):
|
|||
hosts = ["resolve", "hiero", "flame"]
|
||||
|
||||
# plugin default attributes
|
||||
temp_file_head = "tempFile."
|
||||
to_width = 1280
|
||||
to_height = 720
|
||||
output_ext = ".jpg"
|
||||
|
|
@ -58,10 +57,14 @@ class ExtractOTIOReview(publish.Extractor):
|
|||
# Not all hosts can import these modules.
|
||||
import opentimelineio as otio
|
||||
from ayon_core.pipeline.editorial import (
|
||||
otio_range_to_frame_range,
|
||||
make_sequence_collection
|
||||
make_sequence_collection,
|
||||
remap_range_on_file_sequence,
|
||||
is_clip_from_media_sequence
|
||||
)
|
||||
|
||||
# TODO refactor from using instance variable
|
||||
self.temp_file_head = self._get_folder_name_based_prefix(instance)
|
||||
|
||||
# TODO: convert resulting image sequence to mp4
|
||||
|
||||
# get otio clip and other time info from instance clip
|
||||
|
|
@ -71,8 +74,8 @@ class ExtractOTIOReview(publish.Extractor):
|
|||
otio_review_clips = instance.data["otioReviewClips"]
|
||||
|
||||
# add plugin wide attributes
|
||||
self.representation_files = list()
|
||||
self.used_frames = list()
|
||||
self.representation_files = []
|
||||
self.used_frames = []
|
||||
self.workfile_start = int(instance.data.get(
|
||||
"workfileFrameStart", 1001)) - handle_start
|
||||
self.padding = len(str(self.workfile_start))
|
||||
|
|
@ -97,84 +100,110 @@ class ExtractOTIOReview(publish.Extractor):
|
|||
for index, r_otio_cl in enumerate(otio_review_clips):
|
||||
# QUESTION: what if transition on clip?
|
||||
|
||||
# check if resolution is the same
|
||||
width = self.to_width
|
||||
height = self.to_height
|
||||
otio_media = r_otio_cl.media_reference
|
||||
media_metadata = otio_media.metadata
|
||||
|
||||
# get from media reference metadata source
|
||||
if media_metadata.get("openpype.source.width"):
|
||||
width = int(media_metadata.get("openpype.source.width"))
|
||||
if media_metadata.get("openpype.source.height"):
|
||||
height = int(media_metadata.get("openpype.source.height"))
|
||||
|
||||
# compare and reset
|
||||
if width != self.to_width:
|
||||
self.to_width = width
|
||||
if height != self.to_height:
|
||||
self.to_height = height
|
||||
|
||||
self.log.debug("> self.to_width x self.to_height: {} x {}".format(
|
||||
self.to_width, self.to_height
|
||||
))
|
||||
|
||||
# get frame range values
|
||||
# Clip: compute process range from available media range.
|
||||
src_range = r_otio_cl.source_range
|
||||
start = src_range.start_time.value
|
||||
duration = src_range.duration.value
|
||||
available_range = None
|
||||
self.actual_fps = src_range.duration.rate
|
||||
|
||||
# add available range only if not gap
|
||||
if isinstance(r_otio_cl, otio.schema.Clip):
|
||||
# check if resolution is the same as source
|
||||
media_ref = r_otio_cl.media_reference
|
||||
media_metadata = media_ref.metadata
|
||||
|
||||
# get from media reference metadata source
|
||||
# TODO 'openpype' prefix should be removed (added 24/09/03)
|
||||
# NOTE it looks like it is set only in hiero integration
|
||||
res_data = {"width": self.to_width, "height": self.to_height}
|
||||
for key in res_data:
|
||||
for meta_prefix in ("ayon.source.", "openpype.source."):
|
||||
meta_key = f"{meta_prefix}.{key}"
|
||||
value = media_metadata.get(meta_key)
|
||||
if value is not None:
|
||||
res_data[key] = value
|
||||
break
|
||||
|
||||
self.to_width, self.to_height = res_data["width"], res_data["height"]
|
||||
self.log.debug("> self.to_width x self.to_height: {} x {}".format(
|
||||
self.to_width, self.to_height
|
||||
))
|
||||
|
||||
available_range = r_otio_cl.available_range()
|
||||
processing_range = None
|
||||
self.actual_fps = available_range.duration.rate
|
||||
start = src_range.start_time.rescaled_to(self.actual_fps)
|
||||
duration = src_range.duration.rescaled_to(self.actual_fps)
|
||||
|
||||
# Temporary.
|
||||
# Some AYON custom OTIO exporter were implemented with relative
|
||||
# source range for image sequence. Following code maintain
|
||||
# backward-compatibility by adjusting available range
|
||||
# while we are updating those.
|
||||
if (
|
||||
is_clip_from_media_sequence(r_otio_cl)
|
||||
and available_range.start_time.to_frames() == media_ref.start_frame
|
||||
and src_range.start_time.to_frames() < media_ref.start_frame
|
||||
):
|
||||
available_range = otio.opentime.TimeRange(
|
||||
otio.opentime.RationalTime(0, rate=self.actual_fps),
|
||||
available_range.duration,
|
||||
)
|
||||
|
||||
# Gap: no media, generate range based on source range
|
||||
else:
|
||||
available_range = processing_range = None
|
||||
self.actual_fps = src_range.duration.rate
|
||||
start = src_range.start_time
|
||||
duration = src_range.duration
|
||||
|
||||
# Create handle offsets.
|
||||
clip_handle_start = otio.opentime.RationalTime(
|
||||
handle_start,
|
||||
rate=self.actual_fps,
|
||||
)
|
||||
clip_handle_end = otio.opentime.RationalTime(
|
||||
handle_end,
|
||||
rate=self.actual_fps,
|
||||
)
|
||||
|
||||
# reframing handles conditions
|
||||
if (len(otio_review_clips) > 1) and (index == 0):
|
||||
# more clips | first clip reframing with handle
|
||||
start -= handle_start
|
||||
duration += handle_start
|
||||
start -= clip_handle_start
|
||||
duration += clip_handle_start
|
||||
elif len(otio_review_clips) > 1 \
|
||||
and (index == len(otio_review_clips) - 1):
|
||||
# more clips | last clip reframing with handle
|
||||
duration += handle_end
|
||||
duration += clip_handle_end
|
||||
elif len(otio_review_clips) == 1:
|
||||
# one clip | add both handles
|
||||
start -= handle_start
|
||||
duration += (handle_start + handle_end)
|
||||
start -= clip_handle_start
|
||||
duration += (clip_handle_start + clip_handle_end)
|
||||
|
||||
if available_range:
|
||||
available_range = self._trim_available_range(
|
||||
available_range, start, duration, self.actual_fps)
|
||||
processing_range = self._trim_available_range(
|
||||
available_range, start, duration)
|
||||
|
||||
# process all track items of the track
|
||||
if isinstance(r_otio_cl, otio.schema.Clip):
|
||||
# process Clip
|
||||
media_ref = r_otio_cl.media_reference
|
||||
metadata = media_ref.metadata
|
||||
is_sequence = None
|
||||
|
||||
# check in two way if it is sequence
|
||||
if hasattr(otio.schema, "ImageSequenceReference"):
|
||||
# for OpenTimelineIO 0.13 and newer
|
||||
if isinstance(media_ref,
|
||||
otio.schema.ImageSequenceReference):
|
||||
is_sequence = True
|
||||
else:
|
||||
# for OpenTimelineIO 0.12 and older
|
||||
if metadata.get("padding"):
|
||||
is_sequence = True
|
||||
is_sequence = is_clip_from_media_sequence(r_otio_cl)
|
||||
|
||||
# File sequence way
|
||||
if is_sequence:
|
||||
# file sequence way
|
||||
# Remap processing range to input file sequence.
|
||||
processing_range_as_frames = (
|
||||
processing_range.start_time.to_frames(),
|
||||
processing_range.end_time_inclusive().to_frames()
|
||||
)
|
||||
first, last = remap_range_on_file_sequence(
|
||||
r_otio_cl,
|
||||
processing_range_as_frames,
|
||||
)
|
||||
input_fps = processing_range.start_time.rate
|
||||
|
||||
if hasattr(media_ref, "target_url_base"):
|
||||
dirname = media_ref.target_url_base
|
||||
head = media_ref.name_prefix
|
||||
tail = media_ref.name_suffix
|
||||
first, last = otio_range_to_frame_range(
|
||||
available_range)
|
||||
collection = clique.Collection(
|
||||
head=head,
|
||||
tail=tail,
|
||||
|
|
@ -183,8 +212,8 @@ class ExtractOTIOReview(publish.Extractor):
|
|||
collection.indexes.update(
|
||||
[i for i in range(first, (last + 1))])
|
||||
# render segment
|
||||
self._render_seqment(
|
||||
sequence=[dirname, collection])
|
||||
self._render_segment(
|
||||
sequence=[dirname, collection, input_fps])
|
||||
# generate used frames
|
||||
self._generate_used_frames(
|
||||
len(collection.indexes))
|
||||
|
|
@ -193,30 +222,44 @@ class ExtractOTIOReview(publish.Extractor):
|
|||
# `ImageSequenceReference`
|
||||
path = media_ref.target_url
|
||||
collection_data = make_sequence_collection(
|
||||
path, available_range, metadata)
|
||||
path, processing_range, metadata)
|
||||
dir_path, collection = collection_data
|
||||
|
||||
# render segment
|
||||
self._render_seqment(
|
||||
sequence=[dir_path, collection])
|
||||
self._render_segment(
|
||||
sequence=[dir_path, collection, input_fps])
|
||||
# generate used frames
|
||||
self._generate_used_frames(
|
||||
len(collection.indexes))
|
||||
|
||||
# Single video way.
|
||||
# Extraction via FFmpeg.
|
||||
else:
|
||||
# single video file way
|
||||
path = media_ref.target_url
|
||||
# Set extract range from 0 (FFmpeg ignores embedded timecode).
|
||||
extract_range = otio.opentime.TimeRange(
|
||||
otio.opentime.RationalTime(
|
||||
(
|
||||
processing_range.start_time.value
|
||||
- available_range.start_time.value
|
||||
),
|
||||
rate=available_range.start_time.rate,
|
||||
),
|
||||
duration=processing_range.duration,
|
||||
)
|
||||
# render video file to sequence
|
||||
self._render_seqment(
|
||||
video=[path, available_range])
|
||||
self._render_segment(
|
||||
video=[path, extract_range])
|
||||
# generate used frames
|
||||
self._generate_used_frames(
|
||||
available_range.duration.value)
|
||||
processing_range.duration.value)
|
||||
|
||||
# QUESTION: what if nested track composition is in place?
|
||||
else:
|
||||
# at last process a Gap
|
||||
self._render_seqment(gap=duration)
|
||||
self._render_segment(gap=duration.to_frames())
|
||||
# generate used frames
|
||||
self._generate_used_frames(duration)
|
||||
self._generate_used_frames(duration.to_frames())
|
||||
|
||||
# creating and registering representation
|
||||
representation = self._create_representation(start, duration)
|
||||
|
|
@ -265,7 +308,7 @@ class ExtractOTIOReview(publish.Extractor):
|
|||
})
|
||||
return representation_data
|
||||
|
||||
def _trim_available_range(self, avl_range, start, duration, fps):
|
||||
def _trim_available_range(self, avl_range, start, duration):
|
||||
"""
|
||||
Trim available media range to source range.
|
||||
|
||||
|
|
@ -274,69 +317,87 @@ class ExtractOTIOReview(publish.Extractor):
|
|||
|
||||
Args:
|
||||
avl_range (otio.time.TimeRange): media available time range
|
||||
start (int): start frame
|
||||
duration (int): duration frames
|
||||
fps (float): frame rate
|
||||
start (otio.time.RationalTime): start
|
||||
duration (otio.time.RationalTime): duration
|
||||
|
||||
Returns:
|
||||
otio.time.TimeRange: trimmed available range
|
||||
"""
|
||||
# Not all hosts can import these modules.
|
||||
import opentimelineio as otio
|
||||
from ayon_core.pipeline.editorial import (
|
||||
trim_media_range,
|
||||
range_from_frames
|
||||
)
|
||||
|
||||
avl_start = int(avl_range.start_time.value)
|
||||
src_start = int(avl_start + start)
|
||||
avl_durtation = int(avl_range.duration.value)
|
||||
def _round_to_frame(rational_time):
|
||||
""" Handle rounding duration to frame.
|
||||
"""
|
||||
# OpentimelineIO >= 0.16.0
|
||||
try:
|
||||
return rational_time.round().to_frames()
|
||||
|
||||
self.need_offset = bool(avl_start != 0 and src_start != 0)
|
||||
# OpentimelineIO < 0.16.0
|
||||
except AttributeError:
|
||||
return otio.opentime.RationalTime(
|
||||
round(rational_time.value),
|
||||
rate=rational_time.rate,
|
||||
).to_frames()
|
||||
|
||||
# if media start is les then clip requires
|
||||
if src_start < avl_start:
|
||||
# calculate gap
|
||||
gap_duration = avl_start - src_start
|
||||
avl_start = avl_range.start_time
|
||||
|
||||
# An additional gap is required before the available
|
||||
# range to conform source start point and head handles.
|
||||
if start < avl_start:
|
||||
gap_duration = avl_start - start
|
||||
start = avl_start
|
||||
duration -= gap_duration
|
||||
gap_duration = _round_to_frame(gap_duration)
|
||||
|
||||
# create gap data to disk
|
||||
self._render_seqment(gap=gap_duration)
|
||||
self._render_segment(gap=gap_duration)
|
||||
# generate used frames
|
||||
self._generate_used_frames(gap_duration)
|
||||
|
||||
# fix start and end to correct values
|
||||
start = 0
|
||||
# An additional gap is required after the available
|
||||
# range to conform to source end point + tail handles
|
||||
# (media duration is shorter then clip requirement).
|
||||
end_point = start + duration
|
||||
avl_end_point = avl_range.end_time_exclusive()
|
||||
if end_point > avl_end_point:
|
||||
gap_duration = end_point - avl_end_point
|
||||
duration -= gap_duration
|
||||
|
||||
# if media duration is shorter then clip requirement
|
||||
if duration > avl_durtation:
|
||||
# calculate gap
|
||||
gap_start = int(src_start + avl_durtation)
|
||||
gap_end = int(src_start + duration)
|
||||
gap_duration = gap_end - gap_start
|
||||
gap_duration = _round_to_frame(gap_duration)
|
||||
|
||||
# create gap data to disk
|
||||
self._render_seqment(gap=gap_duration, end_offset=avl_durtation)
|
||||
self._render_segment(
|
||||
gap=gap_duration,
|
||||
end_offset=duration.to_frames()
|
||||
)
|
||||
# generate used frames
|
||||
self._generate_used_frames(gap_duration, end_offset=avl_durtation)
|
||||
|
||||
# fix duration lenght
|
||||
duration = avl_durtation
|
||||
self._generate_used_frames(
|
||||
gap_duration,
|
||||
end_offset=duration.to_frames()
|
||||
)
|
||||
|
||||
# return correct trimmed range
|
||||
return trim_media_range(
|
||||
avl_range, range_from_frames(start, duration, fps)
|
||||
avl_range,
|
||||
otio.opentime.TimeRange(
|
||||
start,
|
||||
duration
|
||||
)
|
||||
)
|
||||
|
||||
def _render_seqment(self, sequence=None,
|
||||
def _render_segment(self, sequence=None,
|
||||
video=None, gap=None, end_offset=None):
|
||||
"""
|
||||
Render seqment into image sequence frames.
|
||||
Render segment into image sequence frames.
|
||||
|
||||
Using ffmpeg to convert compatible video and image source
|
||||
to defined image sequence format.
|
||||
|
||||
Args:
|
||||
sequence (list): input dir path string, collection object in list
|
||||
sequence (list): input dir path string, collection object, fps in list
|
||||
video (list)[optional]: video_path string, otio_range in list
|
||||
gap (int)[optional]: gap duration
|
||||
end_offset (int)[optional]: offset gap frame start in frames
|
||||
|
|
@ -358,7 +419,7 @@ class ExtractOTIOReview(publish.Extractor):
|
|||
|
||||
input_extension = None
|
||||
if sequence:
|
||||
input_dir, collection = sequence
|
||||
input_dir, collection, sequence_fps = sequence
|
||||
in_frame_start = min(collection.indexes)
|
||||
|
||||
# converting image sequence to image sequence
|
||||
|
|
@ -366,9 +427,28 @@ class ExtractOTIOReview(publish.Extractor):
|
|||
input_path = os.path.join(input_dir, input_file)
|
||||
input_extension = os.path.splitext(input_path)[-1]
|
||||
|
||||
# form command for rendering gap files
|
||||
"""
|
||||
Form Command for Rendering Sequence Files
|
||||
|
||||
To explicitly set the input frame range and preserve the frame
|
||||
range, avoid silent dropped frames caused by input mismatch
|
||||
with FFmpeg's default rate of 25.0 fps. For more info,
|
||||
refer to the FFmpeg image2 demuxer.
|
||||
|
||||
Implicit:
|
||||
- Input: 100 frames (24fps from metadata)
|
||||
- Demuxer: video 25fps
|
||||
- Output: 98 frames, dropped 2
|
||||
|
||||
Explicit with "-framerate":
|
||||
- Input: 100 frames (24fps from metadata)
|
||||
- Demuxer: video 24fps
|
||||
- Output: 100 frames, no dropped frames
|
||||
"""
|
||||
|
||||
command.extend([
|
||||
"-start_number", str(in_frame_start),
|
||||
"-framerate", str(sequence_fps),
|
||||
"-i", input_path
|
||||
])
|
||||
|
||||
|
|
@ -443,16 +523,11 @@ class ExtractOTIOReview(publish.Extractor):
|
|||
|
||||
padding = "{{:0{}d}}".format(self.padding)
|
||||
|
||||
# create frame offset
|
||||
offset = 0
|
||||
if self.need_offset:
|
||||
offset = 1
|
||||
|
||||
if end_offset:
|
||||
new_frames = list()
|
||||
start_frame = self.used_frames[-1]
|
||||
for index in range((end_offset + offset),
|
||||
(int(end_offset + duration) + offset)):
|
||||
for index in range(end_offset,
|
||||
(int(end_offset + duration))):
|
||||
seq_number = padding.format(start_frame + index)
|
||||
self.log.debug(
|
||||
"index: `{}` | seq_number: `{}`".format(index, seq_number))
|
||||
|
|
@ -491,3 +566,20 @@ class ExtractOTIOReview(publish.Extractor):
|
|||
out_frame_start = self.used_frames[-1]
|
||||
|
||||
return output_path, out_frame_start
|
||||
|
||||
def _get_folder_name_based_prefix(self, instance):
|
||||
"""Creates 'unique' human readable file prefix to differentiate.
|
||||
|
||||
Multiple instances might share same temp folder, but each instance
|
||||
would be differentiated by asset, eg. folder name.
|
||||
|
||||
It ix expected that there won't be multiple instances for same asset.
|
||||
"""
|
||||
folder_path = instance.data["folderPath"]
|
||||
folder_name = folder_path.split("/")[-1]
|
||||
folder_path = folder_path.replace("/", "_").lstrip("_")
|
||||
|
||||
file_prefix = f"{folder_path}_{folder_name}."
|
||||
self.log.debug(f"file_prefix::{file_prefix}")
|
||||
|
||||
return file_prefix
|
||||
|
|
|
|||
|
|
@ -74,9 +74,6 @@ class ExtractOTIOTrimmingVideo(publish.Extractor):
|
|||
otio_range (opentime.TimeRange): range to trim to
|
||||
|
||||
"""
|
||||
# Not all hosts can import this module.
|
||||
from ayon_core.pipeline.editorial import frames_to_seconds
|
||||
|
||||
# create path to destination
|
||||
output_path = self._get_ffmpeg_output(input_file_path)
|
||||
|
||||
|
|
@ -84,11 +81,8 @@ class ExtractOTIOTrimmingVideo(publish.Extractor):
|
|||
command = get_ffmpeg_tool_args("ffmpeg")
|
||||
|
||||
video_path = input_file_path
|
||||
frame_start = otio_range.start_time.value
|
||||
input_fps = otio_range.start_time.rate
|
||||
frame_duration = otio_range.duration.value - 1
|
||||
sec_start = frames_to_seconds(frame_start, input_fps)
|
||||
sec_duration = frames_to_seconds(frame_duration, input_fps)
|
||||
sec_start = otio_range.start_time.to_seconds()
|
||||
sec_duration = otio_range.duration.to_seconds()
|
||||
|
||||
# form command for rendering gap files
|
||||
command.extend([
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ class ExtractReview(pyblish.api.InstancePlugin):
|
|||
]
|
||||
|
||||
# Supported extensions
|
||||
image_exts = ["exr", "jpg", "jpeg", "png", "dpx", "tga"]
|
||||
image_exts = ["exr", "jpg", "jpeg", "png", "dpx", "tga", "tiff", "tif"]
|
||||
video_exts = ["mov", "mp4"]
|
||||
supported_exts = image_exts + video_exts
|
||||
|
||||
|
|
@ -1901,7 +1901,7 @@ class OverscanCrop:
|
|||
string_value = re.sub(r"([ ]+)?px", " ", string_value)
|
||||
string_value = re.sub(r"([ ]+)%", "%", string_value)
|
||||
# Make sure +/- sign at the beginning of string is next to number
|
||||
string_value = re.sub(r"^([\+\-])[ ]+", "\g<1>", string_value)
|
||||
string_value = re.sub(r"^([\+\-])[ ]+", r"\g<1>", string_value)
|
||||
# Make sure +/- sign in the middle has zero spaces before number under
|
||||
# which belongs
|
||||
string_value = re.sub(
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ def get_representation_path_in_publish_context(
|
|||
|
||||
Allow resolving 'latest' paths from a publishing context's instances
|
||||
as if they will exist after publishing without them being integrated yet.
|
||||
|
||||
|
||||
Use first instance that has same folder path and product name,
|
||||
and contains representation with passed name.
|
||||
|
||||
|
|
@ -238,7 +238,7 @@ def add_representation(instance, name,
|
|||
|
||||
|
||||
class CollectUSDLayerContributions(pyblish.api.InstancePlugin,
|
||||
publish.OpenPypePyblishPluginMixin):
|
||||
publish.AYONPyblishPluginMixin):
|
||||
"""Collect the USD Layer Contributions and create dependent instances.
|
||||
|
||||
Our contributions go to the layer
|
||||
|
|
|
|||
|
|
@ -509,8 +509,11 @@ class IntegrateAsset(pyblish.api.InstancePlugin):
|
|||
if not is_sequence_representation:
|
||||
files = [files]
|
||||
|
||||
if any(os.path.isabs(fname) for fname in files):
|
||||
raise KnownPublishError("Given file names contain full paths")
|
||||
for fname in files:
|
||||
if os.path.isabs(fname):
|
||||
raise KnownPublishError(
|
||||
f"Representation file names contains full paths: {fname}"
|
||||
)
|
||||
|
||||
if not is_sequence_representation:
|
||||
return
|
||||
|
|
|
|||
|
|
@ -9,7 +9,14 @@ from ayon_api import (
|
|||
|
||||
|
||||
class IntegrateInputLinksAYON(pyblish.api.ContextPlugin):
|
||||
"""Connecting version level dependency links"""
|
||||
"""Connecting version level dependency links
|
||||
|
||||
Handles links:
|
||||
- generative - what gets produced from workfile
|
||||
- reference - what was loaded into workfile
|
||||
|
||||
It expects workfile instance is being published.
|
||||
"""
|
||||
|
||||
order = pyblish.api.IntegratorOrder + 0.2
|
||||
label = "Connect Dependency InputLinks AYON"
|
||||
|
|
@ -47,6 +54,11 @@ class IntegrateInputLinksAYON(pyblish.api.ContextPlugin):
|
|||
self.create_links_on_server(context, new_links_by_type)
|
||||
|
||||
def split_instances(self, context):
|
||||
"""Separates published instances into workfile and other
|
||||
|
||||
Returns:
|
||||
(tuple(pyblish.plugin.Instance), list(pyblish.plugin.Instance))
|
||||
"""
|
||||
workfile_instance = None
|
||||
other_instances = []
|
||||
|
||||
|
|
@ -83,6 +95,15 @@ class IntegrateInputLinksAYON(pyblish.api.ContextPlugin):
|
|||
def create_workfile_links(
|
||||
self, workfile_instance, other_instances, new_links_by_type
|
||||
):
|
||||
"""Adds links (generative and reference) for workfile.
|
||||
|
||||
Args:
|
||||
workfile_instance (pyblish.plugin.Instance): published workfile
|
||||
other_instances (list[pyblish.plugin.Instance]): other published
|
||||
instances
|
||||
new_links_by_type (dict[str, list[str]]): dictionary collecting new
|
||||
created links by its type
|
||||
"""
|
||||
if workfile_instance is None:
|
||||
self.log.warn("No workfile in this publish session.")
|
||||
return
|
||||
|
|
@ -97,7 +118,7 @@ class IntegrateInputLinksAYON(pyblish.api.ContextPlugin):
|
|||
instance.data["versionEntity"]["id"],
|
||||
)
|
||||
|
||||
loaded_versions = workfile_instance.context.get("loadedVersions")
|
||||
loaded_versions = workfile_instance.context.data.get("loadedVersions")
|
||||
if not loaded_versions:
|
||||
return
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,8 @@ class ValidateCurrentSaveFile(pyblish.api.ContextPlugin):
|
|||
|
||||
label = "Validate File Saved"
|
||||
order = pyblish.api.ValidatorOrder - 0.1
|
||||
hosts = ["fusion", "houdini", "max", "maya", "nuke", "substancepainter"]
|
||||
hosts = ["fusion", "houdini", "max", "maya", "nuke", "substancepainter",
|
||||
"cinema4d"]
|
||||
actions = [SaveByVersionUpAction, ShowWorkfilesAction]
|
||||
|
||||
def process(self, context):
|
||||
|
|
@ -51,9 +52,9 @@ class ValidateCurrentSaveFile(pyblish.api.ContextPlugin):
|
|||
def get_description(self):
|
||||
return inspect.cleandoc("""
|
||||
### File not saved
|
||||
|
||||
|
||||
Your workfile must be saved to continue publishing.
|
||||
|
||||
The **Save Workfile** action will save it for you with the first
|
||||
|
||||
The **Save Workfile** action will save it for you with the first
|
||||
available workfile version number in your current context.
|
||||
""")
|
||||
|
|
|
|||
|
|
@ -70,19 +70,3 @@ def get_ayon_splash_filepath(staging=None):
|
|||
else:
|
||||
splash_file_name = "AYON_splash.png"
|
||||
return get_resource("icons", splash_file_name)
|
||||
|
||||
|
||||
def get_openpype_production_icon_filepath():
|
||||
return get_ayon_production_icon_filepath()
|
||||
|
||||
|
||||
def get_openpype_staging_icon_filepath():
|
||||
return get_ayon_staging_icon_filepath()
|
||||
|
||||
|
||||
def get_openpype_icon_filepath(staging=None):
|
||||
return get_ayon_icon_filepath(staging)
|
||||
|
||||
|
||||
def get_openpype_splash_filepath(staging=None):
|
||||
return get_ayon_splash_filepath(staging)
|
||||
|
|
|
|||
|
|
@ -486,11 +486,11 @@ class TableField(BaseItem):
|
|||
line = self.ellide_text
|
||||
break
|
||||
|
||||
for idx, char in enumerate(_word):
|
||||
for char_index, char in enumerate(_word):
|
||||
_line = line + char + self.ellide_text
|
||||
_line_width = font.getsize(_line)[0]
|
||||
if _line_width > max_width:
|
||||
if idx == 0:
|
||||
if char_index == 0:
|
||||
line = _line
|
||||
break
|
||||
line = line + char
|
||||
|
|
|
|||
|
|
@ -1,79 +0,0 @@
|
|||
# Structure of local settings
|
||||
- local settings do not have any validation schemas right now this should help to see what is stored to local settings and how it works
|
||||
- they are stored by identifier site_id which should be unified identifier of workstation
|
||||
- all keys may and may not available on load
|
||||
- contain main categories: `general`, `applications`, `projects`
|
||||
|
||||
## Categories
|
||||
### General
|
||||
- ATM contain only label of site
|
||||
```json
|
||||
{
|
||||
"general": {
|
||||
"site_label": "MySite"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Applications
|
||||
- modifications of application executables
|
||||
- output should match application groups and variants
|
||||
```json
|
||||
{
|
||||
"applications": {
|
||||
"<app group>": {
|
||||
"<app name>": {
|
||||
"executable": "/my/path/to/nuke_12_2"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Projects
|
||||
- project specific modifications
|
||||
- default project is stored under constant key defined in `pype.settings.contants`
|
||||
```json
|
||||
{
|
||||
"projects": {
|
||||
"<project name>": {
|
||||
"active_site": "<name of active site>",
|
||||
"remote_site": "<name of remote site>",
|
||||
"roots": {
|
||||
"<site name>": {
|
||||
"<root name>": "<root dir path>"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Final document
|
||||
```json
|
||||
{
|
||||
"_id": "<ObjectId(...)>",
|
||||
"site_id": "<site id>",
|
||||
"general": {
|
||||
"site_label": "MySite"
|
||||
},
|
||||
"applications": {
|
||||
"<app group>": {
|
||||
"<app name>": {
|
||||
"executable": "<path to app executable>"
|
||||
}
|
||||
}
|
||||
},
|
||||
"projects": {
|
||||
"<project name>": {
|
||||
"active_site": "<name of active site>",
|
||||
"remote_site": "<name of remote site>",
|
||||
"roots": {
|
||||
"<site name>": {
|
||||
"<root name>": "<root dir path>"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -1118,39 +1118,39 @@ ValidationArtistMessage QLabel {
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
#ValidationActionButton {
|
||||
#PublishActionButton {
|
||||
border-radius: 0.2em;
|
||||
padding: 4px 6px 4px 6px;
|
||||
background: {color:bg-buttons};
|
||||
}
|
||||
|
||||
#ValidationActionButton:hover {
|
||||
#PublishActionButton:hover {
|
||||
background: {color:bg-buttons-hover};
|
||||
color: {color:font-hover};
|
||||
}
|
||||
|
||||
#ValidationActionButton:disabled {
|
||||
#PublishActionButton:disabled {
|
||||
background: {color:bg-buttons-disabled};
|
||||
}
|
||||
|
||||
#ValidationErrorTitleFrame {
|
||||
#PublishErrorTitleFrame {
|
||||
border-radius: 0.2em;
|
||||
background: {color:bg-buttons};
|
||||
}
|
||||
|
||||
#ValidationErrorTitleFrame:hover {
|
||||
#PublishErrorTitleFrame:hover {
|
||||
background: {color:bg-buttons-hover};
|
||||
}
|
||||
|
||||
#ValidationErrorTitleFrame[selected="1"] {
|
||||
#PublishErrorTitleFrame[selected="1"] {
|
||||
background: {color:bg-view-selection};
|
||||
}
|
||||
|
||||
#ValidationErrorInstanceList {
|
||||
#PublishErrorInstanceList {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
#ValidationErrorInstanceList::item {
|
||||
#PublishErrorInstanceList::item {
|
||||
border-bottom: 1px solid {color:border};
|
||||
border-left: 1px solid {color:border};
|
||||
}
|
||||
|
|
@ -1472,14 +1472,6 @@ CreateNextPageOverlay {
|
|||
border-radius: 5px;
|
||||
}
|
||||
|
||||
#OpenPypeVersionLabel[state="success"] {
|
||||
color: {color:settings:version-exists};
|
||||
}
|
||||
|
||||
#OpenPypeVersionLabel[state="warning"] {
|
||||
color: {color:settings:version-not-found};
|
||||
}
|
||||
|
||||
#ShadowWidget {
|
||||
font-size: 36pt;
|
||||
}
|
||||
|
|
|
|||
16
client/ayon_core/tests/conftest.py
Normal file
16
client/ayon_core/tests/conftest.py
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import pytest
|
||||
from pathlib import Path
|
||||
|
||||
collect_ignore = ["vendor", "resources"]
|
||||
|
||||
RESOURCES_PATH = 'resources'
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def resources_path_factory():
|
||||
def factory(*args):
|
||||
dirpath = Path(__file__).parent / RESOURCES_PATH
|
||||
for arg in args:
|
||||
dirpath = dirpath / arg
|
||||
return dirpath
|
||||
return factory
|
||||
52
client/ayon_core/tests/plugins/load/test_export_otio.py
Normal file
52
client/ayon_core/tests/plugins/load/test_export_otio.py
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import pytest
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from ayon_core.plugins.load.export_otio import get_image_info_metadata
|
||||
|
||||
logger = logging.getLogger('test_transcoding')
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"resources_path_factory, metadata, expected, test_id",
|
||||
[
|
||||
(
|
||||
Path(__file__).parent.parent
|
||||
/ "resources"
|
||||
/ "lib"
|
||||
/ "transcoding"
|
||||
/ "a01vfxd_sh010_plateP01_v002.1013.exr",
|
||||
["timecode", "framerate"],
|
||||
{"timecode": "01:00:06:03", "framerate": 23.976023976023978},
|
||||
"test_01",
|
||||
),
|
||||
(
|
||||
Path(__file__).parent.parent
|
||||
/ "resources"
|
||||
/ "lib"
|
||||
/ "transcoding"
|
||||
/ "a01vfxd_sh010_plateP01_v002.1013.exr",
|
||||
["timecode", "width", "height", "duration"],
|
||||
{"timecode": "01:00:06:03", "width": 1920, "height": 1080},
|
||||
"test_02",
|
||||
),
|
||||
(
|
||||
Path(__file__).parent.parent
|
||||
/ "resources"
|
||||
/ "lib"
|
||||
/ "transcoding"
|
||||
/ "a01vfxd_sh010_plateP01_v002.mov",
|
||||
["width", "height", "duration"],
|
||||
{"width": 1920, "height": 1080, "duration": "0.041708"},
|
||||
"test_03",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_get_image_info_metadata_happy_path(
|
||||
resources_path_factory, metadata, expected, test_id
|
||||
):
|
||||
path_to_file = resources_path_factory.as_posix()
|
||||
|
||||
returned_data = get_image_info_metadata(path_to_file, metadata, logger)
|
||||
logger.info(f"Returned data: {returned_data}")
|
||||
|
||||
assert returned_data == expected
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -1,6 +1,9 @@
|
|||
import collections
|
||||
|
||||
from ayon_api import get_representations, get_versions_links
|
||||
from ayon_api import (
|
||||
get_representations,
|
||||
get_versions_links,
|
||||
)
|
||||
|
||||
from ayon_core.lib import Logger, NestedCacheItem
|
||||
from ayon_core.addon import AddonsManager
|
||||
|
|
@ -509,18 +512,19 @@ class SiteSyncModel:
|
|||
"reference"
|
||||
)
|
||||
for link_repre_id in links:
|
||||
try:
|
||||
if not self._sitesync_addon.is_representation_on_site(
|
||||
project_name,
|
||||
link_repre_id,
|
||||
site_name
|
||||
):
|
||||
print("Adding {} to linked representation: {}".format(
|
||||
site_name, link_repre_id))
|
||||
self._sitesync_addon.add_site(
|
||||
project_name,
|
||||
link_repre_id,
|
||||
site_name,
|
||||
force=False
|
||||
force=True
|
||||
)
|
||||
except Exception:
|
||||
# do not add/reset working site for references
|
||||
log.debug("Site present", exc_info=True)
|
||||
|
||||
def _get_linked_representation_id(
|
||||
self,
|
||||
|
|
@ -575,7 +579,7 @@ class SiteSyncModel:
|
|||
project_name,
|
||||
versions_to_check,
|
||||
link_types=link_types,
|
||||
link_direction="out")
|
||||
link_direction="in") # looking for 'in'puts for version
|
||||
|
||||
versions_to_check = set()
|
||||
for links in versions_links.values():
|
||||
|
|
@ -584,9 +588,6 @@ class SiteSyncModel:
|
|||
if link["entityType"] != "version":
|
||||
continue
|
||||
entity_id = link["entityId"]
|
||||
# Skip already found linked version ids
|
||||
if entity_id in linked_version_ids:
|
||||
continue
|
||||
linked_version_ids.add(entity_id)
|
||||
versions_to_check.add(entity_id)
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ from ayon_core.tools.common_models import (
|
|||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .models import CreatorItem
|
||||
from .models import CreatorItem, PublishErrorInfo
|
||||
|
||||
|
||||
class CardMessageTypes:
|
||||
|
|
@ -322,6 +322,12 @@ class AbstractPublisherFrontend(AbstractPublisherCommon):
|
|||
) -> Dict[str, Union[CreatedInstance, None]]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_instances_context_info(
|
||||
self, instance_ids: Optional[Iterable[str]] = None
|
||||
):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_existing_product_names(self, folder_path: str) -> List[str]:
|
||||
pass
|
||||
|
|
@ -537,14 +543,13 @@ class AbstractPublisherFrontend(AbstractPublisherCommon):
|
|||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_publish_error_msg(self) -> Union[str, None]:
|
||||
def get_publish_error_info(self) -> Optional["PublishErrorInfo"]:
|
||||
"""Current error message which cause fail of publishing.
|
||||
|
||||
Returns:
|
||||
Union[str, None]: Message which will be showed to artist or
|
||||
None.
|
||||
"""
|
||||
Optional[PublishErrorInfo]: Error info or None.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
|
|
@ -552,7 +557,7 @@ class AbstractPublisherFrontend(AbstractPublisherCommon):
|
|||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_validation_errors(self):
|
||||
def get_publish_errors_report(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
|
|
|
|||
|
|
@ -190,6 +190,9 @@ class PublisherController(
|
|||
def get_instances_by_id(self, instance_ids=None):
|
||||
return self._create_model.get_instances_by_id(instance_ids)
|
||||
|
||||
def get_instances_context_info(self, instance_ids=None):
|
||||
return self._create_model.get_instances_context_info(instance_ids)
|
||||
|
||||
def get_convertor_items(self):
|
||||
return self._create_model.get_convertor_items()
|
||||
|
||||
|
|
@ -493,14 +496,14 @@ class PublisherController(
|
|||
def get_publish_progress(self):
|
||||
return self._publish_model.get_progress()
|
||||
|
||||
def get_publish_error_msg(self):
|
||||
return self._publish_model.get_error_msg()
|
||||
def get_publish_error_info(self):
|
||||
return self._publish_model.get_error_info()
|
||||
|
||||
def get_publish_report(self):
|
||||
return self._publish_model.get_publish_report()
|
||||
|
||||
def get_validation_errors(self):
|
||||
return self._publish_model.get_validation_errors()
|
||||
def get_publish_errors_report(self):
|
||||
return self._publish_model.get_publish_errors_report()
|
||||
|
||||
def set_comment(self, comment):
|
||||
"""Set comment from ui to pyblish context.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
from .create import CreateModel, CreatorItem
|
||||
from .publish import PublishModel
|
||||
from .publish import PublishModel, PublishErrorInfo
|
||||
|
||||
|
||||
__all__ = (
|
||||
|
|
@ -7,4 +7,5 @@ __all__ = (
|
|||
"CreatorItem",
|
||||
|
||||
"PublishModel",
|
||||
"PublishErrorInfo",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -306,6 +306,14 @@ class CreateModel:
|
|||
for instance_id in instance_ids
|
||||
}
|
||||
|
||||
def get_instances_context_info(
|
||||
self, instance_ids: Optional[Iterable[str]] = None
|
||||
):
|
||||
instances = self.get_instances_by_id(instance_ids).values()
|
||||
return self._create_context.get_instances_context_info(
|
||||
instances
|
||||
)
|
||||
|
||||
def get_convertor_items(self) -> Dict[str, ConvertorItem]:
|
||||
return self._create_context.convertor_items_by_id
|
||||
|
||||
|
|
|
|||
|
|
@ -1,21 +1,27 @@
|
|||
import uuid
|
||||
import copy
|
||||
import inspect
|
||||
import logging
|
||||
import traceback
|
||||
import collections
|
||||
from contextlib import contextmanager
|
||||
from functools import partial
|
||||
from typing import Optional, Dict, List, Union, Any, Iterable
|
||||
|
||||
import arrow
|
||||
import pyblish.plugin
|
||||
|
||||
from ayon_core.lib import env_value_to_bool
|
||||
from ayon_core.pipeline import (
|
||||
PublishValidationError,
|
||||
KnownPublishError,
|
||||
OptionalPyblishPluginMixin,
|
||||
)
|
||||
from ayon_core.pipeline.plugin_discover import DiscoverResult
|
||||
from ayon_core.pipeline.publish import get_publish_instance_label
|
||||
from ayon_core.pipeline.publish import (
|
||||
get_publish_instance_label,
|
||||
PublishError,
|
||||
)
|
||||
from ayon_core.tools.publisher.abstract import AbstractPublisherBackend
|
||||
|
||||
PUBLISH_EVENT_SOURCE = "publisher.publish.model"
|
||||
|
|
@ -23,6 +29,69 @@ PUBLISH_EVENT_SOURCE = "publisher.publish.model"
|
|||
PLUGIN_ORDER_OFFSET = 0.5
|
||||
|
||||
|
||||
class MessageHandler(logging.Handler):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.records = []
|
||||
|
||||
def clear_records(self):
|
||||
self.records = []
|
||||
|
||||
def emit(self, record):
|
||||
try:
|
||||
record.msg = record.getMessage()
|
||||
except Exception:
|
||||
record.msg = str(record.msg)
|
||||
self.records.append(record)
|
||||
|
||||
|
||||
class PublishErrorInfo:
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
is_unknown_error: bool,
|
||||
description: Optional[str] = None,
|
||||
title: Optional[str] = None,
|
||||
detail: Optional[str] = None,
|
||||
):
|
||||
self.message: str = message
|
||||
self.is_unknown_error = is_unknown_error
|
||||
self.description: str = description or message
|
||||
self.title: Optional[str] = title or "Unknown error"
|
||||
self.detail: Optional[str] = detail
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
if not isinstance(other, PublishErrorInfo):
|
||||
return False
|
||||
return (
|
||||
self.description == other.description
|
||||
and self.is_unknown_error == other.is_unknown_error
|
||||
and self.title == other.title
|
||||
and self.detail == other.detail
|
||||
)
|
||||
|
||||
def __ne__(self, other: Any) -> bool:
|
||||
return not self.__eq__(other)
|
||||
|
||||
@classmethod
|
||||
def from_exception(cls, exc) -> "PublishErrorInfo":
|
||||
if isinstance(exc, PublishError):
|
||||
return cls(
|
||||
exc.message,
|
||||
False,
|
||||
exc.description,
|
||||
title=exc.title,
|
||||
detail=exc.detail,
|
||||
)
|
||||
if isinstance(exc, KnownPublishError):
|
||||
msg = str(exc)
|
||||
else:
|
||||
msg = (
|
||||
"Something went wrong. Send report"
|
||||
" to your supervisor or Ynput team."
|
||||
)
|
||||
return cls(msg, True)
|
||||
|
||||
|
||||
class PublishReportMaker:
|
||||
"""Report for single publishing process.
|
||||
|
|
@ -479,10 +548,10 @@ class PublishPluginsProxy:
|
|||
)
|
||||
|
||||
|
||||
class ValidationErrorItem:
|
||||
"""Data driven validation error item.
|
||||
class PublishErrorItem:
|
||||
"""Data driven publish error item.
|
||||
|
||||
Prepared data container with information about validation error and it's
|
||||
Prepared data container with information about publish error and it's
|
||||
source plugin.
|
||||
|
||||
Can be converted to raw data and recreated should be used for controller
|
||||
|
|
@ -490,11 +559,11 @@ class ValidationErrorItem:
|
|||
|
||||
Args:
|
||||
instance_id (Optional[str]): Pyblish instance id to which is
|
||||
validation error connected.
|
||||
publish error connected.
|
||||
instance_label (Optional[str]): Prepared instance label.
|
||||
plugin_id (str): Pyblish plugin id which triggered the validation
|
||||
plugin_id (str): Pyblish plugin id which triggered the publish
|
||||
error. Id is generated using 'PublishPluginsProxy'.
|
||||
context_validation (bool): Error happened on context.
|
||||
is_context_plugin (bool): Error happened on context.
|
||||
title (str): Error title.
|
||||
description (str): Error description.
|
||||
detail (str): Error detail.
|
||||
|
|
@ -505,7 +574,8 @@ class ValidationErrorItem:
|
|||
instance_id: Optional[str],
|
||||
instance_label: Optional[str],
|
||||
plugin_id: str,
|
||||
context_validation: bool,
|
||||
is_context_plugin: bool,
|
||||
is_validation_error: bool,
|
||||
title: str,
|
||||
description: str,
|
||||
detail: str
|
||||
|
|
@ -513,7 +583,8 @@ class ValidationErrorItem:
|
|||
self.instance_id: Optional[str] = instance_id
|
||||
self.instance_label: Optional[str] = instance_label
|
||||
self.plugin_id: str = plugin_id
|
||||
self.context_validation: bool = context_validation
|
||||
self.is_context_plugin: bool = is_context_plugin
|
||||
self.is_validation_error: bool = is_validation_error
|
||||
self.title: str = title
|
||||
self.description: str = description
|
||||
self.detail: str = detail
|
||||
|
|
@ -529,7 +600,8 @@ class ValidationErrorItem:
|
|||
"instance_id": self.instance_id,
|
||||
"instance_label": self.instance_label,
|
||||
"plugin_id": self.plugin_id,
|
||||
"context_validation": self.context_validation,
|
||||
"is_context_plugin": self.is_context_plugin,
|
||||
"is_validation_error": self.is_validation_error,
|
||||
"title": self.title,
|
||||
"description": self.description,
|
||||
"detail": self.detail,
|
||||
|
|
@ -539,13 +611,13 @@ class ValidationErrorItem:
|
|||
def from_result(
|
||||
cls,
|
||||
plugin_id: str,
|
||||
error: PublishValidationError,
|
||||
error: PublishError,
|
||||
instance: Union[pyblish.api.Instance, None]
|
||||
):
|
||||
"""Create new object based on resukt from controller.
|
||||
|
||||
Returns:
|
||||
ValidationErrorItem: New object with filled data.
|
||||
PublishErrorItem: New object with filled data.
|
||||
"""
|
||||
|
||||
instance_label = None
|
||||
|
|
@ -561,6 +633,7 @@ class ValidationErrorItem:
|
|||
instance_label,
|
||||
plugin_id,
|
||||
instance is None,
|
||||
isinstance(error, PublishValidationError),
|
||||
error.title,
|
||||
error.description,
|
||||
error.detail,
|
||||
|
|
@ -571,11 +644,11 @@ class ValidationErrorItem:
|
|||
return cls(**data)
|
||||
|
||||
|
||||
class PublishValidationErrorsReport:
|
||||
"""Publish validation errors report that can be parsed to raw data.
|
||||
class PublishErrorsReport:
|
||||
"""Publish errors report that can be parsed to raw data.
|
||||
|
||||
Args:
|
||||
error_items (List[ValidationErrorItem]): List of validation errors.
|
||||
error_items (List[PublishErrorItem]): List of publish errors.
|
||||
plugin_action_items (Dict[str, List[PublishPluginActionItem]]): Action
|
||||
items by plugin id.
|
||||
|
||||
|
|
@ -584,7 +657,7 @@ class PublishValidationErrorsReport:
|
|||
self._error_items = error_items
|
||||
self._plugin_action_items = plugin_action_items
|
||||
|
||||
def __iter__(self) -> Iterable[ValidationErrorItem]:
|
||||
def __iter__(self) -> Iterable[PublishErrorItem]:
|
||||
for item in self._error_items:
|
||||
yield item
|
||||
|
||||
|
|
@ -658,7 +731,7 @@ class PublishValidationErrorsReport:
|
|||
@classmethod
|
||||
def from_data(
|
||||
cls, data: Dict[str, Any]
|
||||
) -> "PublishValidationErrorsReport":
|
||||
) -> "PublishErrorsReport":
|
||||
"""Recreate object from data.
|
||||
|
||||
Args:
|
||||
|
|
@ -666,11 +739,11 @@ class PublishValidationErrorsReport:
|
|||
using 'to_data' method.
|
||||
|
||||
Returns:
|
||||
PublishValidationErrorsReport: New object based on data.
|
||||
PublishErrorsReport: New object based on data.
|
||||
"""
|
||||
|
||||
error_items = [
|
||||
ValidationErrorItem.from_data(error_item)
|
||||
PublishErrorItem.from_data(error_item)
|
||||
for error_item in data["error_items"]
|
||||
]
|
||||
plugin_action_items = {}
|
||||
|
|
@ -682,12 +755,12 @@ class PublishValidationErrorsReport:
|
|||
return cls(error_items, plugin_action_items)
|
||||
|
||||
|
||||
class PublishValidationErrors:
|
||||
"""Object to keep track about validation errors by plugin."""
|
||||
class PublishErrors:
|
||||
"""Object to keep track about publish errors by plugin."""
|
||||
|
||||
def __init__(self):
|
||||
self._plugins_proxy: Union[PublishPluginsProxy, None] = None
|
||||
self._error_items: List[ValidationErrorItem] = []
|
||||
self._error_items: List[PublishErrorItem] = []
|
||||
self._plugin_action_items: Dict[
|
||||
str, List[PublishPluginActionItem]
|
||||
] = {}
|
||||
|
|
@ -713,29 +786,29 @@ class PublishValidationErrors:
|
|||
self._error_items = []
|
||||
self._plugin_action_items = {}
|
||||
|
||||
def create_report(self) -> PublishValidationErrorsReport:
|
||||
def create_report(self) -> PublishErrorsReport:
|
||||
"""Create report based on currently existing errors.
|
||||
|
||||
Returns:
|
||||
PublishValidationErrorsReport: Validation error report with all
|
||||
PublishErrorsReport: Publish error report with all
|
||||
error information and publish plugin action items.
|
||||
"""
|
||||
|
||||
return PublishValidationErrorsReport(
|
||||
return PublishErrorsReport(
|
||||
self._error_items, self._plugin_action_items
|
||||
)
|
||||
|
||||
def add_error(
|
||||
self,
|
||||
plugin: pyblish.api.Plugin,
|
||||
error: PublishValidationError,
|
||||
error: PublishError,
|
||||
instance: Union[pyblish.api.Instance, None]
|
||||
):
|
||||
"""Add error from pyblish result.
|
||||
|
||||
Args:
|
||||
plugin (pyblish.api.Plugin): Plugin which triggered error.
|
||||
error (PublishValidationError): Validation error.
|
||||
error (PublishError): Publish error.
|
||||
instance (Union[pyblish.api.Instance, None]): Instance on which was
|
||||
error raised or None if was raised on context.
|
||||
"""
|
||||
|
|
@ -750,7 +823,7 @@ class PublishValidationErrors:
|
|||
error.title = plugin_label
|
||||
|
||||
self._error_items.append(
|
||||
ValidationErrorItem.from_result(plugin_id, error, instance)
|
||||
PublishErrorItem.from_result(plugin_id, error, instance)
|
||||
)
|
||||
if plugin_id in self._plugin_action_items:
|
||||
return
|
||||
|
|
@ -796,12 +869,16 @@ class PublishModel:
|
|||
def __init__(self, controller: AbstractPublisherBackend):
|
||||
self._controller = controller
|
||||
|
||||
self._log_to_console: bool = env_value_to_bool(
|
||||
"AYON_PUBLISHER_PRINT_LOGS", default=False
|
||||
)
|
||||
|
||||
# Publishing should stop at validation stage
|
||||
self._publish_up_validation: bool = False
|
||||
self._publish_comment_is_set: bool = False
|
||||
|
||||
# Any other exception that happened during publishing
|
||||
self._publish_error_msg: Optional[str] = None
|
||||
self._publish_error_info: Optional[PublishErrorInfo] = None
|
||||
# Publishing is in progress
|
||||
self._publish_is_running: bool = False
|
||||
# Publishing is over validation order
|
||||
|
|
@ -824,10 +901,8 @@ class PublishModel:
|
|||
self._publish_context = None
|
||||
# Pyblish report
|
||||
self._publish_report: PublishReportMaker = PublishReportMaker()
|
||||
# Store exceptions of validation error
|
||||
self._publish_validation_errors: PublishValidationErrors = (
|
||||
PublishValidationErrors()
|
||||
)
|
||||
# Store exceptions of publish error
|
||||
self._publish_errors: PublishErrors = PublishErrors()
|
||||
|
||||
# This information is not much important for controller but for widget
|
||||
# which can change (and set) the comment.
|
||||
|
|
@ -845,13 +920,21 @@ class PublishModel:
|
|||
self._default_iterator()
|
||||
)
|
||||
|
||||
self._log_handler: MessageHandler = MessageHandler()
|
||||
|
||||
def reset(self):
|
||||
# Allow to change behavior during process lifetime
|
||||
self._log_to_console = env_value_to_bool(
|
||||
"AYON_PUBLISHER_PRINT_LOGS", default=False
|
||||
)
|
||||
|
||||
create_context = self._controller.get_create_context()
|
||||
|
||||
self._publish_up_validation = False
|
||||
self._publish_comment_is_set = False
|
||||
self._publish_has_started = False
|
||||
|
||||
self._set_publish_error_msg(None)
|
||||
self._set_publish_error_info(None)
|
||||
self._set_progress(0)
|
||||
self._set_is_running(False)
|
||||
self._set_has_validated(False)
|
||||
|
|
@ -881,7 +964,7 @@ class PublishModel:
|
|||
)
|
||||
for plugin in create_context.publish_plugins_mismatch_targets:
|
||||
self._publish_report.set_plugin_skipped(plugin.id)
|
||||
self._publish_validation_errors.reset(self._publish_plugins_proxy)
|
||||
self._publish_errors.reset(self._publish_plugins_proxy)
|
||||
|
||||
self._set_max_progress(len(publish_plugins))
|
||||
|
||||
|
|
@ -974,11 +1057,11 @@ class PublishModel:
|
|||
self._publish_context
|
||||
)
|
||||
|
||||
def get_validation_errors(self) -> PublishValidationErrorsReport:
|
||||
return self._publish_validation_errors.create_report()
|
||||
def get_publish_errors_report(self) -> PublishErrorsReport:
|
||||
return self._publish_errors.create_report()
|
||||
|
||||
def get_error_msg(self) -> Optional[str]:
|
||||
return self._publish_error_msg
|
||||
def get_error_info(self) -> Optional[PublishErrorInfo]:
|
||||
return self._publish_error_info
|
||||
|
||||
def set_comment(self, comment: str):
|
||||
# Ignore change of comment when publishing started
|
||||
|
|
@ -1077,9 +1160,9 @@ class PublishModel:
|
|||
{"value": value}
|
||||
)
|
||||
|
||||
def _set_publish_error_msg(self, value: Optional[str]):
|
||||
if self._publish_error_msg != value:
|
||||
self._publish_error_msg = value
|
||||
def _set_publish_error_info(self, value: Optional[PublishErrorInfo]):
|
||||
if self._publish_error_info != value:
|
||||
self._publish_error_info = value
|
||||
self._emit_event(
|
||||
"publish.publish_error.changed",
|
||||
{"value": value}
|
||||
|
|
@ -1214,43 +1297,68 @@ class PublishModel:
|
|||
self._set_progress(self._publish_max_progress)
|
||||
yield partial(self.stop_publish)
|
||||
|
||||
@contextmanager
|
||||
def _log_manager(self, plugin: pyblish.api.Plugin):
|
||||
root = logging.getLogger()
|
||||
if not self._log_to_console:
|
||||
plugin.log.propagate = False
|
||||
plugin.log.addHandler(self._log_handler)
|
||||
root.addHandler(self._log_handler)
|
||||
|
||||
try:
|
||||
if self._log_to_console:
|
||||
yield None
|
||||
else:
|
||||
yield self._log_handler
|
||||
|
||||
finally:
|
||||
if not self._log_to_console:
|
||||
plugin.log.propagate = True
|
||||
plugin.log.removeHandler(self._log_handler)
|
||||
root.removeHandler(self._log_handler)
|
||||
self._log_handler.clear_records()
|
||||
|
||||
def _process_and_continue(
|
||||
self,
|
||||
plugin: pyblish.api.Plugin,
|
||||
instance: pyblish.api.Instance
|
||||
):
|
||||
result = pyblish.plugin.process(
|
||||
plugin, self._publish_context, instance
|
||||
)
|
||||
with self._log_manager(plugin) as log_handler:
|
||||
result = pyblish.plugin.process(
|
||||
plugin, self._publish_context, instance
|
||||
)
|
||||
if log_handler is not None:
|
||||
result["records"] = log_handler.records
|
||||
|
||||
exception = result.get("error")
|
||||
if exception:
|
||||
has_validation_error = False
|
||||
if (
|
||||
isinstance(exception, PublishValidationError)
|
||||
and not self._publish_has_validated
|
||||
):
|
||||
has_validation_error = True
|
||||
result["is_validation_error"] = True
|
||||
self._add_validation_error(result)
|
||||
|
||||
else:
|
||||
if isinstance(exception, KnownPublishError):
|
||||
msg = str(exception)
|
||||
else:
|
||||
msg = (
|
||||
"Something went wrong. Send report"
|
||||
" to your supervisor or Ynput team."
|
||||
)
|
||||
self._set_publish_error_msg(msg)
|
||||
if isinstance(exception, PublishError):
|
||||
if not exception.title:
|
||||
exception.title = plugin.label or plugin.__name__
|
||||
self._add_publish_error_to_report(result)
|
||||
|
||||
error_info = PublishErrorInfo.from_exception(exception)
|
||||
self._set_publish_error_info(error_info)
|
||||
self._set_is_crashed(True)
|
||||
|
||||
result["is_validation_error"] = has_validation_error
|
||||
result["is_validation_error"] = False
|
||||
|
||||
self._publish_report.add_result(plugin.id, result)
|
||||
|
||||
def _add_validation_error(self, result: Dict[str, Any]):
|
||||
self._set_has_validation_errors(True)
|
||||
self._publish_validation_errors.add_error(
|
||||
self._add_publish_error_to_report(result)
|
||||
|
||||
def _add_publish_error_to_report(self, result: Dict[str, Any]):
|
||||
self._publish_errors.add_error(
|
||||
result["plugin"],
|
||||
result["error"],
|
||||
result["instance"]
|
||||
|
|
|
|||
|
|
@ -217,20 +217,22 @@ class InstanceGroupWidget(BaseGroupWidget):
|
|||
def update_icons(self, group_icons):
|
||||
self._group_icons = group_icons
|
||||
|
||||
def update_instance_values(self):
|
||||
def update_instance_values(self, context_info_by_id):
|
||||
"""Trigger update on instance widgets."""
|
||||
|
||||
for widget in self._widgets_by_id.values():
|
||||
widget.update_instance_values()
|
||||
for instance_id, widget in self._widgets_by_id.items():
|
||||
widget.update_instance_values(context_info_by_id[instance_id])
|
||||
|
||||
def update_instances(self, instances):
|
||||
def update_instances(self, instances, context_info_by_id):
|
||||
"""Update instances for the group.
|
||||
|
||||
Args:
|
||||
instances(list<CreatedInstance>): List of instances in
|
||||
instances (list[CreatedInstance]): List of instances in
|
||||
CreateContext.
|
||||
"""
|
||||
context_info_by_id (Dict[str, InstanceContextInfo]): Instance
|
||||
context info by instance id.
|
||||
|
||||
"""
|
||||
# Store instances by id and by product name
|
||||
instances_by_id = {}
|
||||
instances_by_product_name = collections.defaultdict(list)
|
||||
|
|
@ -249,13 +251,14 @@ class InstanceGroupWidget(BaseGroupWidget):
|
|||
widget_idx = 1
|
||||
for product_names in sorted_product_names:
|
||||
for instance in instances_by_product_name[product_names]:
|
||||
context_info = context_info_by_id[instance.id]
|
||||
if instance.id in self._widgets_by_id:
|
||||
widget = self._widgets_by_id[instance.id]
|
||||
widget.update_instance(instance)
|
||||
widget.update_instance(instance, context_info)
|
||||
else:
|
||||
group_icon = self._group_icons[instance.creator_identifier]
|
||||
widget = InstanceCardWidget(
|
||||
instance, group_icon, self
|
||||
instance, context_info, group_icon, self
|
||||
)
|
||||
widget.selected.connect(self._on_widget_selection)
|
||||
widget.active_changed.connect(self._on_active_changed)
|
||||
|
|
@ -388,7 +391,7 @@ class ConvertorItemCardWidget(CardWidget):
|
|||
self._icon_widget = icon_widget
|
||||
self._label_widget = label_widget
|
||||
|
||||
def update_instance_values(self):
|
||||
def update_instance_values(self, context_info):
|
||||
pass
|
||||
|
||||
|
||||
|
|
@ -397,7 +400,7 @@ class InstanceCardWidget(CardWidget):
|
|||
|
||||
active_changed = QtCore.Signal(str, bool)
|
||||
|
||||
def __init__(self, instance, group_icon, parent):
|
||||
def __init__(self, instance, context_info, group_icon, parent):
|
||||
super().__init__(parent)
|
||||
|
||||
self._id = instance.id
|
||||
|
|
@ -458,7 +461,7 @@ class InstanceCardWidget(CardWidget):
|
|||
self._active_checkbox = active_checkbox
|
||||
self._expand_btn = expand_btn
|
||||
|
||||
self.update_instance_values()
|
||||
self.update_instance_values(context_info)
|
||||
|
||||
def set_active_toggle_enabled(self, enabled):
|
||||
self._active_checkbox.setEnabled(enabled)
|
||||
|
|
@ -480,13 +483,13 @@ class InstanceCardWidget(CardWidget):
|
|||
if checkbox_value != new_value:
|
||||
self._active_checkbox.setChecked(new_value)
|
||||
|
||||
def update_instance(self, instance):
|
||||
def update_instance(self, instance, context_info):
|
||||
"""Update instance object and update UI."""
|
||||
self.instance = instance
|
||||
self.update_instance_values()
|
||||
self.update_instance_values(context_info)
|
||||
|
||||
def _validate_context(self):
|
||||
valid = self.instance.has_valid_context
|
||||
def _validate_context(self, context_info):
|
||||
valid = context_info.is_valid
|
||||
self._icon_widget.setVisible(valid)
|
||||
self._context_warning.setVisible(not valid)
|
||||
|
||||
|
|
@ -519,11 +522,11 @@ class InstanceCardWidget(CardWidget):
|
|||
QtCore.Qt.NoTextInteraction
|
||||
)
|
||||
|
||||
def update_instance_values(self):
|
||||
def update_instance_values(self, context_info):
|
||||
"""Update instance data"""
|
||||
self._update_product_name()
|
||||
self.set_active(self.instance["active"])
|
||||
self._validate_context()
|
||||
self._validate_context(context_info)
|
||||
|
||||
def _set_expanded(self, expanded=None):
|
||||
if expanded is None:
|
||||
|
|
@ -694,6 +697,8 @@ class InstanceCardView(AbstractInstanceView):
|
|||
|
||||
self._update_convertor_items_group()
|
||||
|
||||
context_info_by_id = self._controller.get_instances_context_info()
|
||||
|
||||
# Prepare instances by group and identifiers by group
|
||||
instances_by_group = collections.defaultdict(list)
|
||||
identifiers_by_group = collections.defaultdict(set)
|
||||
|
|
@ -747,7 +752,7 @@ class InstanceCardView(AbstractInstanceView):
|
|||
|
||||
widget_idx += 1
|
||||
group_widget.update_instances(
|
||||
instances_by_group[group_name]
|
||||
instances_by_group[group_name], context_info_by_id
|
||||
)
|
||||
group_widget.set_active_toggle_enabled(
|
||||
self._active_toggle_enabled
|
||||
|
|
@ -814,8 +819,9 @@ class InstanceCardView(AbstractInstanceView):
|
|||
|
||||
def refresh_instance_states(self):
|
||||
"""Trigger update of instances on group widgets."""
|
||||
context_info_by_id = self._controller.get_instances_context_info()
|
||||
for widget in self._widgets_by_group.values():
|
||||
widget.update_instance_values()
|
||||
widget.update_instance_values(context_info_by_id)
|
||||
|
||||
def _on_active_changed(self, group_name, instance_id, value):
|
||||
group_widget = self._widgets_by_group[group_name]
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ class InstanceListItemWidget(QtWidgets.QWidget):
|
|||
active_changed = QtCore.Signal(str, bool)
|
||||
double_clicked = QtCore.Signal()
|
||||
|
||||
def __init__(self, instance, parent):
|
||||
def __init__(self, instance, context_info, parent):
|
||||
super().__init__(parent)
|
||||
|
||||
self.instance = instance
|
||||
|
|
@ -151,7 +151,7 @@ class InstanceListItemWidget(QtWidgets.QWidget):
|
|||
|
||||
self._has_valid_context = None
|
||||
|
||||
self._set_valid_property(instance.has_valid_context)
|
||||
self._set_valid_property(context_info.is_valid)
|
||||
|
||||
def mouseDoubleClickEvent(self, event):
|
||||
widget = self.childAt(event.pos())
|
||||
|
|
@ -188,12 +188,12 @@ class InstanceListItemWidget(QtWidgets.QWidget):
|
|||
if checkbox_value != new_value:
|
||||
self._active_checkbox.setChecked(new_value)
|
||||
|
||||
def update_instance(self, instance):
|
||||
def update_instance(self, instance, context_info):
|
||||
"""Update instance object."""
|
||||
self.instance = instance
|
||||
self.update_instance_values()
|
||||
self.update_instance_values(context_info)
|
||||
|
||||
def update_instance_values(self):
|
||||
def update_instance_values(self, context_info):
|
||||
"""Update instance data propagated to widgets."""
|
||||
# Check product name
|
||||
label = self.instance.label
|
||||
|
|
@ -202,7 +202,7 @@ class InstanceListItemWidget(QtWidgets.QWidget):
|
|||
# Check active state
|
||||
self.set_active(self.instance["active"])
|
||||
# Check valid states
|
||||
self._set_valid_property(self.instance.has_valid_context)
|
||||
self._set_valid_property(context_info.is_valid)
|
||||
|
||||
def _on_active_change(self):
|
||||
new_value = self._active_checkbox.isChecked()
|
||||
|
|
@ -583,6 +583,8 @@ class InstanceListView(AbstractInstanceView):
|
|||
|
||||
self._update_convertor_items_group()
|
||||
|
||||
context_info_by_id = self._controller.get_instances_context_info()
|
||||
|
||||
# Prepare instances by their groups
|
||||
instances_by_group_name = collections.defaultdict(list)
|
||||
group_names = set()
|
||||
|
|
@ -643,13 +645,15 @@ class InstanceListView(AbstractInstanceView):
|
|||
elif activity != instance["active"]:
|
||||
activity = -1
|
||||
|
||||
context_info = context_info_by_id[instance_id]
|
||||
|
||||
self._group_by_instance_id[instance_id] = group_name
|
||||
# Remove instance id from `to_remove` if already exists and
|
||||
# trigger update of widget
|
||||
if instance_id in to_remove:
|
||||
to_remove.remove(instance_id)
|
||||
widget = self._widgets_by_id[instance_id]
|
||||
widget.update_instance(instance)
|
||||
widget.update_instance(instance, context_info)
|
||||
continue
|
||||
|
||||
# Create new item and store it as new
|
||||
|
|
@ -695,7 +699,8 @@ class InstanceListView(AbstractInstanceView):
|
|||
group_item.appendRows(new_items)
|
||||
|
||||
for item, instance in new_items_with_instance:
|
||||
if not instance.has_valid_context:
|
||||
context_info = context_info_by_id[instance.id]
|
||||
if not context_info.is_valid:
|
||||
expand_groups.add(group_name)
|
||||
item_index = self._instance_model.index(
|
||||
item.row(),
|
||||
|
|
@ -704,7 +709,7 @@ class InstanceListView(AbstractInstanceView):
|
|||
)
|
||||
proxy_index = self._proxy_model.mapFromSource(item_index)
|
||||
widget = InstanceListItemWidget(
|
||||
instance, self._instance_view
|
||||
instance, context_info, self._instance_view
|
||||
)
|
||||
widget.set_active_toggle_enabled(
|
||||
self._active_toggle_enabled
|
||||
|
|
@ -870,8 +875,10 @@ class InstanceListView(AbstractInstanceView):
|
|||
|
||||
def refresh_instance_states(self):
|
||||
"""Trigger update of all instances."""
|
||||
for widget in self._widgets_by_id.values():
|
||||
widget.update_instance_values()
|
||||
context_info_by_id = self._controller.get_instances_context_info()
|
||||
for instance_id, widget in self._widgets_by_id.items():
|
||||
context_info = context_info_by_id[instance_id]
|
||||
widget.update_instance_values(context_info)
|
||||
|
||||
def _on_active_changed(self, changed_instance_id, new_value):
|
||||
selected_instance_ids, _, _ = self.get_selected_items()
|
||||
|
|
|
|||
|
|
@ -387,7 +387,7 @@ class OverviewWidget(QtWidgets.QFrame):
|
|||
|
||||
Returns:
|
||||
list[str]: Selected legacy convertor identifiers.
|
||||
Example: ['io.openpype.creators.houdini.legacy']
|
||||
Example: ['io.ayon.creators.houdini.legacy']
|
||||
"""
|
||||
|
||||
_, _, convertor_identifiers = self.get_selected_items()
|
||||
|
|
|
|||
|
|
@ -411,10 +411,13 @@ class PublishFrame(QtWidgets.QWidget):
|
|||
"""Show error message to artist on publish crash."""
|
||||
|
||||
self._set_main_label("Error happened")
|
||||
error_info = self._controller.get_publish_error_info()
|
||||
|
||||
self._message_label_top.setText(
|
||||
self._controller.get_publish_error_msg()
|
||||
)
|
||||
error_message = "Unknown error happened"
|
||||
if error_info is not None:
|
||||
error_message = error_info.message
|
||||
|
||||
self._message_label_top.setText(error_message)
|
||||
|
||||
self._set_success_property(1)
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ from ayon_core.tools.publisher.constants import (
|
|||
CONTEXT_LABEL,
|
||||
)
|
||||
|
||||
from .widgets import IconValuePixmapLabel
|
||||
from .widgets import PublishPixmapLabel, IconValuePixmapLabel
|
||||
from .icons import (
|
||||
get_pixmap,
|
||||
get_image,
|
||||
|
|
@ -42,7 +42,7 @@ INFO_VISIBLE = 1 << 6
|
|||
|
||||
|
||||
class VerticalScrollArea(QtWidgets.QScrollArea):
|
||||
"""Scroll area for validation error titles.
|
||||
"""Scroll area for publish error titles.
|
||||
|
||||
The biggest difference is that the scroll area has scroll bar on left side
|
||||
and resize of content will also resize scrollarea itself.
|
||||
|
|
@ -126,7 +126,7 @@ class ActionButton(BaseClickableFrame):
|
|||
def __init__(self, plugin_action_item, parent):
|
||||
super().__init__(parent)
|
||||
|
||||
self.setObjectName("ValidationActionButton")
|
||||
self.setObjectName("PublishActionButton")
|
||||
|
||||
self.plugin_action_item = plugin_action_item
|
||||
|
||||
|
|
@ -155,10 +155,10 @@ class ActionButton(BaseClickableFrame):
|
|||
)
|
||||
|
||||
|
||||
class ValidateActionsWidget(QtWidgets.QFrame):
|
||||
class PublishActionsWidget(QtWidgets.QFrame):
|
||||
"""Wrapper widget for plugin actions.
|
||||
|
||||
Change actions based on selected validation error.
|
||||
Change actions based on selected publish error.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
|
|
@ -243,16 +243,16 @@ class ValidateActionsWidget(QtWidgets.QFrame):
|
|||
self._controller.run_action(plugin_id, action_id)
|
||||
|
||||
|
||||
# --- Validation error titles ---
|
||||
class ValidationErrorInstanceList(QtWidgets.QListView):
|
||||
"""List of publish instances that caused a validation error.
|
||||
# --- Publish error titles ---
|
||||
class PublishErrorInstanceList(QtWidgets.QListView):
|
||||
"""List of publish instances that caused a publish error.
|
||||
|
||||
Instances are collected per plugin's validation error title.
|
||||
Instances are collected per plugin's publish error title.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.setObjectName("ValidationErrorInstanceList")
|
||||
self.setObjectName("PublishErrorInstanceList")
|
||||
|
||||
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||
self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
|
||||
|
|
@ -270,18 +270,19 @@ class ValidationErrorInstanceList(QtWidgets.QListView):
|
|||
return result
|
||||
|
||||
|
||||
class ValidationErrorTitleWidget(QtWidgets.QWidget):
|
||||
"""Title of validation error.
|
||||
class PublishErrorTitleWidget(QtWidgets.QWidget):
|
||||
"""Title of publish error.
|
||||
|
||||
Widget is used as radio button so requires clickable functionality and
|
||||
changing style on selection/deselection.
|
||||
|
||||
Has toggle button to show/hide instances on which validation error happened
|
||||
Has toggle button to show/hide instances on which publish error happened
|
||||
if there is a list (Valdation error may happen on context).
|
||||
"""
|
||||
|
||||
selected = QtCore.Signal(str)
|
||||
instance_changed = QtCore.Signal(str)
|
||||
_error_pixmap = None
|
||||
|
||||
def __init__(self, title_id, error_info, parent):
|
||||
super().__init__(parent)
|
||||
|
|
@ -290,30 +291,17 @@ class ValidationErrorTitleWidget(QtWidgets.QWidget):
|
|||
self._error_info = error_info
|
||||
self._selected = False
|
||||
|
||||
title_frame = ClickableFrame(self)
|
||||
title_frame.setObjectName("ValidationErrorTitleFrame")
|
||||
|
||||
toggle_instance_btn = QtWidgets.QToolButton(title_frame)
|
||||
toggle_instance_btn.setObjectName("ArrowBtn")
|
||||
toggle_instance_btn.setArrowType(QtCore.Qt.RightArrow)
|
||||
toggle_instance_btn.setMaximumWidth(14)
|
||||
|
||||
label_widget = QtWidgets.QLabel(error_info["title"], title_frame)
|
||||
|
||||
title_frame_layout = QtWidgets.QHBoxLayout(title_frame)
|
||||
title_frame_layout.addWidget(label_widget, 1)
|
||||
title_frame_layout.addWidget(toggle_instance_btn, 0)
|
||||
|
||||
instances_model = QtGui.QStandardItemModel()
|
||||
|
||||
instance_ids = []
|
||||
|
||||
items = []
|
||||
context_validation = False
|
||||
is_context_plugin = False
|
||||
is_crashing_error = False
|
||||
for error_item in error_info["error_items"]:
|
||||
context_validation = error_item.context_validation
|
||||
if context_validation:
|
||||
toggle_instance_btn.setArrowType(QtCore.Qt.NoArrow)
|
||||
is_crashing_error = not error_item.is_validation_error
|
||||
is_context_plugin = error_item.is_context_plugin
|
||||
if is_context_plugin:
|
||||
instance_ids.append(CONTEXT_ID)
|
||||
# Add fake item to have minimum size hint of view widget
|
||||
items.append(QtGui.QStandardItem(CONTEXT_LABEL))
|
||||
|
|
@ -333,7 +321,33 @@ class ValidationErrorTitleWidget(QtWidgets.QWidget):
|
|||
root_item = instances_model.invisibleRootItem()
|
||||
root_item.appendRows(items)
|
||||
|
||||
instances_view = ValidationErrorInstanceList(self)
|
||||
title_frame = ClickableFrame(self)
|
||||
title_frame.setObjectName("PublishErrorTitleFrame")
|
||||
|
||||
toggle_instance_btn = QtWidgets.QToolButton(title_frame)
|
||||
toggle_instance_btn.setObjectName("ArrowBtn")
|
||||
toggle_instance_btn.setArrowType(QtCore.Qt.RightArrow)
|
||||
toggle_instance_btn.setMaximumWidth(14)
|
||||
if is_context_plugin:
|
||||
toggle_instance_btn.setArrowType(QtCore.Qt.NoArrow)
|
||||
|
||||
icon_label = None
|
||||
if is_crashing_error:
|
||||
error_pixmap = self._get_error_pixmap()
|
||||
icon_label = PublishPixmapLabel(error_pixmap, self)
|
||||
|
||||
label_widget = QtWidgets.QLabel(error_info["title"], title_frame)
|
||||
|
||||
title_frame_layout = QtWidgets.QHBoxLayout(title_frame)
|
||||
title_frame_layout.setContentsMargins(8, 8, 8, 8)
|
||||
title_frame_layout.setSpacing(0)
|
||||
if icon_label is not None:
|
||||
title_frame_layout.addWidget(icon_label, 0)
|
||||
title_frame_layout.addSpacing(6)
|
||||
title_frame_layout.addWidget(label_widget, 1)
|
||||
title_frame_layout.addWidget(toggle_instance_btn, 0)
|
||||
|
||||
instances_view = PublishErrorInstanceList(self)
|
||||
instances_view.setModel(instances_model)
|
||||
|
||||
self.setLayoutDirection(QtCore.Qt.LeftToRight)
|
||||
|
|
@ -352,7 +366,7 @@ class ValidationErrorTitleWidget(QtWidgets.QWidget):
|
|||
layout.addWidget(view_widget, 0)
|
||||
view_widget.setVisible(False)
|
||||
|
||||
if not context_validation:
|
||||
if not is_context_plugin:
|
||||
toggle_instance_btn.clicked.connect(self._on_toggle_btn_click)
|
||||
|
||||
title_frame.clicked.connect(self._mouse_release_callback)
|
||||
|
|
@ -369,7 +383,8 @@ class ValidationErrorTitleWidget(QtWidgets.QWidget):
|
|||
self._instances_model = instances_model
|
||||
self._instances_view = instances_view
|
||||
|
||||
self._context_validation = context_validation
|
||||
self._is_context_plugin = is_context_plugin
|
||||
self._is_crashing_error = is_crashing_error
|
||||
|
||||
self._instance_ids = instance_ids
|
||||
self._expanded = False
|
||||
|
|
@ -411,6 +426,10 @@ class ValidationErrorTitleWidget(QtWidgets.QWidget):
|
|||
def id(self):
|
||||
return self._title_id
|
||||
|
||||
@property
|
||||
def is_crashing_error(self):
|
||||
return self._is_crashing_error
|
||||
|
||||
def _change_style_property(self, selected):
|
||||
"""Change style of widget based on selection."""
|
||||
|
||||
|
|
@ -438,6 +457,12 @@ class ValidationErrorTitleWidget(QtWidgets.QWidget):
|
|||
self.selected.emit(self._title_id)
|
||||
self._set_expanded(True)
|
||||
|
||||
@classmethod
|
||||
def _get_error_pixmap(cls):
|
||||
if cls._error_pixmap is None:
|
||||
cls._error_pixmap = get_pixmap("error")
|
||||
return cls._error_pixmap
|
||||
|
||||
def _on_toggle_btn_click(self):
|
||||
"""Show/hide instances list."""
|
||||
|
||||
|
|
@ -450,7 +475,7 @@ class ValidationErrorTitleWidget(QtWidgets.QWidget):
|
|||
elif expanded is self._expanded:
|
||||
return
|
||||
|
||||
if expanded and self._context_validation:
|
||||
if expanded and self._is_context_plugin:
|
||||
return
|
||||
|
||||
self._expanded = expanded
|
||||
|
|
@ -464,7 +489,7 @@ class ValidationErrorTitleWidget(QtWidgets.QWidget):
|
|||
self.instance_changed.emit(self._title_id)
|
||||
|
||||
def get_selected_instances(self):
|
||||
if self._context_validation:
|
||||
if self._is_context_plugin:
|
||||
return [CONTEXT_ID]
|
||||
sel_model = self._instances_view.selectionModel()
|
||||
return [
|
||||
|
|
@ -477,21 +502,7 @@ class ValidationErrorTitleWidget(QtWidgets.QWidget):
|
|||
return list(self._instance_ids)
|
||||
|
||||
|
||||
class ValidationArtistMessage(QtWidgets.QWidget):
|
||||
def __init__(self, message, parent):
|
||||
super().__init__(parent)
|
||||
|
||||
artist_msg_label = QtWidgets.QLabel(message, self)
|
||||
artist_msg_label.setAlignment(QtCore.Qt.AlignCenter)
|
||||
|
||||
main_layout = QtWidgets.QHBoxLayout(self)
|
||||
main_layout.setContentsMargins(0, 0, 0, 0)
|
||||
main_layout.addWidget(
|
||||
artist_msg_label, 1, QtCore.Qt.AlignCenter
|
||||
)
|
||||
|
||||
|
||||
class ValidationErrorsView(QtWidgets.QWidget):
|
||||
class PublishErrorsView(QtWidgets.QWidget):
|
||||
selection_changed = QtCore.Signal()
|
||||
|
||||
def __init__(self, parent):
|
||||
|
|
@ -510,8 +521,9 @@ class ValidationErrorsView(QtWidgets.QWidget):
|
|||
# scroll widget
|
||||
errors_layout.setContentsMargins(5, 0, 0, 0)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.addWidget(errors_scroll, 1)
|
||||
main_layout = QtWidgets.QVBoxLayout(self)
|
||||
main_layout.setContentsMargins(8, 8, 8, 8)
|
||||
main_layout.addWidget(errors_scroll, 1)
|
||||
|
||||
self._errors_widget = errors_widget
|
||||
self._errors_layout = errors_layout
|
||||
|
|
@ -533,28 +545,30 @@ class ValidationErrorsView(QtWidgets.QWidget):
|
|||
"""Set errors into context and created titles.
|
||||
|
||||
Args:
|
||||
validation_error_report (PublishValidationErrorsReport): Report
|
||||
with information about validation errors and publish plugin
|
||||
grouped_error_items (List[Dict[str, Any]]): Report
|
||||
with information about publish errors and publish plugin
|
||||
actions.
|
||||
"""
|
||||
|
||||
self._clear()
|
||||
|
||||
first_id = None
|
||||
select_id = None
|
||||
for title_item in grouped_error_items:
|
||||
title_id = title_item["id"]
|
||||
if first_id is None:
|
||||
first_id = title_id
|
||||
widget = ValidationErrorTitleWidget(title_id, title_item, self)
|
||||
if select_id is None:
|
||||
select_id = title_id
|
||||
widget = PublishErrorTitleWidget(title_id, title_item, self)
|
||||
widget.selected.connect(self._on_select)
|
||||
widget.instance_changed.connect(self._on_instance_change)
|
||||
if widget.is_crashing_error:
|
||||
select_id = title_id
|
||||
self._errors_layout.addWidget(widget)
|
||||
self._title_widgets[title_id] = widget
|
||||
|
||||
self._errors_layout.addStretch(1)
|
||||
|
||||
if first_id:
|
||||
self._title_widgets[first_id].set_selected(True)
|
||||
if select_id:
|
||||
self._title_widgets[select_id].set_selected(True)
|
||||
else:
|
||||
self.selection_changed.emit()
|
||||
|
||||
|
|
@ -1319,6 +1333,7 @@ class InstancesLogsView(QtWidgets.QFrame):
|
|||
content_widget = QtWidgets.QWidget(content_wrap_widget)
|
||||
content_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground)
|
||||
content_layout = QtWidgets.QVBoxLayout(content_widget)
|
||||
content_layout.setContentsMargins(8, 8, 8, 8)
|
||||
content_layout.setSpacing(15)
|
||||
|
||||
scroll_area.setWidget(content_wrap_widget)
|
||||
|
|
@ -1454,6 +1469,78 @@ class InstancesLogsView(QtWidgets.QFrame):
|
|||
self._update_instances()
|
||||
|
||||
|
||||
class ErrorDetailWidget(QtWidgets.QWidget):
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
|
||||
error_detail_top = ClickableFrame(self)
|
||||
|
||||
line_l_widget = SeparatorWidget(1, parent=error_detail_top)
|
||||
error_detail_expand_btn = ClassicExpandBtn(error_detail_top)
|
||||
error_detail_expand_label = QtWidgets.QLabel(
|
||||
"Details", error_detail_top)
|
||||
|
||||
line_r_widget = SeparatorWidget(1, parent=error_detail_top)
|
||||
|
||||
error_detail_top_l = QtWidgets.QHBoxLayout(error_detail_top)
|
||||
error_detail_top_l.setContentsMargins(0, 0, 10, 0)
|
||||
error_detail_top_l.addWidget(line_l_widget, 1)
|
||||
error_detail_top_l.addWidget(error_detail_expand_btn, 0)
|
||||
error_detail_top_l.addWidget(error_detail_expand_label, 0)
|
||||
error_detail_top_l.addWidget(line_r_widget, 9)
|
||||
|
||||
error_detail_input = ExpandingTextEdit(self)
|
||||
error_detail_input.setObjectName("InfoText")
|
||||
error_detail_input.setTextInteractionFlags(
|
||||
QtCore.Qt.TextBrowserInteraction
|
||||
)
|
||||
|
||||
main_layout = QtWidgets.QVBoxLayout(self)
|
||||
main_layout.setContentsMargins(0, 0, 0, 0)
|
||||
main_layout.addWidget(error_detail_top, 0)
|
||||
main_layout.addWidget(error_detail_input, 0)
|
||||
main_layout.addStretch(1)
|
||||
|
||||
error_detail_input.setVisible(not error_detail_expand_btn.collapsed)
|
||||
|
||||
error_detail_top.clicked.connect(self._on_detail_toggle)
|
||||
|
||||
self._error_detail_top = error_detail_top
|
||||
self._error_detail_expand_btn = error_detail_expand_btn
|
||||
self._error_detail_input = error_detail_input
|
||||
|
||||
def set_detail(self, detail):
|
||||
if not detail:
|
||||
self._set_visible_inputs(False)
|
||||
return
|
||||
|
||||
if commonmark:
|
||||
self._error_detail_input.setHtml(
|
||||
commonmark.commonmark(detail)
|
||||
)
|
||||
|
||||
elif hasattr(self._error_detail_input, "setMarkdown"):
|
||||
self._error_detail_input.setMarkdown(detail)
|
||||
|
||||
else:
|
||||
self._error_detail_input.setText(detail)
|
||||
|
||||
self._set_visible_inputs(True)
|
||||
|
||||
def _set_visible_inputs(self, visible):
|
||||
self._error_detail_top.setVisible(visible)
|
||||
input_visible = visible
|
||||
if input_visible:
|
||||
input_visible = not self._error_detail_expand_btn.collapsed
|
||||
self._error_detail_input.setVisible(input_visible)
|
||||
|
||||
def _on_detail_toggle(self):
|
||||
self._error_detail_expand_btn.set_collapsed()
|
||||
self._error_detail_input.setVisible(
|
||||
not self._error_detail_expand_btn.collapsed
|
||||
)
|
||||
|
||||
|
||||
class CrashWidget(QtWidgets.QWidget):
|
||||
"""Widget shown when publishing crashes.
|
||||
|
||||
|
|
@ -1488,6 +1575,8 @@ class CrashWidget(QtWidgets.QWidget):
|
|||
"Save to disk", btns_widget)
|
||||
|
||||
btns_layout = QtWidgets.QHBoxLayout(btns_widget)
|
||||
btns_layout.setContentsMargins(0, 0, 0, 0)
|
||||
btns_layout.setSpacing(0)
|
||||
btns_layout.addStretch(1)
|
||||
btns_layout.addWidget(copy_clipboard_btn, 0)
|
||||
btns_layout.addSpacing(20)
|
||||
|
|
@ -1495,11 +1584,13 @@ class CrashWidget(QtWidgets.QWidget):
|
|||
btns_layout.addStretch(1)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.setContentsMargins(5, 5, 5, 5)
|
||||
layout.setSpacing(0)
|
||||
layout.addStretch(1)
|
||||
layout.addWidget(main_label, 0)
|
||||
layout.addSpacing(20)
|
||||
layout.addSpacing(30)
|
||||
layout.addWidget(report_label, 0)
|
||||
layout.addSpacing(20)
|
||||
layout.addSpacing(30)
|
||||
layout.addWidget(btns_widget, 0)
|
||||
layout.addStretch(2)
|
||||
|
||||
|
|
@ -1517,7 +1608,7 @@ class CrashWidget(QtWidgets.QWidget):
|
|||
"export_report.request", {}, "report_page")
|
||||
|
||||
|
||||
class ErrorDetailsWidget(QtWidgets.QWidget):
|
||||
class PublishFailWidget(QtWidgets.QWidget):
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
|
||||
|
|
@ -1530,34 +1621,7 @@ class ErrorDetailsWidget(QtWidgets.QWidget):
|
|||
)
|
||||
|
||||
# Error 'Details' widget -> Collapsible
|
||||
error_details_widget = QtWidgets.QWidget(inputs_widget)
|
||||
|
||||
error_details_top = ClickableFrame(error_details_widget)
|
||||
|
||||
error_details_expand_btn = ClassicExpandBtn(error_details_top)
|
||||
error_details_expand_label = QtWidgets.QLabel(
|
||||
"Details", error_details_top)
|
||||
|
||||
line_widget = SeparatorWidget(1, parent=error_details_top)
|
||||
|
||||
error_details_top_l = QtWidgets.QHBoxLayout(error_details_top)
|
||||
error_details_top_l.setContentsMargins(0, 0, 10, 0)
|
||||
error_details_top_l.addWidget(error_details_expand_btn, 0)
|
||||
error_details_top_l.addWidget(error_details_expand_label, 0)
|
||||
error_details_top_l.addWidget(line_widget, 1)
|
||||
|
||||
error_details_input = ExpandingTextEdit(error_details_widget)
|
||||
error_details_input.setObjectName("InfoText")
|
||||
error_details_input.setTextInteractionFlags(
|
||||
QtCore.Qt.TextBrowserInteraction
|
||||
)
|
||||
error_details_input.setVisible(not error_details_expand_btn.collapsed)
|
||||
|
||||
error_details_layout = QtWidgets.QVBoxLayout(error_details_widget)
|
||||
error_details_layout.setContentsMargins(0, 0, 0, 0)
|
||||
error_details_layout.addWidget(error_details_top, 0)
|
||||
error_details_layout.addWidget(error_details_input, 0)
|
||||
error_details_layout.addStretch(1)
|
||||
error_details_widget = ErrorDetailWidget(inputs_widget)
|
||||
|
||||
# Description and Details layout
|
||||
inputs_layout = QtWidgets.QVBoxLayout(inputs_widget)
|
||||
|
|
@ -1570,17 +1634,8 @@ class ErrorDetailsWidget(QtWidgets.QWidget):
|
|||
main_layout.setContentsMargins(0, 0, 0, 0)
|
||||
main_layout.addWidget(inputs_widget, 1)
|
||||
|
||||
error_details_top.clicked.connect(self._on_detail_toggle)
|
||||
|
||||
self._error_details_widget = error_details_widget
|
||||
self._error_description_input = error_description_input
|
||||
self._error_details_expand_btn = error_details_expand_btn
|
||||
self._error_details_input = error_details_input
|
||||
|
||||
def _on_detail_toggle(self):
|
||||
self._error_details_expand_btn.set_collapsed()
|
||||
self._error_details_input.setVisible(
|
||||
not self._error_details_expand_btn.collapsed)
|
||||
self._error_details_widget = error_details_widget
|
||||
|
||||
def set_error_item(self, error_item):
|
||||
detail = ""
|
||||
|
|
@ -1589,23 +1644,18 @@ class ErrorDetailsWidget(QtWidgets.QWidget):
|
|||
description = error_item.description or description
|
||||
detail = error_item.detail or detail
|
||||
|
||||
self._error_details_widget.set_detail(detail)
|
||||
|
||||
if commonmark:
|
||||
self._error_description_input.setHtml(
|
||||
commonmark.commonmark(description)
|
||||
)
|
||||
self._error_details_input.setHtml(
|
||||
commonmark.commonmark(detail)
|
||||
)
|
||||
|
||||
elif hasattr(self._error_details_input, "setMarkdown"):
|
||||
elif hasattr(self._error_description_input, "setMarkdown"):
|
||||
self._error_description_input.setMarkdown(description)
|
||||
self._error_details_input.setMarkdown(detail)
|
||||
|
||||
else:
|
||||
self._error_description_input.setText(description)
|
||||
self._error_details_input.setText(detail)
|
||||
|
||||
self._error_details_widget.setVisible(bool(detail))
|
||||
|
||||
|
||||
class ReportsWidget(QtWidgets.QWidget):
|
||||
|
|
@ -1622,7 +1672,7 @@ class ReportsWidget(QtWidgets.QWidget):
|
|||
│ │ │
|
||||
│ │ │
|
||||
└──────┴───────────────────┘
|
||||
# Validation errors layout
|
||||
# Publish errors layout
|
||||
┌──────┬─────────┬─────────┐
|
||||
│Views │ Actions │ │
|
||||
│ ├─────────┤ Details │
|
||||
|
|
@ -1641,12 +1691,12 @@ class ReportsWidget(QtWidgets.QWidget):
|
|||
|
||||
instances_view = PublishInstancesViewWidget(controller, views_widget)
|
||||
|
||||
validation_error_view = ValidationErrorsView(views_widget)
|
||||
publish_error_view = PublishErrorsView(views_widget)
|
||||
|
||||
views_layout = QtWidgets.QStackedLayout(views_widget)
|
||||
views_layout.setContentsMargins(0, 0, 0, 0)
|
||||
views_layout.addWidget(instances_view)
|
||||
views_layout.addWidget(validation_error_view)
|
||||
views_layout.addWidget(publish_error_view)
|
||||
|
||||
views_layout.setCurrentWidget(instances_view)
|
||||
|
||||
|
|
@ -1655,10 +1705,13 @@ class ReportsWidget(QtWidgets.QWidget):
|
|||
details_widget.setObjectName("PublishInstancesDetails")
|
||||
|
||||
# Actions widget
|
||||
actions_widget = ValidateActionsWidget(controller, details_widget)
|
||||
actions_widget = PublishActionsWidget(controller, details_widget)
|
||||
|
||||
pages_widget = QtWidgets.QWidget(details_widget)
|
||||
|
||||
# Crash information
|
||||
crash_widget = CrashWidget(controller, details_widget)
|
||||
|
||||
# Logs view
|
||||
logs_view = InstancesLogsView(pages_widget)
|
||||
|
||||
|
|
@ -1671,30 +1724,24 @@ class ReportsWidget(QtWidgets.QWidget):
|
|||
|
||||
detail_input_scroll = QtWidgets.QScrollArea(pages_widget)
|
||||
|
||||
detail_inputs_widget = ErrorDetailsWidget(detail_input_scroll)
|
||||
detail_inputs_widget = PublishFailWidget(detail_input_scroll)
|
||||
detail_inputs_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground)
|
||||
|
||||
detail_input_scroll.setWidget(detail_inputs_widget)
|
||||
detail_input_scroll.setWidgetResizable(True)
|
||||
detail_input_scroll.setViewportMargins(0, 0, 0, 0)
|
||||
|
||||
# Crash information
|
||||
crash_widget = CrashWidget(controller, details_widget)
|
||||
|
||||
# Layout pages
|
||||
pages_layout = QtWidgets.QHBoxLayout(pages_widget)
|
||||
pages_layout.setContentsMargins(0, 0, 0, 0)
|
||||
pages_layout.addWidget(crash_widget, 1)
|
||||
pages_layout.addWidget(logs_view, 1)
|
||||
pages_layout.addWidget(detail_inputs_spacer, 0)
|
||||
pages_layout.addWidget(detail_input_scroll, 1)
|
||||
pages_layout.addWidget(crash_widget, 1)
|
||||
|
||||
details_layout = QtWidgets.QVBoxLayout(details_widget)
|
||||
margins = details_layout.contentsMargins()
|
||||
margins.setTop(margins.top() * 2)
|
||||
margins.setBottom(margins.bottom() * 2)
|
||||
details_layout.setContentsMargins(margins)
|
||||
details_layout.setSpacing(margins.top())
|
||||
details_layout.setContentsMargins(8, 16, 8, 16)
|
||||
details_layout.setSpacing(8)
|
||||
details_layout.addWidget(actions_widget, 0)
|
||||
details_layout.addWidget(pages_widget, 1)
|
||||
|
||||
|
|
@ -1704,12 +1751,12 @@ class ReportsWidget(QtWidgets.QWidget):
|
|||
content_layout.addWidget(details_widget, 1)
|
||||
|
||||
instances_view.selection_changed.connect(self._on_instance_selection)
|
||||
validation_error_view.selection_changed.connect(
|
||||
publish_error_view.selection_changed.connect(
|
||||
self._on_error_selection)
|
||||
|
||||
self._views_layout = views_layout
|
||||
self._instances_view = instances_view
|
||||
self._validation_error_view = validation_error_view
|
||||
self._publish_error_view = publish_error_view
|
||||
|
||||
self._actions_widget = actions_widget
|
||||
self._detail_inputs_widget = detail_inputs_widget
|
||||
|
|
@ -1720,7 +1767,7 @@ class ReportsWidget(QtWidgets.QWidget):
|
|||
|
||||
self._controller: AbstractPublisherFrontend = controller
|
||||
|
||||
self._validation_errors_by_id = {}
|
||||
self._publish_errors_by_id = {}
|
||||
|
||||
def _get_instance_items(self):
|
||||
report = self._controller.get_publish_report()
|
||||
|
|
@ -1750,40 +1797,50 @@ class ReportsWidget(QtWidgets.QWidget):
|
|||
return instance_items
|
||||
|
||||
def update_data(self):
|
||||
view = self._instances_view
|
||||
validation_error_mode = False
|
||||
if (
|
||||
not self._controller.publish_has_crashed()
|
||||
and self._controller.publish_has_validation_errors()
|
||||
):
|
||||
view = self._validation_error_view
|
||||
validation_error_mode = True
|
||||
has_validation_error = self._controller.publish_has_validation_errors()
|
||||
has_finished = self._controller.publish_has_finished()
|
||||
has_crashed = self._controller.publish_has_crashed()
|
||||
error_info = None
|
||||
if has_crashed:
|
||||
error_info = self._controller.get_publish_error_info()
|
||||
|
||||
publish_error_mode = False
|
||||
if error_info is not None:
|
||||
publish_error_mode = not error_info.is_unknown_error
|
||||
elif has_validation_error:
|
||||
publish_error_mode = True
|
||||
|
||||
if publish_error_mode:
|
||||
view = self._publish_error_view
|
||||
else:
|
||||
view = self._instances_view
|
||||
|
||||
self._actions_widget.set_visible_mode(validation_error_mode)
|
||||
self._detail_inputs_spacer.setVisible(validation_error_mode)
|
||||
self._detail_input_scroll.setVisible(validation_error_mode)
|
||||
self._views_layout.setCurrentWidget(view)
|
||||
|
||||
is_crashed = self._controller.publish_has_crashed()
|
||||
self._crash_widget.setVisible(is_crashed)
|
||||
self._logs_view.setVisible(not is_crashed)
|
||||
self._actions_widget.set_visible_mode(publish_error_mode)
|
||||
self._detail_inputs_spacer.setVisible(publish_error_mode)
|
||||
self._detail_input_scroll.setVisible(publish_error_mode)
|
||||
|
||||
logs_visible = publish_error_mode or has_finished or not has_crashed
|
||||
self._logs_view.setVisible(logs_visible)
|
||||
self._crash_widget.setVisible(not logs_visible)
|
||||
|
||||
# Instance view & logs update
|
||||
instance_items = self._get_instance_items()
|
||||
self._instances_view.update_instances(instance_items)
|
||||
self._logs_view.update_instances(instance_items)
|
||||
|
||||
# Validation errors
|
||||
validation_errors = self._controller.get_validation_errors()
|
||||
grouped_error_items = validation_errors.group_items_by_title()
|
||||
# Publish errors
|
||||
publish_errors_report = self._controller.get_publish_errors_report()
|
||||
grouped_error_items = publish_errors_report.group_items_by_title()
|
||||
|
||||
validation_errors_by_id = {
|
||||
publish_errors_by_id = {
|
||||
title_item["id"]: title_item
|
||||
for title_item in grouped_error_items
|
||||
}
|
||||
|
||||
self._validation_errors_by_id = validation_errors_by_id
|
||||
self._validation_error_view.set_errors(grouped_error_items)
|
||||
self._publish_errors_by_id = publish_errors_by_id
|
||||
self._publish_error_view.set_errors(grouped_error_items)
|
||||
|
||||
def _on_instance_selection(self):
|
||||
instance_ids = self._instances_view.get_selected_instance_ids()
|
||||
|
|
@ -1791,8 +1848,8 @@ class ReportsWidget(QtWidgets.QWidget):
|
|||
|
||||
def _on_error_selection(self):
|
||||
title_id, instance_ids = (
|
||||
self._validation_error_view.get_selected_items())
|
||||
error_info = self._validation_errors_by_id.get(title_id)
|
||||
self._publish_error_view.get_selected_items())
|
||||
error_info = self._publish_errors_by_id.get(title_id)
|
||||
if error_info is None:
|
||||
self._actions_widget.set_error_info(None)
|
||||
self._detail_inputs_widget.set_error_item(None)
|
||||
|
|
@ -1820,12 +1877,12 @@ class ReportPageWidget(QtWidgets.QFrame):
|
|||
2. Publishing is paused. ┐
|
||||
3. Publishing successfully finished. │> Instances with logs.
|
||||
4. Publishing crashed. ┘
|
||||
5. Crashed because of validation error. > Errors with logs.
|
||||
5. Crashed because of publish error. > Errors with logs.
|
||||
|
||||
This widget is shown if validation errors happened during validation part.
|
||||
This widget is shown if publish errors happened.
|
||||
|
||||
Shows validation error titles with instances on which they happened
|
||||
and validation error detail with possible actions (repair).
|
||||
Shows publish error titles with instances on which they happened
|
||||
and publish error detail with possible actions (repair).
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
|
|
|
|||
|
|
@ -1182,6 +1182,10 @@ class GlobalAttrsWidget(QtWidgets.QWidget):
|
|||
invalid_tasks = False
|
||||
folder_paths = []
|
||||
for instance in self._current_instances:
|
||||
# Ignore instances that have promised context
|
||||
if instance.has_promised_context:
|
||||
continue
|
||||
|
||||
new_variant_value = instance.get("variant")
|
||||
new_folder_path = instance.get("folderPath")
|
||||
new_task_name = instance.get("task")
|
||||
|
|
@ -1206,7 +1210,6 @@ class GlobalAttrsWidget(QtWidgets.QWidget):
|
|||
|
||||
except TaskNotSetError:
|
||||
invalid_tasks = True
|
||||
instance.set_task_invalid(True)
|
||||
product_names.add(instance["productName"])
|
||||
continue
|
||||
|
||||
|
|
@ -1216,11 +1219,9 @@ class GlobalAttrsWidget(QtWidgets.QWidget):
|
|||
|
||||
if folder_path is not None:
|
||||
instance["folderPath"] = folder_path
|
||||
instance.set_folder_invalid(False)
|
||||
|
||||
if task_name is not None:
|
||||
instance["task"] = task_name or None
|
||||
instance.set_task_invalid(False)
|
||||
|
||||
instance["productName"] = new_product_name
|
||||
|
||||
|
|
@ -1306,7 +1307,13 @@ class GlobalAttrsWidget(QtWidgets.QWidget):
|
|||
editable = False
|
||||
|
||||
folder_task_combinations = []
|
||||
context_editable = None
|
||||
for instance in instances:
|
||||
if not instance.has_promised_context:
|
||||
context_editable = True
|
||||
elif context_editable is None:
|
||||
context_editable = False
|
||||
|
||||
# NOTE I'm not sure how this can even happen?
|
||||
if instance.creator_identifier is None:
|
||||
editable = False
|
||||
|
|
@ -1319,6 +1326,11 @@ class GlobalAttrsWidget(QtWidgets.QWidget):
|
|||
folder_task_combinations.append((folder_path, task_name))
|
||||
product_names.add(instance.get("productName") or self.unknown_value)
|
||||
|
||||
if not editable:
|
||||
context_editable = False
|
||||
elif context_editable is None:
|
||||
context_editable = True
|
||||
|
||||
self.variant_input.set_value(variants)
|
||||
|
||||
# Set context of folder widget
|
||||
|
|
@ -1329,8 +1341,21 @@ class GlobalAttrsWidget(QtWidgets.QWidget):
|
|||
self.product_value_widget.set_value(product_names)
|
||||
|
||||
self.variant_input.setEnabled(editable)
|
||||
self.folder_value_widget.setEnabled(editable)
|
||||
self.task_value_widget.setEnabled(editable)
|
||||
self.folder_value_widget.setEnabled(context_editable)
|
||||
self.task_value_widget.setEnabled(context_editable)
|
||||
|
||||
if not editable:
|
||||
folder_tooltip = "Select instances to change folder path."
|
||||
task_tooltip = "Select instances to change task name."
|
||||
elif not context_editable:
|
||||
folder_tooltip = "Folder path is defined by Create plugin."
|
||||
task_tooltip = "Task is defined by Create plugin."
|
||||
else:
|
||||
folder_tooltip = "Change folder path of selected instances."
|
||||
task_tooltip = "Change task of selected instances."
|
||||
|
||||
self.folder_value_widget.setToolTip(folder_tooltip)
|
||||
self.task_value_widget.setToolTip(task_tooltip)
|
||||
|
||||
|
||||
class CreatorAttrsWidget(QtWidgets.QWidget):
|
||||
|
|
@ -1339,7 +1364,7 @@ class CreatorAttrsWidget(QtWidgets.QWidget):
|
|||
Attributes are defined on creator so are dynamic. Their look and type is
|
||||
based on attribute definitions that are defined in
|
||||
`~/ayon_core/lib/attribute_definitions.py` and their widget
|
||||
representation in `~/openpype/tools/attribute_defs/*`.
|
||||
representation in `~/ayon_core/tools/attribute_defs/*`.
|
||||
|
||||
Widgets are disabled if context of instance is not valid.
|
||||
|
||||
|
|
@ -1768,9 +1793,16 @@ class ProductAttributesWidget(QtWidgets.QWidget):
|
|||
self.bottom_separator = bottom_separator
|
||||
|
||||
def _on_instance_context_changed(self):
|
||||
instance_ids = {
|
||||
instance.id
|
||||
for instance in self._current_instances
|
||||
}
|
||||
context_info_by_id = self._controller.get_instances_context_info(
|
||||
instance_ids
|
||||
)
|
||||
all_valid = True
|
||||
for instance in self._current_instances:
|
||||
if not instance.has_valid_context:
|
||||
for instance_id, context_info in context_info_by_id.items():
|
||||
if not context_info.is_valid:
|
||||
all_valid = False
|
||||
break
|
||||
|
||||
|
|
@ -1795,9 +1827,17 @@ class ProductAttributesWidget(QtWidgets.QWidget):
|
|||
convertor_identifiers(List[str]): Identifiers of convert items.
|
||||
"""
|
||||
|
||||
instance_ids = {
|
||||
instance.id
|
||||
for instance in instances
|
||||
}
|
||||
context_info_by_id = self._controller.get_instances_context_info(
|
||||
instance_ids
|
||||
)
|
||||
|
||||
all_valid = True
|
||||
for instance in instances:
|
||||
if not instance.has_valid_context:
|
||||
for context_info in context_info_by_id.values():
|
||||
if not context_info.is_valid:
|
||||
all_valid = False
|
||||
break
|
||||
|
||||
|
|
|
|||
|
|
@ -439,10 +439,13 @@ class PublisherWindow(QtWidgets.QDialog):
|
|||
def make_sure_is_visible(self):
|
||||
if self._window_is_visible:
|
||||
self.setWindowState(QtCore.Qt.WindowActive)
|
||||
|
||||
else:
|
||||
self.show()
|
||||
|
||||
self.raise_()
|
||||
self.activateWindow()
|
||||
self.showNormal()
|
||||
|
||||
def showEvent(self, event):
|
||||
self._window_is_visible = True
|
||||
super().showEvent(event)
|
||||
|
|
@ -913,12 +916,18 @@ class PublisherWindow(QtWidgets.QDialog):
|
|||
self._set_footer_enabled(True)
|
||||
return
|
||||
|
||||
active_instances_by_id = {
|
||||
instance.id: instance
|
||||
for instance in self._controller.get_instances()
|
||||
if instance["active"]
|
||||
}
|
||||
context_info_by_id = self._controller.get_instances_context_info(
|
||||
active_instances_by_id.keys()
|
||||
)
|
||||
all_valid = None
|
||||
for instance in self._controller.get_instances():
|
||||
if not instance["active"]:
|
||||
continue
|
||||
|
||||
if not instance.has_valid_context:
|
||||
for instance_id, instance in active_instances_by_id.items():
|
||||
context_info = context_info_by_id[instance_id]
|
||||
if not context_info.is_valid:
|
||||
all_valid = False
|
||||
break
|
||||
|
||||
|
|
|
|||
|
|
@ -777,7 +777,7 @@ class ProjectPushItemProcess:
|
|||
task_info = copy.deepcopy(task_info)
|
||||
task_info["name"] = dst_task_name
|
||||
# Fill rest of task information based on task type
|
||||
task_type_name = task_info["type"]
|
||||
task_type_name = task_info["taskType"]
|
||||
task_types_by_name = {
|
||||
task_type["name"]: task_type
|
||||
for task_type in self._project_entity["taskTypes"]
|
||||
|
|
@ -821,7 +821,7 @@ class ProjectPushItemProcess:
|
|||
task_name = task_type = None
|
||||
if task_info:
|
||||
task_name = task_info["name"]
|
||||
task_type = task_info["type"]
|
||||
task_type = task_info["taskType"]
|
||||
|
||||
product_name = get_product_name(
|
||||
self._item.dst_project_name,
|
||||
|
|
@ -905,7 +905,7 @@ class ProjectPushItemProcess:
|
|||
project_name,
|
||||
self.host_name,
|
||||
task_name=self._task_info["name"],
|
||||
task_type=self._task_info["type"],
|
||||
task_type=self._task_info["taskType"],
|
||||
product_type=product_type,
|
||||
product_name=product_entity["name"]
|
||||
)
|
||||
|
|
@ -959,7 +959,7 @@ class ProjectPushItemProcess:
|
|||
formatting_data = get_template_data(
|
||||
self._project_entity,
|
||||
self._folder_entity,
|
||||
self._task_info.get("name"),
|
||||
self._task_info,
|
||||
self.host_name
|
||||
)
|
||||
formatting_data.update({
|
||||
|
|
|
|||
|
|
@ -135,7 +135,6 @@ class OrderGroups:
|
|||
|
||||
def env_variable_to_bool(env_key, default=False):
|
||||
"""Boolean based on environment variable value."""
|
||||
# TODO: move to pype lib
|
||||
value = os.environ.get(env_key)
|
||||
if value is not None:
|
||||
value = value.lower()
|
||||
|
|
|
|||
|
|
@ -578,7 +578,7 @@ def make_sure_tray_is_running(
|
|||
args = get_ayon_launcher_args("tray", "--force")
|
||||
if env is None:
|
||||
env = os.environ.copy()
|
||||
|
||||
|
||||
# Make sure 'QT_API' is not set
|
||||
env.pop("QT_API", None)
|
||||
|
||||
|
|
|
|||
|
|
@ -237,11 +237,8 @@ class TrayAddonsManager(AddonsManager):
|
|||
webserver_url = self.webserver_url
|
||||
statics_url = f"{webserver_url}/res"
|
||||
|
||||
# Deprecated
|
||||
# TODO stop using these env variables
|
||||
# - function 'get_tray_server_url' should be used instead
|
||||
os.environ[self.webserver_url_env] = webserver_url
|
||||
os.environ["AYON_STATICS_SERVER"] = statics_url
|
||||
|
||||
# Deprecated
|
||||
os.environ["OPENPYPE_WEBSERVER_URL"] = webserver_url
|
||||
os.environ["OPENPYPE_STATICS_SERVER"] = statics_url
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ from .lib import (
|
|||
qt_app_context,
|
||||
get_qt_app,
|
||||
get_ayon_qt_app,
|
||||
get_openpype_qt_app,
|
||||
get_qt_icon,
|
||||
)
|
||||
|
||||
|
|
@ -122,7 +121,6 @@ __all__ = (
|
|||
"qt_app_context",
|
||||
"get_qt_app",
|
||||
"get_ayon_qt_app",
|
||||
"get_openpype_qt_app",
|
||||
"get_qt_icon",
|
||||
|
||||
"RecursiveSortFilterProxyModel",
|
||||
|
|
|
|||
|
|
@ -196,10 +196,6 @@ def get_ayon_qt_app():
|
|||
return app
|
||||
|
||||
|
||||
def get_openpype_qt_app():
|
||||
return get_ayon_qt_app()
|
||||
|
||||
|
||||
def iter_model_rows(model, column=0, include_root=False):
|
||||
"""Iterate over all row indices in a model"""
|
||||
indexes_queue = collections.deque()
|
||||
|
|
|
|||
|
|
@ -540,11 +540,38 @@ class ClassicExpandBtnLabel(ExpandBtnLabel):
|
|||
right_arrow_path = get_style_image_path("right_arrow")
|
||||
down_arrow_path = get_style_image_path("down_arrow")
|
||||
|
||||
def _normalize_pixmap(self, pixmap):
|
||||
if pixmap.width() == pixmap.height():
|
||||
return pixmap
|
||||
width = pixmap.width()
|
||||
height = pixmap.height()
|
||||
size = max(width, height)
|
||||
pos_x = 0
|
||||
pos_y = 0
|
||||
if width > height:
|
||||
pos_y = (size - height) // 2
|
||||
else:
|
||||
pos_x = (size - width) // 2
|
||||
|
||||
new_pix = QtGui.QPixmap(size, size)
|
||||
new_pix.fill(QtCore.Qt.transparent)
|
||||
painter = QtGui.QPainter(new_pix)
|
||||
render_hints = (
|
||||
QtGui.QPainter.Antialiasing
|
||||
| QtGui.QPainter.SmoothPixmapTransform
|
||||
)
|
||||
if hasattr(QtGui.QPainter, "HighQualityAntialiasing"):
|
||||
render_hints |= QtGui.QPainter.HighQualityAntialiasing
|
||||
painter.setRenderHints(render_hints)
|
||||
painter.drawPixmap(QtCore.QPoint(pos_x, pos_y), pixmap)
|
||||
painter.end()
|
||||
return new_pix
|
||||
|
||||
def _create_collapsed_pixmap(self):
|
||||
return QtGui.QPixmap(self.right_arrow_path)
|
||||
return self._normalize_pixmap(QtGui.QPixmap(self.right_arrow_path))
|
||||
|
||||
def _create_expanded_pixmap(self):
|
||||
return QtGui.QPixmap(self.down_arrow_path)
|
||||
return self._normalize_pixmap(QtGui.QPixmap(self.down_arrow_path))
|
||||
|
||||
|
||||
class ClassicExpandBtn(ExpandBtn):
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ def main(title="Scripts", parent=None, objectName=None):
|
|||
|
||||
# Register control + shift callback to add to shelf (maya behavior)
|
||||
modifiers = QtCore.Qt.ControlModifier | QtCore.Qt.ShiftModifier
|
||||
if int(cmds.about(version=True)) <= 2025:
|
||||
if int(cmds.about(version=True)) < 2025:
|
||||
modifiers = int(modifiers)
|
||||
|
||||
menu.register_callback(modifiers, to_shelf)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Package declaring AYON core addon version."""
|
||||
__version__ = "0.4.5-dev.1"
|
||||
"""Package declaring AYON addon 'core' version."""
|
||||
__version__ = "1.0.1+dev"
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""Prepares server package from addon repo to upload to server.
|
||||
|
||||
Requires Python 3.9. (Or at least 3.8+).
|
||||
|
|
@ -22,32 +24,39 @@ client side code zipped in `private` subfolder.
|
|||
import os
|
||||
import sys
|
||||
import re
|
||||
import io
|
||||
import shutil
|
||||
import argparse
|
||||
import platform
|
||||
import argparse
|
||||
import logging
|
||||
import collections
|
||||
import zipfile
|
||||
import hashlib
|
||||
import subprocess
|
||||
from typing import Optional, Iterable, Pattern, Union, List, Tuple
|
||||
|
||||
from typing import Optional
|
||||
import package
|
||||
|
||||
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
PACKAGE_PATH = os.path.join(CURRENT_DIR, "package.py")
|
||||
package_content = {}
|
||||
with open(PACKAGE_PATH, "r") as stream:
|
||||
exec(stream.read(), package_content)
|
||||
FileMapping = Tuple[Union[str, io.BytesIO], str]
|
||||
ADDON_NAME: str = package.name
|
||||
ADDON_VERSION: str = package.version
|
||||
ADDON_CLIENT_DIR: Union[str, None] = getattr(package, "client_dir", None)
|
||||
|
||||
ADDON_VERSION = package_content["version"]
|
||||
ADDON_NAME = package_content["name"]
|
||||
ADDON_CLIENT_DIR = package_content["client_dir"]
|
||||
CLIENT_VERSION_CONTENT = '''# -*- coding: utf-8 -*-
|
||||
"""Package declaring AYON core addon version."""
|
||||
__version__ = "{}"
|
||||
CURRENT_ROOT: str = os.path.dirname(os.path.abspath(__file__))
|
||||
SERVER_ROOT: str = os.path.join(CURRENT_ROOT, "server")
|
||||
FRONTEND_ROOT: str = os.path.join(CURRENT_ROOT, "frontend")
|
||||
FRONTEND_DIST_ROOT: str = os.path.join(FRONTEND_ROOT, "dist")
|
||||
DST_DIST_DIR: str = os.path.join("frontend", "dist")
|
||||
PRIVATE_ROOT: str = os.path.join(CURRENT_ROOT, "private")
|
||||
PUBLIC_ROOT: str = os.path.join(CURRENT_ROOT, "public")
|
||||
CLIENT_ROOT: str = os.path.join(CURRENT_ROOT, "client")
|
||||
|
||||
VERSION_PY_CONTENT = f'''# -*- coding: utf-8 -*-
|
||||
"""Package declaring AYON addon '{ADDON_NAME}' version."""
|
||||
__version__ = "{ADDON_VERSION}"
|
||||
'''
|
||||
|
||||
# Patterns of directories to be skipped for server part of addon
|
||||
IGNORE_DIR_PATTERNS = [
|
||||
IGNORE_DIR_PATTERNS: List[Pattern] = [
|
||||
re.compile(pattern)
|
||||
for pattern in {
|
||||
# Skip directories starting with '.'
|
||||
|
|
@ -58,7 +67,7 @@ IGNORE_DIR_PATTERNS = [
|
|||
]
|
||||
|
||||
# Patterns of files to be skipped for server part of addon
|
||||
IGNORE_FILE_PATTERNS = [
|
||||
IGNORE_FILE_PATTERNS: List[Pattern] = [
|
||||
re.compile(pattern)
|
||||
for pattern in {
|
||||
# Skip files starting with '.'
|
||||
|
|
@ -70,15 +79,6 @@ IGNORE_FILE_PATTERNS = [
|
|||
]
|
||||
|
||||
|
||||
def calculate_file_checksum(filepath, hash_algorithm, chunk_size=10000):
|
||||
func = getattr(hashlib, hash_algorithm)
|
||||
hash_obj = func()
|
||||
with open(filepath, "rb") as f:
|
||||
for chunk in iter(lambda: f.read(chunk_size), b""):
|
||||
hash_obj.update(chunk)
|
||||
return hash_obj.hexdigest()
|
||||
|
||||
|
||||
class ZipFileLongPaths(zipfile.ZipFile):
|
||||
"""Allows longer paths in zip files.
|
||||
|
||||
|
|
@ -97,12 +97,28 @@ class ZipFileLongPaths(zipfile.ZipFile):
|
|||
else:
|
||||
tpath = "\\\\?\\" + tpath
|
||||
|
||||
return super(ZipFileLongPaths, self)._extract_member(
|
||||
member, tpath, pwd
|
||||
)
|
||||
return super()._extract_member(member, tpath, pwd)
|
||||
|
||||
|
||||
def safe_copy_file(src_path, dst_path):
|
||||
def _get_yarn_executable() -> Union[str, None]:
|
||||
cmd = "which"
|
||||
if platform.system().lower() == "windows":
|
||||
cmd = "where"
|
||||
|
||||
for line in subprocess.check_output(
|
||||
[cmd, "yarn"], encoding="utf-8"
|
||||
).splitlines():
|
||||
if not line or not os.path.exists(line):
|
||||
continue
|
||||
try:
|
||||
subprocess.call([line, "--version"])
|
||||
return line
|
||||
except OSError:
|
||||
continue
|
||||
return None
|
||||
|
||||
|
||||
def safe_copy_file(src_path: str, dst_path: str):
|
||||
"""Copy file and make sure destination directory exists.
|
||||
|
||||
Ignore if destination already contains directories from source.
|
||||
|
|
@ -115,210 +131,335 @@ def safe_copy_file(src_path, dst_path):
|
|||
if src_path == dst_path:
|
||||
return
|
||||
|
||||
dst_dir = os.path.dirname(dst_path)
|
||||
try:
|
||||
os.makedirs(dst_dir)
|
||||
except Exception:
|
||||
pass
|
||||
dst_dir: str = os.path.dirname(dst_path)
|
||||
os.makedirs(dst_dir, exist_ok=True)
|
||||
|
||||
shutil.copy2(src_path, dst_path)
|
||||
|
||||
|
||||
def _value_match_regexes(value, regexes):
|
||||
for regex in regexes:
|
||||
if regex.search(value):
|
||||
return True
|
||||
return False
|
||||
def _value_match_regexes(value: str, regexes: Iterable[Pattern]) -> bool:
|
||||
return any(
|
||||
regex.search(value)
|
||||
for regex in regexes
|
||||
)
|
||||
|
||||
|
||||
def find_files_in_subdir(
|
||||
src_path,
|
||||
ignore_file_patterns=None,
|
||||
ignore_dir_patterns=None
|
||||
):
|
||||
src_path: str,
|
||||
ignore_file_patterns: Optional[List[Pattern]] = None,
|
||||
ignore_dir_patterns: Optional[List[Pattern]] = None
|
||||
) -> List[Tuple[str, str]]:
|
||||
"""Find all files to copy in subdirectories of given path.
|
||||
|
||||
All files that match any of the patterns in 'ignore_file_patterns' will
|
||||
be skipped and any directories that match any of the patterns in
|
||||
'ignore_dir_patterns' will be skipped with all subfiles.
|
||||
|
||||
Args:
|
||||
src_path (str): Path to directory to search in.
|
||||
ignore_file_patterns (Optional[list[Pattern]]): List of regexes
|
||||
to match files to ignore.
|
||||
ignore_dir_patterns (Optional[list[Pattern]]): List of regexes
|
||||
to match directories to ignore.
|
||||
|
||||
Returns:
|
||||
list[tuple[str, str]]: List of tuples with path to file and parent
|
||||
directories relative to 'src_path'.
|
||||
"""
|
||||
|
||||
if ignore_file_patterns is None:
|
||||
ignore_file_patterns = IGNORE_FILE_PATTERNS
|
||||
|
||||
if ignore_dir_patterns is None:
|
||||
ignore_dir_patterns = IGNORE_DIR_PATTERNS
|
||||
output = []
|
||||
output: List[Tuple[str, str]] = []
|
||||
if not os.path.exists(src_path):
|
||||
return output
|
||||
|
||||
hierarchy_queue = collections.deque()
|
||||
hierarchy_queue: collections.deque = collections.deque()
|
||||
hierarchy_queue.append((src_path, []))
|
||||
while hierarchy_queue:
|
||||
item = hierarchy_queue.popleft()
|
||||
item: Tuple[str, str] = hierarchy_queue.popleft()
|
||||
dirpath, parents = item
|
||||
for name in os.listdir(dirpath):
|
||||
path = os.path.join(dirpath, name)
|
||||
path: str = os.path.join(dirpath, name)
|
||||
if os.path.isfile(path):
|
||||
if not _value_match_regexes(name, ignore_file_patterns):
|
||||
items = list(parents)
|
||||
items: List[str] = list(parents)
|
||||
items.append(name)
|
||||
output.append((path, os.path.sep.join(items)))
|
||||
continue
|
||||
|
||||
if not _value_match_regexes(name, ignore_dir_patterns):
|
||||
items = list(parents)
|
||||
items: List[str] = list(parents)
|
||||
items.append(name)
|
||||
hierarchy_queue.append((path, items))
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def copy_server_content(addon_output_dir, current_dir, log):
|
||||
def update_client_version(logger):
|
||||
"""Update version in client code if version.py is present."""
|
||||
if not ADDON_CLIENT_DIR:
|
||||
return
|
||||
|
||||
version_path: str = os.path.join(
|
||||
CLIENT_ROOT, ADDON_CLIENT_DIR, "version.py"
|
||||
)
|
||||
if not os.path.exists(version_path):
|
||||
logger.debug("Did not find version.py in client directory")
|
||||
return
|
||||
|
||||
logger.info("Updating client version")
|
||||
with open(version_path, "w") as stream:
|
||||
stream.write(VERSION_PY_CONTENT)
|
||||
|
||||
|
||||
def update_pyproject_toml(logger):
|
||||
filepath = os.path.join(CURRENT_ROOT, "pyproject.toml")
|
||||
new_lines = []
|
||||
with open(filepath, "r") as stream:
|
||||
version_found = False
|
||||
for line in stream.readlines():
|
||||
if not version_found and line.startswith("version ="):
|
||||
line = f'version = "{ADDON_VERSION}"\n'
|
||||
version_found = True
|
||||
|
||||
new_lines.append(line)
|
||||
|
||||
with open(filepath, "w") as stream:
|
||||
stream.write("".join(new_lines))
|
||||
|
||||
|
||||
def build_frontend():
|
||||
yarn_executable = _get_yarn_executable()
|
||||
if yarn_executable is None:
|
||||
raise RuntimeError("Yarn executable was not found.")
|
||||
|
||||
subprocess.run([yarn_executable, "install"], cwd=FRONTEND_ROOT)
|
||||
subprocess.run([yarn_executable, "build"], cwd=FRONTEND_ROOT)
|
||||
if not os.path.exists(FRONTEND_DIST_ROOT):
|
||||
raise RuntimeError(
|
||||
"Frontend build failed. Did not find 'dist' folder."
|
||||
)
|
||||
|
||||
|
||||
def get_client_files_mapping() -> List[Tuple[str, str]]:
|
||||
"""Mapping of source client code files to destination paths.
|
||||
|
||||
Example output:
|
||||
[
|
||||
(
|
||||
"C:/addons/MyAddon/version.py",
|
||||
"my_addon/version.py"
|
||||
),
|
||||
(
|
||||
"C:/addons/MyAddon/client/my_addon/__init__.py",
|
||||
"my_addon/__init__.py"
|
||||
)
|
||||
]
|
||||
|
||||
Returns:
|
||||
list[tuple[str, str]]: List of path mappings to copy. The destination
|
||||
path is relative to expected output directory.
|
||||
|
||||
"""
|
||||
# Add client code content to zip
|
||||
client_code_dir: str = os.path.join(CLIENT_ROOT, ADDON_CLIENT_DIR)
|
||||
mapping = [
|
||||
(path, os.path.join(ADDON_CLIENT_DIR, sub_path))
|
||||
for path, sub_path in find_files_in_subdir(client_code_dir)
|
||||
]
|
||||
|
||||
license_path = os.path.join(CURRENT_ROOT, "LICENSE")
|
||||
if os.path.exists(license_path):
|
||||
mapping.append((license_path, f"{ADDON_CLIENT_DIR}/LICENSE"))
|
||||
return mapping
|
||||
|
||||
|
||||
def get_client_zip_content(log) -> io.BytesIO:
|
||||
log.info("Preparing client code zip")
|
||||
files_mapping: List[Tuple[str, str]] = get_client_files_mapping()
|
||||
stream = io.BytesIO()
|
||||
with ZipFileLongPaths(stream, "w", zipfile.ZIP_DEFLATED) as zipf:
|
||||
for src_path, subpath in files_mapping:
|
||||
zipf.write(src_path, subpath)
|
||||
stream.seek(0)
|
||||
return stream
|
||||
|
||||
|
||||
def get_base_files_mapping() -> List[FileMapping]:
|
||||
filepaths_to_copy: List[FileMapping] = [
|
||||
(
|
||||
os.path.join(CURRENT_ROOT, "package.py"),
|
||||
"package.py"
|
||||
)
|
||||
]
|
||||
# Add license file to package if exists
|
||||
license_path = os.path.join(CURRENT_ROOT, "LICENSE")
|
||||
if os.path.exists(license_path):
|
||||
filepaths_to_copy.append((license_path, "LICENSE"))
|
||||
|
||||
# Go through server, private and public directories and find all files
|
||||
for dirpath in (SERVER_ROOT, PRIVATE_ROOT, PUBLIC_ROOT):
|
||||
if not os.path.exists(dirpath):
|
||||
continue
|
||||
|
||||
dirname = os.path.basename(dirpath)
|
||||
for src_file, subpath in find_files_in_subdir(dirpath):
|
||||
dst_subpath = os.path.join(dirname, subpath)
|
||||
filepaths_to_copy.append((src_file, dst_subpath))
|
||||
|
||||
if os.path.exists(FRONTEND_DIST_ROOT):
|
||||
for src_file, subpath in find_files_in_subdir(FRONTEND_DIST_ROOT):
|
||||
dst_subpath = os.path.join(DST_DIST_DIR, subpath)
|
||||
filepaths_to_copy.append((src_file, dst_subpath))
|
||||
|
||||
pyproject_toml = os.path.join(CLIENT_ROOT, "pyproject.toml")
|
||||
if os.path.exists(pyproject_toml):
|
||||
filepaths_to_copy.append(
|
||||
(pyproject_toml, "private/pyproject.toml")
|
||||
)
|
||||
|
||||
return filepaths_to_copy
|
||||
|
||||
|
||||
def copy_client_code(output_dir: str, log: logging.Logger):
|
||||
"""Copies server side folders to 'addon_package_dir'
|
||||
|
||||
Args:
|
||||
addon_output_dir (str): package dir in addon repo dir
|
||||
current_dir (str): addon repo dir
|
||||
output_dir (str): Output directory path.
|
||||
log (logging.Logger)
|
||||
|
||||
"""
|
||||
log.info(f"Copying client for {ADDON_NAME}-{ADDON_VERSION}")
|
||||
|
||||
log.info("Copying server content")
|
||||
full_output_path = os.path.join(
|
||||
output_dir, f"{ADDON_NAME}_{ADDON_VERSION}"
|
||||
)
|
||||
if os.path.exists(full_output_path):
|
||||
shutil.rmtree(full_output_path)
|
||||
os.makedirs(full_output_path, exist_ok=True)
|
||||
|
||||
filepaths_to_copy = []
|
||||
server_dirpath = os.path.join(current_dir, "server")
|
||||
|
||||
for item in find_files_in_subdir(server_dirpath):
|
||||
src_path, dst_subpath = item
|
||||
dst_path = os.path.join(addon_output_dir, "server", dst_subpath)
|
||||
filepaths_to_copy.append((src_path, dst_path))
|
||||
|
||||
# Copy files
|
||||
for src_path, dst_path in filepaths_to_copy:
|
||||
for src_path, dst_subpath in get_client_files_mapping():
|
||||
dst_path = os.path.join(full_output_path, dst_subpath)
|
||||
safe_copy_file(src_path, dst_path)
|
||||
|
||||
|
||||
def _update_client_version(client_addon_dir):
|
||||
"""Write version.py file to 'client' directory.
|
||||
|
||||
Make sure the version in client dir is the same as in package.py.
|
||||
|
||||
Args:
|
||||
client_addon_dir (str): Directory path of client addon.
|
||||
"""
|
||||
|
||||
dst_version_path = os.path.join(client_addon_dir, "version.py")
|
||||
with open(dst_version_path, "w") as stream:
|
||||
stream.write(CLIENT_VERSION_CONTENT.format(ADDON_VERSION))
|
||||
log.info("Client copy finished")
|
||||
|
||||
|
||||
def zip_client_side(addon_package_dir, current_dir, log):
|
||||
"""Copy and zip `client` content into 'addon_package_dir'.
|
||||
|
||||
Args:
|
||||
addon_package_dir (str): Output package directory path.
|
||||
current_dir (str): Directory path of addon source.
|
||||
log (logging.Logger): Logger object.
|
||||
"""
|
||||
|
||||
client_dir = os.path.join(current_dir, "client")
|
||||
client_addon_dir = os.path.join(client_dir, ADDON_CLIENT_DIR)
|
||||
if not os.path.isdir(client_addon_dir):
|
||||
raise ValueError(
|
||||
f"Failed to find client directory '{client_addon_dir}'"
|
||||
)
|
||||
|
||||
log.info("Preparing client code zip")
|
||||
private_dir = os.path.join(addon_package_dir, "private")
|
||||
|
||||
if not os.path.exists(private_dir):
|
||||
os.makedirs(private_dir)
|
||||
|
||||
_update_client_version(client_addon_dir)
|
||||
|
||||
zip_filepath = os.path.join(os.path.join(private_dir, "client.zip"))
|
||||
with ZipFileLongPaths(zip_filepath, "w", zipfile.ZIP_DEFLATED) as zipf:
|
||||
# Add client code content to zip
|
||||
for path, sub_path in find_files_in_subdir(client_addon_dir):
|
||||
sub_path = os.path.join(ADDON_CLIENT_DIR, sub_path)
|
||||
zipf.write(path, sub_path)
|
||||
|
||||
shutil.copy(os.path.join(client_dir, "pyproject.toml"), private_dir)
|
||||
|
||||
|
||||
def create_server_package(
|
||||
def copy_addon_package(
|
||||
output_dir: str,
|
||||
addon_output_dir: str,
|
||||
files_mapping: List[FileMapping],
|
||||
log: logging.Logger
|
||||
):
|
||||
"""Create server package zip file.
|
||||
|
||||
The zip file can be installed to a server using UI or rest api endpoints.
|
||||
"""Copy client code to output directory.
|
||||
|
||||
Args:
|
||||
output_dir (str): Directory path to output zip file.
|
||||
addon_output_dir (str): Directory path to addon output directory.
|
||||
output_dir (str): Directory path to output client code.
|
||||
files_mapping (List[FileMapping]): List of tuples with source file
|
||||
and destination subpath.
|
||||
log (logging.Logger): Logger object.
|
||||
"""
|
||||
|
||||
log.info("Creating server package")
|
||||
"""
|
||||
log.info(f"Copying package for {ADDON_NAME}-{ADDON_VERSION}")
|
||||
|
||||
# Add addon name and version to output directory
|
||||
addon_output_dir: str = os.path.join(
|
||||
output_dir, ADDON_NAME, ADDON_VERSION
|
||||
)
|
||||
if os.path.isdir(addon_output_dir):
|
||||
log.info(f"Purging {addon_output_dir}")
|
||||
shutil.rmtree(addon_output_dir)
|
||||
|
||||
os.makedirs(addon_output_dir, exist_ok=True)
|
||||
|
||||
# Copy server content
|
||||
for src_file, dst_subpath in files_mapping:
|
||||
dst_path: str = os.path.join(addon_output_dir, dst_subpath)
|
||||
dst_dir: str = os.path.dirname(dst_path)
|
||||
os.makedirs(dst_dir, exist_ok=True)
|
||||
if isinstance(src_file, io.BytesIO):
|
||||
with open(dst_path, "wb") as stream:
|
||||
stream.write(src_file.getvalue())
|
||||
else:
|
||||
safe_copy_file(src_file, dst_path)
|
||||
|
||||
log.info("Package copy finished")
|
||||
|
||||
|
||||
def create_addon_package(
|
||||
output_dir: str,
|
||||
files_mapping: List[FileMapping],
|
||||
log: logging.Logger
|
||||
):
|
||||
log.info(f"Creating package for {ADDON_NAME}-{ADDON_VERSION}")
|
||||
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
output_path = os.path.join(
|
||||
output_dir, f"{ADDON_NAME}-{ADDON_VERSION}.zip"
|
||||
)
|
||||
|
||||
with ZipFileLongPaths(output_path, "w", zipfile.ZIP_DEFLATED) as zipf:
|
||||
# Move addon content to zip into 'addon' directory
|
||||
addon_output_dir_offset = len(addon_output_dir) + 1
|
||||
for root, _, filenames in os.walk(addon_output_dir):
|
||||
if not filenames:
|
||||
continue
|
||||
# Copy server content
|
||||
for src_file, dst_subpath in files_mapping:
|
||||
if isinstance(src_file, io.BytesIO):
|
||||
zipf.writestr(dst_subpath, src_file.getvalue())
|
||||
else:
|
||||
zipf.write(src_file, dst_subpath)
|
||||
|
||||
dst_root = None
|
||||
if root != addon_output_dir:
|
||||
dst_root = root[addon_output_dir_offset:]
|
||||
for filename in filenames:
|
||||
src_path = os.path.join(root, filename)
|
||||
dst_path = filename
|
||||
if dst_root:
|
||||
dst_path = os.path.join(dst_root, dst_path)
|
||||
zipf.write(src_path, dst_path)
|
||||
|
||||
log.info(f"Output package can be found: {output_path}")
|
||||
log.info("Package created")
|
||||
|
||||
|
||||
def main(
|
||||
output_dir: Optional[str]=None,
|
||||
skip_zip: bool=False,
|
||||
keep_sources: bool=False,
|
||||
clear_output_dir: bool=False
|
||||
output_dir: Optional[str] = None,
|
||||
skip_zip: Optional[bool] = False,
|
||||
only_client: Optional[bool] = False
|
||||
):
|
||||
log = logging.getLogger("create_package")
|
||||
log.info("Start creating package")
|
||||
log: logging.Logger = logging.getLogger("create_package")
|
||||
log.info("Package creation started")
|
||||
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
if not output_dir:
|
||||
output_dir = os.path.join(current_dir, "package")
|
||||
output_dir = os.path.join(CURRENT_ROOT, "package")
|
||||
|
||||
has_client_code = bool(ADDON_CLIENT_DIR)
|
||||
if has_client_code:
|
||||
client_dir: str = os.path.join(CLIENT_ROOT, ADDON_CLIENT_DIR)
|
||||
if not os.path.exists(client_dir):
|
||||
raise RuntimeError(
|
||||
f"Client directory was not found '{client_dir}'."
|
||||
" Please check 'client_dir' in 'package.py'."
|
||||
)
|
||||
update_client_version(log)
|
||||
|
||||
new_created_version_dir = os.path.join(
|
||||
output_dir, ADDON_NAME, ADDON_VERSION
|
||||
)
|
||||
update_pyproject_toml(log)
|
||||
|
||||
if os.path.isdir(new_created_version_dir) and clear_output_dir:
|
||||
log.info(f"Purging {new_created_version_dir}")
|
||||
shutil.rmtree(output_dir)
|
||||
if only_client:
|
||||
if not has_client_code:
|
||||
raise RuntimeError("Client code is not available. Skipping")
|
||||
|
||||
copy_client_code(output_dir, log)
|
||||
return
|
||||
|
||||
log.info(f"Preparing package for {ADDON_NAME}-{ADDON_VERSION}")
|
||||
|
||||
addon_output_root = os.path.join(output_dir, ADDON_NAME)
|
||||
addon_output_dir = os.path.join(addon_output_root, ADDON_VERSION)
|
||||
if not os.path.exists(addon_output_dir):
|
||||
os.makedirs(addon_output_dir)
|
||||
if os.path.exists(FRONTEND_ROOT):
|
||||
build_frontend()
|
||||
|
||||
copy_server_content(addon_output_dir, current_dir, log)
|
||||
safe_copy_file(
|
||||
PACKAGE_PATH,
|
||||
os.path.join(addon_output_dir, os.path.basename(PACKAGE_PATH))
|
||||
)
|
||||
zip_client_side(addon_output_dir, current_dir, log)
|
||||
files_mapping: List[FileMapping] = []
|
||||
files_mapping.extend(get_base_files_mapping())
|
||||
|
||||
if has_client_code:
|
||||
files_mapping.append(
|
||||
(get_client_zip_content(log), "private/client.zip")
|
||||
)
|
||||
|
||||
# Skip server zipping
|
||||
if not skip_zip:
|
||||
create_server_package(output_dir, addon_output_dir, log)
|
||||
# Remove sources only if zip file is created
|
||||
if not keep_sources:
|
||||
log.info("Removing source files for server package")
|
||||
shutil.rmtree(addon_output_root)
|
||||
if skip_zip:
|
||||
copy_addon_package(output_dir, files_mapping, log)
|
||||
else:
|
||||
create_addon_package(output_dir, files_mapping, log)
|
||||
|
||||
log.info("Package creation finished")
|
||||
|
||||
|
||||
|
|
@ -333,23 +474,6 @@ if __name__ == "__main__":
|
|||
" server folder structure."
|
||||
)
|
||||
)
|
||||
parser.add_argument(
|
||||
"--keep-sources",
|
||||
dest="keep_sources",
|
||||
action="store_true",
|
||||
help=(
|
||||
"Keep folder structure when server package is created."
|
||||
)
|
||||
)
|
||||
parser.add_argument(
|
||||
"-c", "--clear-output-dir",
|
||||
dest="clear_output_dir",
|
||||
action="store_true",
|
||||
help=(
|
||||
"Clear output directory before package creation."
|
||||
)
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-o", "--output",
|
||||
dest="output_dir",
|
||||
|
|
@ -359,11 +483,25 @@ if __name__ == "__main__":
|
|||
" (Will be purged if already exists!)"
|
||||
)
|
||||
)
|
||||
parser.add_argument(
|
||||
"--only-client",
|
||||
dest="only_client",
|
||||
action="store_true",
|
||||
help=(
|
||||
"Extract only client code. This is useful for development."
|
||||
" Requires '-o', '--output' argument to be filled."
|
||||
)
|
||||
)
|
||||
parser.add_argument(
|
||||
"--debug",
|
||||
dest="debug",
|
||||
action="store_true",
|
||||
help="Debug log messages."
|
||||
)
|
||||
|
||||
args = parser.parse_args(sys.argv[1:])
|
||||
main(
|
||||
args.output_dir,
|
||||
args.skip_zip,
|
||||
args.keep_sources,
|
||||
args.clear_output_dir
|
||||
)
|
||||
level = logging.INFO
|
||||
if args.debug:
|
||||
level = logging.DEBUG
|
||||
logging.basicConfig(level=level)
|
||||
main(args.output_dir, args.skip_zip, args.only_client)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
name = "core"
|
||||
title = "Core"
|
||||
version = "0.4.5-dev.1"
|
||||
version = "1.0.1+dev"
|
||||
|
||||
client_dir = "ayon_core"
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
[tool.poetry]
|
||||
name = "ayon-core"
|
||||
version = "0.4.3-dev.1"
|
||||
version = "1.0.1+dev"
|
||||
description = ""
|
||||
authors = ["Ynput Team <team@ynput.io>"]
|
||||
readme = "README.md"
|
||||
|
|
@ -23,6 +23,7 @@ ayon-python-api = "^1.0"
|
|||
ruff = "^0.3.3"
|
||||
pre-commit = "^3.6.2"
|
||||
codespell = "^2.2.6"
|
||||
semver = "^3.0.2"
|
||||
|
||||
|
||||
[tool.ruff]
|
||||
|
|
@ -67,7 +68,7 @@ target-version = "py39"
|
|||
|
||||
[tool.ruff.lint]
|
||||
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
|
||||
select = ["E4", "E7", "E9", "F"]
|
||||
select = ["E4", "E7", "E9", "F", "W"]
|
||||
ignore = []
|
||||
|
||||
# Allow fix for all enabled rules (when `--fix`) is provided.
|
||||
|
|
@ -84,7 +85,6 @@ exclude = [
|
|||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"client/ayon_core/lib/__init__.py" = ["E402"]
|
||||
"client/ayon_core/hosts/max/startup/startup.py" = ["E402"]
|
||||
|
||||
[tool.ruff.format]
|
||||
# Like Black, use double quotes for strings.
|
||||
|
|
@ -114,3 +114,12 @@ quiet-level = 3
|
|||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
log_cli = true
|
||||
log_cli_level = "INFO"
|
||||
addopts = "-ra -q"
|
||||
testpaths = [
|
||||
"client/ayon_core/tests"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -4,6 +4,29 @@ from typing import Any
|
|||
from .publish_plugins import DEFAULT_PUBLISH_VALUES
|
||||
|
||||
|
||||
def _convert_imageio_configs_0_4_5(overrides):
|
||||
"""Imageio config settings did change to profiles since 0.4.5."""
|
||||
imageio_overrides = overrides.get("imageio") or {}
|
||||
|
||||
# make sure settings are already converted to profiles
|
||||
ocio_config_profiles = imageio_overrides.get("ocio_config_profiles")
|
||||
if not ocio_config_profiles:
|
||||
return
|
||||
|
||||
for profile in ocio_config_profiles:
|
||||
if profile.get("type") != "product_name":
|
||||
continue
|
||||
|
||||
profile["type"] = "published_product"
|
||||
profile["published_product"] = {
|
||||
"product_name": profile.pop("product_name"),
|
||||
"fallback": {
|
||||
"type": "builtin_path",
|
||||
"builtin_path": "{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _convert_imageio_configs_0_3_1(overrides):
|
||||
"""Imageio config settings did change to profiles since 0.3.1. ."""
|
||||
imageio_overrides = overrides.get("imageio") or {}
|
||||
|
|
@ -71,10 +94,43 @@ def _convert_validate_version_0_3_3(publish_overrides):
|
|||
validate_version["plugin_state_profiles"] = [profile]
|
||||
|
||||
|
||||
def _conver_publish_plugins(overrides):
|
||||
def _convert_oiio_transcode_0_4_5(publish_overrides):
|
||||
"""ExtractOIIOTranscode plugin changed in 0.4.5."""
|
||||
if "ExtractOIIOTranscode" not in publish_overrides:
|
||||
return
|
||||
|
||||
transcode_profiles = publish_overrides["ExtractOIIOTranscode"].get(
|
||||
"profiles")
|
||||
if not transcode_profiles:
|
||||
return
|
||||
|
||||
for profile in transcode_profiles:
|
||||
outputs = profile.get("outputs")
|
||||
if outputs is None:
|
||||
return
|
||||
|
||||
for output in outputs:
|
||||
# Already new settings
|
||||
if "display_view" in output:
|
||||
break
|
||||
|
||||
# Fix 'display' -> 'display_view' in 'transcoding_type'
|
||||
transcode_type = output.get("transcoding_type")
|
||||
if transcode_type == "display":
|
||||
output["transcoding_type"] = "display_view"
|
||||
|
||||
# Convert 'display' and 'view' to new values
|
||||
output["display_view"] = {
|
||||
"display": output.pop("display", ""),
|
||||
"view": output.pop("view", ""),
|
||||
}
|
||||
|
||||
|
||||
def _convert_publish_plugins(overrides):
|
||||
if "publish" not in overrides:
|
||||
return
|
||||
_convert_validate_version_0_3_3(overrides["publish"])
|
||||
_convert_oiio_transcode_0_4_5(overrides["publish"])
|
||||
|
||||
|
||||
def convert_settings_overrides(
|
||||
|
|
@ -82,5 +138,6 @@ def convert_settings_overrides(
|
|||
overrides: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
_convert_imageio_configs_0_3_1(overrides)
|
||||
_conver_publish_plugins(overrides)
|
||||
_convert_imageio_configs_0_4_5(overrides)
|
||||
_convert_publish_plugins(overrides)
|
||||
return overrides
|
||||
|
|
|
|||
|
|
@ -58,7 +58,14 @@ def _ocio_config_profile_types():
|
|||
return [
|
||||
{"value": "builtin_path", "label": "AYON built-in OCIO config"},
|
||||
{"value": "custom_path", "label": "Path to OCIO config"},
|
||||
{"value": "product_name", "label": "Published product"},
|
||||
{"value": "published_product", "label": "Published product"},
|
||||
]
|
||||
|
||||
|
||||
def _fallback_ocio_config_profile_types():
|
||||
return [
|
||||
{"value": "builtin_path", "label": "AYON built-in OCIO config"},
|
||||
{"value": "custom_path", "label": "Path to OCIO config"},
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -76,6 +83,49 @@ def _ocio_built_in_paths():
|
|||
]
|
||||
|
||||
|
||||
class FallbackProductModel(BaseSettingsModel):
|
||||
_layout = "expanded"
|
||||
fallback_type: str = SettingsField(
|
||||
title="Fallback config type",
|
||||
enum_resolver=_fallback_ocio_config_profile_types,
|
||||
conditionalEnum=True,
|
||||
default="builtin_path",
|
||||
description=(
|
||||
"Type of config which needs to be used in case published "
|
||||
"product is not found."
|
||||
),
|
||||
)
|
||||
builtin_path: str = SettingsField(
|
||||
"ACES 1.2",
|
||||
title="Built-in OCIO config",
|
||||
enum_resolver=_ocio_built_in_paths,
|
||||
description=(
|
||||
"AYON ocio addon distributed OCIO config. "
|
||||
"Activated addon in bundle is required: 'ayon_ocio' >= 1.1.1"
|
||||
),
|
||||
)
|
||||
custom_path: str = SettingsField(
|
||||
"",
|
||||
title="OCIO config path",
|
||||
description="Path to OCIO config. Anatomy formatting is supported.",
|
||||
)
|
||||
|
||||
|
||||
class PublishedProductModel(BaseSettingsModel):
|
||||
_layout = "expanded"
|
||||
product_name: str = SettingsField(
|
||||
"",
|
||||
title="Product name",
|
||||
description=(
|
||||
"Context related published product name to get OCIO config from. "
|
||||
"Partial match is supported via use of regex expression."
|
||||
),
|
||||
)
|
||||
fallback: FallbackProductModel = SettingsField(
|
||||
default_factory=FallbackProductModel,
|
||||
)
|
||||
|
||||
|
||||
class CoreImageIOConfigProfilesModel(BaseSettingsModel):
|
||||
_layout = "expanded"
|
||||
host_names: list[str] = SettingsField(
|
||||
|
|
@ -102,19 +152,19 @@ class CoreImageIOConfigProfilesModel(BaseSettingsModel):
|
|||
"ACES 1.2",
|
||||
title="Built-in OCIO config",
|
||||
enum_resolver=_ocio_built_in_paths,
|
||||
description=(
|
||||
"AYON ocio addon distributed OCIO config. "
|
||||
"Activated addon in bundle is required: 'ayon_ocio' >= 1.1.1"
|
||||
),
|
||||
)
|
||||
custom_path: str = SettingsField(
|
||||
"",
|
||||
title="OCIO config path",
|
||||
description="Path to OCIO config. Anatomy formatting is supported.",
|
||||
)
|
||||
product_name: str = SettingsField(
|
||||
"",
|
||||
title="Product name",
|
||||
description=(
|
||||
"Published product name to get OCIO config from. "
|
||||
"Partial match is supported."
|
||||
),
|
||||
published_product: PublishedProductModel = SettingsField(
|
||||
default_factory=PublishedProductModel,
|
||||
title="Published product",
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -294,7 +344,14 @@ DEFAULT_VALUES = {
|
|||
"type": "builtin_path",
|
||||
"builtin_path": "{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio",
|
||||
"custom_path": "",
|
||||
"product_name": "",
|
||||
"published_product": {
|
||||
"product_name": "",
|
||||
"fallback": {
|
||||
"fallback_type": "builtin_path",
|
||||
"builtin_path": "ACES 1.2",
|
||||
"custom_path": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"file_rules": {
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ class CollectFramesFixDefModel(BaseSettingsModel):
|
|||
True,
|
||||
title="Show 'Rewrite latest version' toggle"
|
||||
)
|
||||
|
||||
|
||||
|
||||
class ContributionLayersModel(BaseSettingsModel):
|
||||
_layout = "compact"
|
||||
|
|
@ -268,13 +268,36 @@ class ExtractThumbnailModel(BaseSettingsModel):
|
|||
def _extract_oiio_transcoding_type():
|
||||
return [
|
||||
{"value": "colorspace", "label": "Use Colorspace"},
|
||||
{"value": "display", "label": "Use Display&View"}
|
||||
{"value": "display_view", "label": "Use Display&View"}
|
||||
]
|
||||
|
||||
|
||||
class OIIOToolArgumentsModel(BaseSettingsModel):
|
||||
additional_command_args: list[str] = SettingsField(
|
||||
default_factory=list, title="Arguments")
|
||||
default_factory=list,
|
||||
title="Arguments",
|
||||
description="Additional command line arguments for *oiiotool*."
|
||||
)
|
||||
|
||||
|
||||
class UseDisplayViewModel(BaseSettingsModel):
|
||||
_layout = "expanded"
|
||||
display: str = SettingsField(
|
||||
"",
|
||||
title="Target Display",
|
||||
description=(
|
||||
"Display of the target transform. If left empty, the"
|
||||
" source Display value will be used."
|
||||
)
|
||||
)
|
||||
view: str = SettingsField(
|
||||
"",
|
||||
title="Target View",
|
||||
description=(
|
||||
"View of the target transform. If left empty, the"
|
||||
" source View value will be used."
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class ExtractOIIOTranscodeOutputModel(BaseSettingsModel):
|
||||
|
|
@ -285,22 +308,57 @@ class ExtractOIIOTranscodeOutputModel(BaseSettingsModel):
|
|||
description="Output name (no space)",
|
||||
regex=r"[a-zA-Z0-9_]([a-zA-Z0-9_\.\-]*[a-zA-Z0-9_])?$",
|
||||
)
|
||||
extension: str = SettingsField("", title="Extension")
|
||||
extension: str = SettingsField(
|
||||
"",
|
||||
title="Extension",
|
||||
description=(
|
||||
"Target extension. If left empty, original"
|
||||
" extension is used."
|
||||
),
|
||||
)
|
||||
transcoding_type: str = SettingsField(
|
||||
"colorspace",
|
||||
title="Transcoding type",
|
||||
enum_resolver=_extract_oiio_transcoding_type
|
||||
enum_resolver=_extract_oiio_transcoding_type,
|
||||
conditionalEnum=True,
|
||||
description=(
|
||||
"Select the transcoding type for your output, choosing either "
|
||||
"*Colorspace* or *Display&View* transform."
|
||||
" Only one option can be applied per output definition."
|
||||
),
|
||||
)
|
||||
colorspace: str = SettingsField("", title="Colorspace")
|
||||
display: str = SettingsField("", title="Display")
|
||||
view: str = SettingsField("", title="View")
|
||||
colorspace: str = SettingsField(
|
||||
"",
|
||||
title="Target Colorspace",
|
||||
description=(
|
||||
"Choose the desired target colorspace, confirming its availability"
|
||||
" in the active OCIO config. If left empty, the"
|
||||
" source colorspace value will be used, resulting in no"
|
||||
" colorspace conversion."
|
||||
)
|
||||
)
|
||||
display_view: UseDisplayViewModel = SettingsField(
|
||||
title="Use Display&View",
|
||||
default_factory=UseDisplayViewModel
|
||||
)
|
||||
|
||||
oiiotool_args: OIIOToolArgumentsModel = SettingsField(
|
||||
default_factory=OIIOToolArgumentsModel,
|
||||
title="OIIOtool arguments")
|
||||
|
||||
tags: list[str] = SettingsField(default_factory=list, title="Tags")
|
||||
tags: list[str] = SettingsField(
|
||||
default_factory=list,
|
||||
title="Tags",
|
||||
description=(
|
||||
"Additional tags that will be added to the created representation."
|
||||
"\nAdd *review* tag to create review from the transcoded"
|
||||
" representation instead of the original."
|
||||
)
|
||||
)
|
||||
custom_tags: list[str] = SettingsField(
|
||||
default_factory=list, title="Custom Tags"
|
||||
default_factory=list,
|
||||
title="Custom Tags",
|
||||
description="Additional custom tags that will be added to the created representation."
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -328,7 +386,13 @@ class ExtractOIIOTranscodeProfileModel(BaseSettingsModel):
|
|||
)
|
||||
delete_original: bool = SettingsField(
|
||||
True,
|
||||
title="Delete Original Representation"
|
||||
title="Delete Original Representation",
|
||||
description=(
|
||||
"Choose to preserve or remove the original representation.\n"
|
||||
"Keep in mind that if the transcoded representation includes"
|
||||
" a `review` tag, it will take precedence over"
|
||||
" the original for creating reviews."
|
||||
),
|
||||
)
|
||||
outputs: list[ExtractOIIOTranscodeOutputModel] = SettingsField(
|
||||
default_factory=list,
|
||||
|
|
@ -371,7 +435,7 @@ class ExtractReviewFFmpegModel(BaseSettingsModel):
|
|||
def extract_review_filter_enum():
|
||||
return [
|
||||
{
|
||||
"value": "everytime",
|
||||
"value": "everytime", # codespell:ignore everytime
|
||||
"label": "Always"
|
||||
},
|
||||
{
|
||||
|
|
@ -393,7 +457,7 @@ class ExtractReviewFilterModel(BaseSettingsModel):
|
|||
default_factory=list, title="Custom Tags"
|
||||
)
|
||||
single_frame_filter: str = SettingsField(
|
||||
"everytime",
|
||||
"everytime", # codespell:ignore everytime
|
||||
description=(
|
||||
"Use output <b>always</b> / only if input <b>is 1 frame</b>"
|
||||
" image / only if has <b>2+ frames</b> or <b>is video</b>"
|
||||
|
|
@ -791,7 +855,7 @@ class IntegrateHeroVersionModel(BaseSettingsModel):
|
|||
|
||||
class CleanUpModel(BaseSettingsModel):
|
||||
_isGroup = True
|
||||
paterns: list[str] = SettingsField(
|
||||
paterns: list[str] = SettingsField( # codespell:ignore paterns
|
||||
default_factory=list,
|
||||
title="Patterns (regex)"
|
||||
)
|
||||
|
|
@ -1225,7 +1289,7 @@ DEFAULT_PUBLISH_VALUES = {
|
|||
"use_hardlinks": False
|
||||
},
|
||||
"CleanUp": {
|
||||
"paterns": [],
|
||||
"paterns": [], # codespell:ignore paterns
|
||||
"remove_temp_renders": False
|
||||
},
|
||||
"CleanUpFarm": {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,255 @@
|
|||
{
|
||||
"OTIO_SCHEMA": "Clip.2",
|
||||
"metadata": {
|
||||
"active": true,
|
||||
"applieswhole": 1,
|
||||
"asset": "sh020",
|
||||
"audio": true,
|
||||
"families": [
|
||||
"clip"
|
||||
],
|
||||
"family": "plate",
|
||||
"handleEnd": 8,
|
||||
"handleStart": 0,
|
||||
"heroTrack": true,
|
||||
"hierarchy": "shots/sq001",
|
||||
"hierarchyData": {
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"sequence": "sq001",
|
||||
"shot": "sh020",
|
||||
"track": "reference"
|
||||
},
|
||||
"hiero_source_type": "TrackItem",
|
||||
"id": "pyblish.avalon.instance",
|
||||
"label": "openpypeData",
|
||||
"note": "OpenPype data container",
|
||||
"parents": [
|
||||
{
|
||||
"entity_name": "shots",
|
||||
"entity_type": "folder"
|
||||
},
|
||||
{
|
||||
"entity_name": "sq001",
|
||||
"entity_type": "sequence"
|
||||
}
|
||||
],
|
||||
"publish": true,
|
||||
"reviewTrack": null,
|
||||
"sourceResolution": false,
|
||||
"subset": "plateP01",
|
||||
"variant": "Main",
|
||||
"workfileFrameStart": 1001
|
||||
},
|
||||
"name": "sh020",
|
||||
"source_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 23.976024627685547,
|
||||
"value": 51.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 23.976024627685547,
|
||||
"value": 0.0
|
||||
}
|
||||
},
|
||||
"effects": [],
|
||||
"markers": [
|
||||
{
|
||||
"OTIO_SCHEMA": "Marker.2",
|
||||
"metadata": {
|
||||
"active": true,
|
||||
"applieswhole": 1,
|
||||
"asset": "sh020",
|
||||
"audio": true,
|
||||
"families": [
|
||||
"clip"
|
||||
],
|
||||
"family": "plate",
|
||||
"handleEnd": 8,
|
||||
"handleStart": 0,
|
||||
"heroTrack": true,
|
||||
"hierarchy": "shots/sq001",
|
||||
"hierarchyData": {
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"sequence": "sq001",
|
||||
"shot": "sh020",
|
||||
"track": "reference"
|
||||
},
|
||||
"hiero_source_type": "TrackItem",
|
||||
"id": "pyblish.avalon.instance",
|
||||
"label": "openpypeData",
|
||||
"note": "OpenPype data container",
|
||||
"parents": [
|
||||
{
|
||||
"entity_name": "shots",
|
||||
"entity_type": "folder"
|
||||
},
|
||||
{
|
||||
"entity_name": "sq001",
|
||||
"entity_type": "sequence"
|
||||
}
|
||||
],
|
||||
"publish": true,
|
||||
"reviewTrack": null,
|
||||
"sourceResolution": false,
|
||||
"subset": "plateP01",
|
||||
"variant": "Main",
|
||||
"workfileFrameStart": 1001
|
||||
},
|
||||
"name": "openpypeData",
|
||||
"color": "RED",
|
||||
"marked_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 23.976024627685547,
|
||||
"value": 0.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 23.976024627685547,
|
||||
"value": 0.0
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Marker.2",
|
||||
"metadata": {
|
||||
"applieswhole": 1,
|
||||
"family": "task",
|
||||
"hiero_source_type": "TrackItem",
|
||||
"label": "comp",
|
||||
"note": "Compositing",
|
||||
"type": "Compositing"
|
||||
},
|
||||
"name": "comp",
|
||||
"color": "RED",
|
||||
"marked_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 23.976024627685547,
|
||||
"value": 0.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 23.976024627685547,
|
||||
"value": 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"enabled": true,
|
||||
"media_references": {
|
||||
"DEFAULT_MEDIA": {
|
||||
"OTIO_SCHEMA": "ImageSequenceReference.1",
|
||||
"metadata": {
|
||||
"clip.properties.blendfunc": "0",
|
||||
"clip.properties.colourspacename": "default",
|
||||
"clip.properties.domainroot": "",
|
||||
"clip.properties.enabled": "1",
|
||||
"clip.properties.expanded": "1",
|
||||
"clip.properties.opacity": "1",
|
||||
"clip.properties.valuesource": "",
|
||||
"foundry.source.audio": "",
|
||||
"foundry.source.bitmapsize": "0",
|
||||
"foundry.source.bitsperchannel": "0",
|
||||
"foundry.source.channelformat": "integer",
|
||||
"foundry.source.colourtransform": "ACES - ACES2065-1",
|
||||
"foundry.source.duration": "59",
|
||||
"foundry.source.filename": "MER_sq001_sh020_P01.%04d.exr 997-1055",
|
||||
"foundry.source.filesize": "",
|
||||
"foundry.source.fragments": "59",
|
||||
"foundry.source.framerate": "23.98",
|
||||
"foundry.source.fullpath": "",
|
||||
"foundry.source.height": "1080",
|
||||
"foundry.source.layers": "colour",
|
||||
"foundry.source.path": "C:/projects/AY01_VFX_demo/resources/plates/MER_sq001_sh020_P01/MER_sq001_sh020_P01.%04d.exr 997-1055",
|
||||
"foundry.source.pixelAspect": "1",
|
||||
"foundry.source.pixelAspectRatio": "",
|
||||
"foundry.source.pixelformat": "RGBA (Float16) Open Color IO space: 11",
|
||||
"foundry.source.reelID": "",
|
||||
"foundry.source.resolution": "",
|
||||
"foundry.source.samplerate": "Invalid",
|
||||
"foundry.source.shortfilename": "MER_sq001_sh020_P01.%04d.exr 997-1055",
|
||||
"foundry.source.shot": "",
|
||||
"foundry.source.shotDate": "",
|
||||
"foundry.source.startTC": "",
|
||||
"foundry.source.starttime": "997",
|
||||
"foundry.source.timecode": "172800",
|
||||
"foundry.source.umid": "1bf7437a-b446-440c-07c5-7cae7acf4f5e",
|
||||
"foundry.source.umidOriginator": "foundry.source.umid",
|
||||
"foundry.source.width": "1920",
|
||||
"foundry.timeline.autodiskcachemode": "Manual",
|
||||
"foundry.timeline.colorSpace": "ACES - ACES2065-1",
|
||||
"foundry.timeline.duration": "59",
|
||||
"foundry.timeline.framerate": "23.98",
|
||||
"foundry.timeline.outputformat": "",
|
||||
"foundry.timeline.poster": "0",
|
||||
"foundry.timeline.posterLayer": "colour",
|
||||
"foundry.timeline.readParams": "AAAAAQAAAAAAAAAFAAAACmNvbG9yc3BhY2UAAAAFaW50MzIAAAAMAAAAC2VkZ2VfcGl4ZWxzAAAABWludDMyAAAAAAAAABFpZ25vcmVfcGFydF9uYW1lcwAAAARib29sAAAAAAhub3ByZWZpeAAAAARib29sAAAAAB5vZmZzZXRfbmVnYXRpdmVfZGlzcGxheV93aW5kb3cAAAAEYm9vbAE=",
|
||||
"foundry.timeline.samplerate": "Invalid",
|
||||
"isSequence": true,
|
||||
"media.exr.channels": "B:{1 0 1 1},G:{1 0 1 1},R:{1 0 1 1}",
|
||||
"media.exr.compression": "8",
|
||||
"media.exr.compressionName": "DWAA",
|
||||
"media.exr.dataWindow": "0,0,1919,1079",
|
||||
"media.exr.displayWindow": "0,0,1919,1079",
|
||||
"media.exr.dwaCompressionLevel": "90",
|
||||
"media.exr.lineOrder": "0",
|
||||
"media.exr.pixelAspectRatio": "1",
|
||||
"media.exr.screenWindowCenter": "0,0",
|
||||
"media.exr.screenWindowWidth": "1",
|
||||
"media.exr.type": "scanlineimage",
|
||||
"media.exr.version": "1",
|
||||
"media.input.bitsperchannel": "16-bit half float",
|
||||
"media.input.ctime": "2022-04-21 11:56:03",
|
||||
"media.input.filename": "C:/projects/AY01_VFX_demo/resources/plates/MER_sq001_sh020_P01/MER_sq001_sh020_P01.0997.exr",
|
||||
"media.input.filereader": "exr",
|
||||
"media.input.filesize": "1235182",
|
||||
"media.input.frame": "1",
|
||||
"media.input.frame_rate": "23.976",
|
||||
"media.input.height": "1080",
|
||||
"media.input.mtime": "2022-03-06 10:14:41",
|
||||
"media.input.timecode": "02:00:00:00",
|
||||
"media.input.width": "1920",
|
||||
"media.nuke.full_layer_names": "0",
|
||||
"media.nuke.node_hash": "ffffffffffffffff",
|
||||
"media.nuke.version": "12.2v3",
|
||||
"openpype.source.colourtransform": "ACES - ACES2065-1",
|
||||
"openpype.source.height": 1080,
|
||||
"openpype.source.pixelAspect": 1.0,
|
||||
"openpype.source.width": 1920,
|
||||
"padding": 4
|
||||
},
|
||||
"name": "",
|
||||
"available_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 23.976,
|
||||
"value": 59.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 23.976,
|
||||
"value": 997.0
|
||||
}
|
||||
},
|
||||
"available_image_bounds": null,
|
||||
"target_url_base": "C:/projects/AY01_VFX_demo/resources/plates/MER_sq001_sh020_P01\\",
|
||||
"name_prefix": "MER_sq001_sh020_P01.",
|
||||
"name_suffix": ".exr",
|
||||
"start_frame": 997,
|
||||
"frame_step": 1,
|
||||
"rate": 23.976,
|
||||
"frame_zero_padding": 4,
|
||||
"missing_frame_policy": "error"
|
||||
}
|
||||
},
|
||||
"active_media_reference_key": "DEFAULT_MEDIA"
|
||||
}
|
||||
|
|
@ -0,0 +1,363 @@
|
|||
{
|
||||
"OTIO_SCHEMA": "Clip.2",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {}
|
||||
},
|
||||
"name": "output.[1000-1100].exr",
|
||||
"source_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 74.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 91046.625
|
||||
}
|
||||
},
|
||||
"effects": [
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Transform",
|
||||
"Enabled": true,
|
||||
"Name": "Transform",
|
||||
"Parameters": [],
|
||||
"Type": 2
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Cropping",
|
||||
"Enabled": true,
|
||||
"Name": "Cropping",
|
||||
"Parameters": [],
|
||||
"Type": 3
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Dynamic Zoom",
|
||||
"Enabled": false,
|
||||
"Name": "Dynamic Zoom",
|
||||
"Parameters": [
|
||||
{
|
||||
"Default Parameter Value": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Key Frames": {
|
||||
"-6": {
|
||||
"Value": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Variant Type": "POINTF"
|
||||
},
|
||||
"994": {
|
||||
"Value": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Variant Type": "POINTF"
|
||||
}
|
||||
},
|
||||
"Parameter ID": "dynamicZoomCenter",
|
||||
"Parameter Value": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Variant Type": "POINTF"
|
||||
},
|
||||
{
|
||||
"Default Parameter Value": 1.0,
|
||||
"Key Frames": {
|
||||
"-6": {
|
||||
"Value": 0.8,
|
||||
"Variant Type": "Double"
|
||||
},
|
||||
"994": {
|
||||
"Value": 1.0,
|
||||
"Variant Type": "Double"
|
||||
}
|
||||
},
|
||||
"Parameter ID": "dynamicZoomScale",
|
||||
"Parameter Value": 1.0,
|
||||
"Variant Type": "Double",
|
||||
"maxValue": 100.0,
|
||||
"minValue": 0.01
|
||||
}
|
||||
],
|
||||
"Type": 59
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Composite",
|
||||
"Enabled": true,
|
||||
"Name": "Composite",
|
||||
"Parameters": [],
|
||||
"Type": 1
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Lens Correction",
|
||||
"Enabled": true,
|
||||
"Name": "Lens Correction",
|
||||
"Parameters": [],
|
||||
"Type": 43
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Retime and Scaling",
|
||||
"Enabled": true,
|
||||
"Name": "Retime and Scaling",
|
||||
"Parameters": [],
|
||||
"Type": 22
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Video Faders",
|
||||
"Enabled": true,
|
||||
"Name": "Video Faders",
|
||||
"Parameters": [],
|
||||
"Type": 36
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
}
|
||||
],
|
||||
"markers": [
|
||||
{
|
||||
"OTIO_SCHEMA": "Marker.2",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Keywords": [],
|
||||
"Note": "{\"resolve_sub_products\": {\"io.ayon.creators.resolve.shot\": {\"id\": \"pyblish.avalon.instance\", \"productType\": \"shot\", \"productName\": \"shotMain\", \"active\": true, \"creator_identifier\": \"io.ayon.creators.resolve.shot\", \"variant\": \"Main\", \"folderPath\": \"/shots/sq01/Video_1sq01sh010\", \"task\": \"Generic\", \"clip_variant\": \"<track_name>\", \"clip_index\": \"f2918dd7-a30b-4b7d-8ac1-7d5f400058bf\", \"clip_source_resolution\": {\"width\": \"956\", \"height\": \"720\", \"pixelAspect\": 1.0}, \"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"sq01\", \"track\": \"{_track_}\", \"shot\": \"sh###\", \"hierarchy\": \"shots/sq01\", \"sourceResolution\": false, \"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"parents\": [{\"folder_type\": \"folder\", \"entity_name\": \"shots\"}, {\"folder_type\": \"sequence\", \"entity_name\": \"sq01\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"sq01\", \"track\": \"Video_1\", \"shot\": \"sh010\"}, \"heroTrack\": true, \"uuid\": \"ade94deb-f104-47dc-b8e9-04943f900914\", \"reviewTrack\": null, \"label\": \"/shots/sq01/Video_1sq01sh010 shot\", \"has_promised_context\": true, \"newHierarchyIntegration\": true, \"newAssetPublishing\": true, \"instance_id\": \"5109899f-d744-4ed3-8547-8585ef9b703b\", \"creator_attributes\": {\"folderPath\": \"/shots/sq01/Video_1sq01sh010\", \"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"frameStart\": 1001, \"frameEnd\": 1075, \"clipIn\": 655, \"clipOut\": 729, \"clipDuration\": 74, \"sourceIn\": 6, \"sourceOut\": 80, \"fps\": \"from_selection\"}, \"publish_attributes\": {\"CollectSlackFamilies\": {\"additional_message\": \"\"}}}, \"io.ayon.creators.resolve.plate\": {\"id\": \"pyblish.avalon.instance\", \"productType\": \"plate\", \"productName\": \"plateVideo_1\", \"active\": true, \"creator_identifier\": \"io.ayon.creators.resolve.plate\", \"variant\": \"Video_1\", \"folderPath\": \"/shots/sq01/Video_1sq01sh010\", \"task\": \"Generic\", \"clip_variant\": \"<track_name>\", \"clip_index\": \"f2918dd7-a30b-4b7d-8ac1-7d5f400058bf\", \"clip_source_resolution\": {\"width\": \"956\", \"height\": \"720\", \"pixelAspect\": 1.0}, \"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"sq01\", \"track\": \"{_track_}\", \"shot\": \"sh###\", \"hierarchy\": \"shots/sq01\", \"sourceResolution\": false, \"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"parents\": [{\"folder_type\": \"folder\", \"entity_name\": \"shots\"}, {\"folder_type\": \"sequence\", \"entity_name\": \"sq01\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"sq01\", \"track\": \"Video_1\", \"shot\": \"sh010\"}, \"heroTrack\": true, \"uuid\": \"ade94deb-f104-47dc-b8e9-04943f900914\", \"reviewTrack\": null, \"parent_instance_id\": \"5109899f-d744-4ed3-8547-8585ef9b703b\", \"label\": \"/shots/sq01/Video_1sq01sh010 plate\", \"has_promised_context\": true, \"newHierarchyIntegration\": true, \"newAssetPublishing\": true, \"instance_id\": \"85c729e9-0503-4c3a-8d7f-be0920f047d8\", \"creator_attributes\": {\"parentInstance\": \"/shots/sq01/Video_1sq01sh010 shot\", \"vSyncOn\": false, \"vSyncTrack\": \"Video 1\"}, \"publish_attributes\": {\"CollectSlackFamilies\": {\"additional_message\": \"\"}}}}, \"clip_index\": \"f2918dd7-a30b-4b7d-8ac1-7d5f400058bf\", \"publish\": true}"
|
||||
},
|
||||
"clip_index": "f2918dd7-a30b-4b7d-8ac1-7d5f400058bf",
|
||||
"publish": true,
|
||||
"resolve_sub_products": {
|
||||
"io.ayon.creators.resolve.plate": {
|
||||
"active": true,
|
||||
"clip_index": "f2918dd7-a30b-4b7d-8ac1-7d5f400058bf",
|
||||
"clip_source_resolution": {
|
||||
"height": "720",
|
||||
"pixelAspect": 1.0,
|
||||
"width": "956"
|
||||
},
|
||||
"clip_variant": "<track_name>",
|
||||
"creator_attributes": {
|
||||
"parentInstance": "/shots/sq01/Video_1sq01sh010 shot",
|
||||
"vSyncOn": false,
|
||||
"vSyncTrack": "Video 1"
|
||||
},
|
||||
"creator_identifier": "io.ayon.creators.resolve.plate",
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"folderPath": "/shots/sq01/Video_1sq01sh010",
|
||||
"handleEnd": 10,
|
||||
"handleStart": 10,
|
||||
"has_promised_context": true,
|
||||
"heroTrack": true,
|
||||
"hierarchy": "shots/sq01",
|
||||
"hierarchyData": {
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"sequence": "sq01",
|
||||
"shot": "sh010",
|
||||
"track": "Video_1"
|
||||
},
|
||||
"id": "pyblish.avalon.instance",
|
||||
"instance_id": "85c729e9-0503-4c3a-8d7f-be0920f047d8",
|
||||
"label": "/shots/sq01/Video_1sq01sh010 plate",
|
||||
"newAssetPublishing": true,
|
||||
"newHierarchyIntegration": true,
|
||||
"parent_instance_id": "5109899f-d744-4ed3-8547-8585ef9b703b",
|
||||
"parents": [
|
||||
{
|
||||
"entity_name": "shots",
|
||||
"folder_type": "folder"
|
||||
},
|
||||
{
|
||||
"entity_name": "sq01",
|
||||
"folder_type": "sequence"
|
||||
}
|
||||
],
|
||||
"productName": "plateVideo_1",
|
||||
"productType": "plate",
|
||||
"publish_attributes": {
|
||||
"CollectSlackFamilies": {
|
||||
"additional_message": ""
|
||||
}
|
||||
},
|
||||
"reviewTrack": null,
|
||||
"sequence": "sq01",
|
||||
"shot": "sh###",
|
||||
"sourceResolution": false,
|
||||
"task": "Generic",
|
||||
"track": "{_track_}",
|
||||
"uuid": "ade94deb-f104-47dc-b8e9-04943f900914",
|
||||
"variant": "Video_1",
|
||||
"workfileFrameStart": 1001
|
||||
},
|
||||
"io.ayon.creators.resolve.shot": {
|
||||
"active": true,
|
||||
"clip_index": "f2918dd7-a30b-4b7d-8ac1-7d5f400058bf",
|
||||
"clip_source_resolution": {
|
||||
"height": "720",
|
||||
"pixelAspect": 1.0,
|
||||
"width": "956"
|
||||
},
|
||||
"clip_variant": "<track_name>",
|
||||
"creator_attributes": {
|
||||
"clipDuration": 74,
|
||||
"clipIn": 655,
|
||||
"clipOut": 729,
|
||||
"folderPath": "/shots/sq01/Video_1sq01sh010",
|
||||
"fps": "from_selection",
|
||||
"frameEnd": 1075,
|
||||
"frameStart": 1001,
|
||||
"handleEnd": 10,
|
||||
"handleStart": 10,
|
||||
"sourceIn": 6,
|
||||
"sourceOut": 80,
|
||||
"workfileFrameStart": 1001
|
||||
},
|
||||
"creator_identifier": "io.ayon.creators.resolve.shot",
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"folderPath": "/shots/sq01/Video_1sq01sh010",
|
||||
"handleEnd": 10,
|
||||
"handleStart": 10,
|
||||
"has_promised_context": true,
|
||||
"heroTrack": true,
|
||||
"hierarchy": "shots/sq01",
|
||||
"hierarchyData": {
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"sequence": "sq01",
|
||||
"shot": "sh010",
|
||||
"track": "Video_1"
|
||||
},
|
||||
"id": "pyblish.avalon.instance",
|
||||
"instance_id": "5109899f-d744-4ed3-8547-8585ef9b703b",
|
||||
"label": "/shots/sq01/Video_1sq01sh010 shot",
|
||||
"newAssetPublishing": true,
|
||||
"newHierarchyIntegration": true,
|
||||
"parents": [
|
||||
{
|
||||
"entity_name": "shots",
|
||||
"folder_type": "folder"
|
||||
},
|
||||
{
|
||||
"entity_name": "sq01",
|
||||
"folder_type": "sequence"
|
||||
}
|
||||
],
|
||||
"productName": "shotMain",
|
||||
"productType": "shot",
|
||||
"publish_attributes": {
|
||||
"CollectSlackFamilies": {
|
||||
"additional_message": ""
|
||||
}
|
||||
},
|
||||
"reviewTrack": null,
|
||||
"sequence": "sq01",
|
||||
"shot": "sh###",
|
||||
"sourceResolution": false,
|
||||
"task": "Generic",
|
||||
"track": "{_track_}",
|
||||
"uuid": "ade94deb-f104-47dc-b8e9-04943f900914",
|
||||
"variant": "Main",
|
||||
"workfileFrameStart": 1001
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "AyonData",
|
||||
"color": "GREEN",
|
||||
"marked_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 1.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 91083.625
|
||||
}
|
||||
},
|
||||
"comment": ""
|
||||
}
|
||||
],
|
||||
"enabled": true,
|
||||
"media_references": {
|
||||
"DEFAULT_MEDIA": {
|
||||
"OTIO_SCHEMA": "ImageSequenceReference.1",
|
||||
"metadata": {},
|
||||
"name": "output.[1000-1100].exr",
|
||||
"available_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 101.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 87399.0
|
||||
}
|
||||
},
|
||||
"available_image_bounds": null,
|
||||
"target_url_base": "C:\\Users\\robin\\OneDrive\\Bureau\\dev_ayon\\samples\\exr_embedded_tc",
|
||||
"name_prefix": "output.",
|
||||
"name_suffix": ".exr",
|
||||
"start_frame": 1000,
|
||||
"frame_step": 1,
|
||||
"rate": 24.0,
|
||||
"frame_zero_padding": 4,
|
||||
"missing_frame_policy": "error"
|
||||
}
|
||||
},
|
||||
"active_media_reference_key": "DEFAULT_MEDIA"
|
||||
}
|
||||
|
|
@ -0,0 +1,363 @@
|
|||
{
|
||||
"OTIO_SCHEMA": "Clip.2",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {}
|
||||
},
|
||||
"name": "output.[1000-1100].exr",
|
||||
"source_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 101.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 87399.0
|
||||
}
|
||||
},
|
||||
"effects": [
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Transform",
|
||||
"Enabled": true,
|
||||
"Name": "Transform",
|
||||
"Parameters": [],
|
||||
"Type": 2
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Cropping",
|
||||
"Enabled": true,
|
||||
"Name": "Cropping",
|
||||
"Parameters": [],
|
||||
"Type": 3
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Dynamic Zoom",
|
||||
"Enabled": false,
|
||||
"Name": "Dynamic Zoom",
|
||||
"Parameters": [
|
||||
{
|
||||
"Default Parameter Value": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Key Frames": {
|
||||
"0": {
|
||||
"Value": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Variant Type": "POINTF"
|
||||
},
|
||||
"1000": {
|
||||
"Value": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Variant Type": "POINTF"
|
||||
}
|
||||
},
|
||||
"Parameter ID": "dynamicZoomCenter",
|
||||
"Parameter Value": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Variant Type": "POINTF"
|
||||
},
|
||||
{
|
||||
"Default Parameter Value": 1.0,
|
||||
"Key Frames": {
|
||||
"0": {
|
||||
"Value": 0.8,
|
||||
"Variant Type": "Double"
|
||||
},
|
||||
"1000": {
|
||||
"Value": 1.0,
|
||||
"Variant Type": "Double"
|
||||
}
|
||||
},
|
||||
"Parameter ID": "dynamicZoomScale",
|
||||
"Parameter Value": 1.0,
|
||||
"Variant Type": "Double",
|
||||
"maxValue": 100.0,
|
||||
"minValue": 0.01
|
||||
}
|
||||
],
|
||||
"Type": 59
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Composite",
|
||||
"Enabled": true,
|
||||
"Name": "Composite",
|
||||
"Parameters": [],
|
||||
"Type": 1
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Lens Correction",
|
||||
"Enabled": true,
|
||||
"Name": "Lens Correction",
|
||||
"Parameters": [],
|
||||
"Type": 43
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Retime and Scaling",
|
||||
"Enabled": true,
|
||||
"Name": "Retime and Scaling",
|
||||
"Parameters": [],
|
||||
"Type": 22
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Video Faders",
|
||||
"Enabled": true,
|
||||
"Name": "Video Faders",
|
||||
"Parameters": [],
|
||||
"Type": 36
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
}
|
||||
],
|
||||
"markers": [
|
||||
{
|
||||
"OTIO_SCHEMA": "Marker.2",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Keywords": [],
|
||||
"Note": "{\"resolve_sub_products\": {\"io.ayon.creators.resolve.shot\": {\"id\": \"pyblish.avalon.instance\", \"productType\": \"shot\", \"productName\": \"shotMain\", \"active\": true, \"creator_identifier\": \"io.ayon.creators.resolve.shot\", \"variant\": \"Main\", \"folderPath\": \"/shots/seq_img_tc_handles_out/sh010\", \"task\": \"Generic\", \"clip_variant\": \"<track_name>\", \"clip_index\": \"adca7e5b-b53c-48ab-8469-abe4db3c276a\", \"clip_source_resolution\": {\"width\": \"956\", \"height\": \"720\", \"pixelAspect\": 1.0}, \"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"seq_img_tc_handles_out\", \"track\": \"{_track_}\", \"shot\": \"sh###\", \"hierarchy\": \"shots/seq_img_tc_handles_out\", \"sourceResolution\": true, \"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"parents\": [{\"folder_type\": \"folder\", \"entity_name\": \"shots\"}, {\"folder_type\": \"sequence\", \"entity_name\": \"seq_img_tc_handles_out\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"seq_img_tc_handles_out\", \"track\": \"Video1\", \"shot\": \"sh010\"}, \"heroTrack\": true, \"uuid\": \"fca94ed7-1e74-4ddc-8d56-05696e8c472a\", \"reviewTrack\": \"Video1\", \"label\": \"/shots/seq_img_tc_handles_out/sh010 shot\", \"has_promised_context\": true, \"newHierarchyIntegration\": true, \"newAssetPublishing\": true, \"instance_id\": \"6c2baba3-183c-41f0-b9a9-596d315fd162\", \"creator_attributes\": {\"folderPath\": \"/shots/seq_img_tc_handles_out/sh010\", \"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"frameStart\": 1001, \"frameEnd\": 1102, \"clipIn\": 86524, \"clipOut\": 86625, \"clipDuration\": 101, \"sourceIn\": 0, \"sourceOut\": 101, \"fps\": \"from_selection\"}, \"publish_attributes\": {\"CollectSlackFamilies\": {\"additional_message\": \"\"}}}, \"io.ayon.creators.resolve.plate\": {\"id\": \"pyblish.avalon.instance\", \"productType\": \"plate\", \"productName\": \"plateVideo1\", \"active\": true, \"creator_identifier\": \"io.ayon.creators.resolve.plate\", \"variant\": \"Video1\", \"folderPath\": \"/shots/seq_img_tc_handles_out/sh010\", \"task\": \"Generic\", \"clip_variant\": \"<track_name>\", \"clip_index\": \"adca7e5b-b53c-48ab-8469-abe4db3c276a\", \"clip_source_resolution\": {\"width\": \"956\", \"height\": \"720\", \"pixelAspect\": 1.0}, \"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"seq_img_tc_handles_out\", \"track\": \"{_track_}\", \"shot\": \"sh###\", \"hierarchy\": \"shots/seq_img_tc_handles_out\", \"sourceResolution\": true, \"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"parents\": [{\"folder_type\": \"folder\", \"entity_name\": \"shots\"}, {\"folder_type\": \"sequence\", \"entity_name\": \"seq_img_tc_handles_out\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"seq_img_tc_handles_out\", \"track\": \"Video1\", \"shot\": \"sh010\"}, \"heroTrack\": true, \"uuid\": \"fca94ed7-1e74-4ddc-8d56-05696e8c472a\", \"reviewTrack\": \"Video1\", \"parent_instance_id\": \"6c2baba3-183c-41f0-b9a9-596d315fd162\", \"label\": \"/shots/seq_img_tc_handles_out/sh010 plate\", \"has_promised_context\": true, \"newHierarchyIntegration\": true, \"newAssetPublishing\": true, \"instance_id\": \"8b1f1e6f-699a-4481-b9be-92d819bc4096\", \"creator_attributes\": {\"parentInstance\": \"/shots/seq_img_tc_handles_out/sh010 shot\", \"vSyncOn\": true, \"vSyncTrack\": \"Video1\"}, \"publish_attributes\": {\"CollectSlackFamilies\": {\"additional_message\": \"\"}}}}, \"clip_index\": \"adca7e5b-b53c-48ab-8469-abe4db3c276a\", \"publish\": true}"
|
||||
},
|
||||
"clip_index": "adca7e5b-b53c-48ab-8469-abe4db3c276a",
|
||||
"publish": true,
|
||||
"resolve_sub_products": {
|
||||
"io.ayon.creators.resolve.plate": {
|
||||
"active": true,
|
||||
"clip_index": "adca7e5b-b53c-48ab-8469-abe4db3c276a",
|
||||
"clip_source_resolution": {
|
||||
"height": "720",
|
||||
"pixelAspect": 1.0,
|
||||
"width": "956"
|
||||
},
|
||||
"clip_variant": "<track_name>",
|
||||
"creator_attributes": {
|
||||
"parentInstance": "/shots/seq_img_tc_handles_out/sh010 shot",
|
||||
"vSyncOn": true,
|
||||
"vSyncTrack": "Video1"
|
||||
},
|
||||
"creator_identifier": "io.ayon.creators.resolve.plate",
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"folderPath": "/shots/seq_img_tc_handles_out/sh010",
|
||||
"handleEnd": 10,
|
||||
"handleStart": 10,
|
||||
"has_promised_context": true,
|
||||
"heroTrack": true,
|
||||
"hierarchy": "shots/seq_img_tc_handles_out",
|
||||
"hierarchyData": {
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"sequence": "seq_img_tc_handles_out",
|
||||
"shot": "sh010",
|
||||
"track": "Video1"
|
||||
},
|
||||
"id": "pyblish.avalon.instance",
|
||||
"instance_id": "8b1f1e6f-699a-4481-b9be-92d819bc4096",
|
||||
"label": "/shots/seq_img_tc_handles_out/sh010 plate",
|
||||
"newAssetPublishing": true,
|
||||
"newHierarchyIntegration": true,
|
||||
"parent_instance_id": "6c2baba3-183c-41f0-b9a9-596d315fd162",
|
||||
"parents": [
|
||||
{
|
||||
"entity_name": "shots",
|
||||
"folder_type": "folder"
|
||||
},
|
||||
{
|
||||
"entity_name": "seq_img_tc_handles_out",
|
||||
"folder_type": "sequence"
|
||||
}
|
||||
],
|
||||
"productName": "plateVideo1",
|
||||
"productType": "plate",
|
||||
"publish_attributes": {
|
||||
"CollectSlackFamilies": {
|
||||
"additional_message": ""
|
||||
}
|
||||
},
|
||||
"reviewTrack": "Video1",
|
||||
"sequence": "seq_img_tc_handles_out",
|
||||
"shot": "sh###",
|
||||
"sourceResolution": true,
|
||||
"task": "Generic",
|
||||
"track": "{_track_}",
|
||||
"uuid": "fca94ed7-1e74-4ddc-8d56-05696e8c472a",
|
||||
"variant": "Video1",
|
||||
"workfileFrameStart": 1001
|
||||
},
|
||||
"io.ayon.creators.resolve.shot": {
|
||||
"active": true,
|
||||
"clip_index": "adca7e5b-b53c-48ab-8469-abe4db3c276a",
|
||||
"clip_source_resolution": {
|
||||
"height": "720",
|
||||
"pixelAspect": 1.0,
|
||||
"width": "956"
|
||||
},
|
||||
"clip_variant": "<track_name>",
|
||||
"creator_attributes": {
|
||||
"clipDuration": 101,
|
||||
"clipIn": 86524,
|
||||
"clipOut": 86625,
|
||||
"folderPath": "/shots/seq_img_tc_handles_out/sh010",
|
||||
"fps": "from_selection",
|
||||
"frameEnd": 1102,
|
||||
"frameStart": 1001,
|
||||
"handleEnd": 10,
|
||||
"handleStart": 10,
|
||||
"sourceIn": 0,
|
||||
"sourceOut": 101,
|
||||
"workfileFrameStart": 1001
|
||||
},
|
||||
"creator_identifier": "io.ayon.creators.resolve.shot",
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"folderPath": "/shots/seq_img_tc_handles_out/sh010",
|
||||
"handleEnd": 10,
|
||||
"handleStart": 10,
|
||||
"has_promised_context": true,
|
||||
"heroTrack": true,
|
||||
"hierarchy": "shots/seq_img_tc_handles_out",
|
||||
"hierarchyData": {
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"sequence": "seq_img_tc_handles_out",
|
||||
"shot": "sh010",
|
||||
"track": "Video1"
|
||||
},
|
||||
"id": "pyblish.avalon.instance",
|
||||
"instance_id": "6c2baba3-183c-41f0-b9a9-596d315fd162",
|
||||
"label": "/shots/seq_img_tc_handles_out/sh010 shot",
|
||||
"newAssetPublishing": true,
|
||||
"newHierarchyIntegration": true,
|
||||
"parents": [
|
||||
{
|
||||
"entity_name": "shots",
|
||||
"folder_type": "folder"
|
||||
},
|
||||
{
|
||||
"entity_name": "seq_img_tc_handles_out",
|
||||
"folder_type": "sequence"
|
||||
}
|
||||
],
|
||||
"productName": "shotMain",
|
||||
"productType": "shot",
|
||||
"publish_attributes": {
|
||||
"CollectSlackFamilies": {
|
||||
"additional_message": ""
|
||||
}
|
||||
},
|
||||
"reviewTrack": "Video1",
|
||||
"sequence": "seq_img_tc_handles_out",
|
||||
"shot": "sh###",
|
||||
"sourceResolution": true,
|
||||
"task": "Generic",
|
||||
"track": "{_track_}",
|
||||
"uuid": "fca94ed7-1e74-4ddc-8d56-05696e8c472a",
|
||||
"variant": "Main",
|
||||
"workfileFrameStart": 1001
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "AyonData",
|
||||
"color": "GREEN",
|
||||
"marked_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 1.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 87449.0
|
||||
}
|
||||
},
|
||||
"comment": ""
|
||||
}
|
||||
],
|
||||
"enabled": true,
|
||||
"media_references": {
|
||||
"DEFAULT_MEDIA": {
|
||||
"OTIO_SCHEMA": "ImageSequenceReference.1",
|
||||
"metadata": {},
|
||||
"name": "output.[1000-1100].exr",
|
||||
"available_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 101.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 87399.0
|
||||
}
|
||||
},
|
||||
"available_image_bounds": null,
|
||||
"target_url_base": "C:\\exr_embedded_tc",
|
||||
"name_prefix": "output.",
|
||||
"name_suffix": ".exr",
|
||||
"start_frame": 1000,
|
||||
"frame_step": 1,
|
||||
"rate": 24.0,
|
||||
"frame_zero_padding": 4,
|
||||
"missing_frame_policy": "error"
|
||||
}
|
||||
},
|
||||
"active_media_reference_key": "DEFAULT_MEDIA"
|
||||
}
|
||||
|
|
@ -0,0 +1,363 @@
|
|||
{
|
||||
"OTIO_SCHEMA": "Clip.2",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {}
|
||||
},
|
||||
"name": "output.[1000-1100].tif",
|
||||
"source_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 101.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 0.0
|
||||
}
|
||||
},
|
||||
"effects": [
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Transform",
|
||||
"Enabled": true,
|
||||
"Name": "Transform",
|
||||
"Parameters": [],
|
||||
"Type": 2
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Cropping",
|
||||
"Enabled": true,
|
||||
"Name": "Cropping",
|
||||
"Parameters": [],
|
||||
"Type": 3
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Dynamic Zoom",
|
||||
"Enabled": false,
|
||||
"Name": "Dynamic Zoom",
|
||||
"Parameters": [
|
||||
{
|
||||
"Default Parameter Value": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Key Frames": {
|
||||
"0": {
|
||||
"Value": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Variant Type": "POINTF"
|
||||
},
|
||||
"1000": {
|
||||
"Value": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Variant Type": "POINTF"
|
||||
}
|
||||
},
|
||||
"Parameter ID": "dynamicZoomCenter",
|
||||
"Parameter Value": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Variant Type": "POINTF"
|
||||
},
|
||||
{
|
||||
"Default Parameter Value": 1.0,
|
||||
"Key Frames": {
|
||||
"0": {
|
||||
"Value": 0.8,
|
||||
"Variant Type": "Double"
|
||||
},
|
||||
"1000": {
|
||||
"Value": 1.0,
|
||||
"Variant Type": "Double"
|
||||
}
|
||||
},
|
||||
"Parameter ID": "dynamicZoomScale",
|
||||
"Parameter Value": 1.0,
|
||||
"Variant Type": "Double",
|
||||
"maxValue": 100.0,
|
||||
"minValue": 0.01
|
||||
}
|
||||
],
|
||||
"Type": 59
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Composite",
|
||||
"Enabled": true,
|
||||
"Name": "Composite",
|
||||
"Parameters": [],
|
||||
"Type": 1
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Lens Correction",
|
||||
"Enabled": true,
|
||||
"Name": "Lens Correction",
|
||||
"Parameters": [],
|
||||
"Type": 43
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Retime and Scaling",
|
||||
"Enabled": true,
|
||||
"Name": "Retime and Scaling",
|
||||
"Parameters": [],
|
||||
"Type": 22
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Video Faders",
|
||||
"Enabled": true,
|
||||
"Name": "Video Faders",
|
||||
"Parameters": [],
|
||||
"Type": 36
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
}
|
||||
],
|
||||
"markers": [
|
||||
{
|
||||
"OTIO_SCHEMA": "Marker.2",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Keywords": [],
|
||||
"Note": "{\"resolve_sub_products\": {\"io.ayon.creators.resolve.shot\": {\"id\": \"pyblish.avalon.instance\", \"productType\": \"shot\", \"productName\": \"shotMain\", \"active\": true, \"creator_identifier\": \"io.ayon.creators.resolve.shot\", \"variant\": \"Main\", \"folderPath\": \"/shots/sq01/Video_1sq01sh010\", \"task\": \"Generic\", \"clip_variant\": \"<track_name>\", \"clip_index\": \"cfea9be4-3ecd-4253-a0a9-1a2bc9a4a184\", \"clip_source_resolution\": {\"width\": \"1920\", \"height\": \"1080\", \"pixelAspect\": 1.0}, \"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"sq01\", \"track\": \"{_track_}\", \"shot\": \"sh###\", \"hierarchy\": \"shots/sq01\", \"sourceResolution\": false, \"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"parents\": [{\"folder_type\": \"folder\", \"entity_name\": \"shots\"}, {\"folder_type\": \"sequence\", \"entity_name\": \"sq01\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"sq01\", \"track\": \"Video_1\", \"shot\": \"sh010\"}, \"heroTrack\": true, \"uuid\": \"647b2ba6-6fca-4219-b163-cd321df9652f\", \"reviewTrack\": null, \"label\": \"/shots/sq01/Video_1sq01sh010 shot\", \"has_promised_context\": true, \"newHierarchyIntegration\": true, \"newAssetPublishing\": true, \"instance_id\": \"2ab8f149-e32c-40f5-a6cb-ad1ca567ccc1\", \"creator_attributes\": {\"folderPath\": \"/shots/sq01/Video_1sq01sh010\", \"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"frameStart\": 1001, \"frameEnd\": 1102, \"clipIn\": 509, \"clipOut\": 610, \"clipDuration\": 101, \"sourceIn\": 0, \"sourceOut\": 101, \"fps\": \"from_selection\"}, \"publish_attributes\": {\"CollectSlackFamilies\": {\"additional_message\": \"\"}}}, \"io.ayon.creators.resolve.plate\": {\"id\": \"pyblish.avalon.instance\", \"productType\": \"plate\", \"productName\": \"plateVideo_1\", \"active\": true, \"creator_identifier\": \"io.ayon.creators.resolve.plate\", \"variant\": \"Video_1\", \"folderPath\": \"/shots/sq01/Video_1sq01sh010\", \"task\": \"Generic\", \"clip_variant\": \"<track_name>\", \"clip_index\": \"cfea9be4-3ecd-4253-a0a9-1a2bc9a4a184\", \"clip_source_resolution\": {\"width\": \"1920\", \"height\": \"1080\", \"pixelAspect\": 1.0}, \"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"sq01\", \"track\": \"{_track_}\", \"shot\": \"sh###\", \"hierarchy\": \"shots/sq01\", \"sourceResolution\": false, \"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"parents\": [{\"folder_type\": \"folder\", \"entity_name\": \"shots\"}, {\"folder_type\": \"sequence\", \"entity_name\": \"sq01\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"sq01\", \"track\": \"Video_1\", \"shot\": \"sh010\"}, \"heroTrack\": true, \"uuid\": \"647b2ba6-6fca-4219-b163-cd321df9652f\", \"reviewTrack\": null, \"parent_instance_id\": \"2ab8f149-e32c-40f5-a6cb-ad1ca567ccc1\", \"label\": \"/shots/sq01/Video_1sq01sh010 plate\", \"has_promised_context\": true, \"newHierarchyIntegration\": true, \"newAssetPublishing\": true, \"instance_id\": \"9f866936-966b-4a73-8e61-1a5b6e648a3f\", \"creator_attributes\": {\"parentInstance\": \"/shots/sq01/Video_1sq01sh010 shot\", \"vSyncOn\": false, \"vSyncTrack\": \"Video 1\"}, \"publish_attributes\": {\"CollectSlackFamilies\": {\"additional_message\": \"\"}}}}, \"clip_index\": \"cfea9be4-3ecd-4253-a0a9-1a2bc9a4a184\", \"publish\": true}"
|
||||
},
|
||||
"clip_index": "cfea9be4-3ecd-4253-a0a9-1a2bc9a4a184",
|
||||
"publish": true,
|
||||
"resolve_sub_products": {
|
||||
"io.ayon.creators.resolve.plate": {
|
||||
"active": true,
|
||||
"clip_index": "cfea9be4-3ecd-4253-a0a9-1a2bc9a4a184",
|
||||
"clip_source_resolution": {
|
||||
"height": "1080",
|
||||
"pixelAspect": 1.0,
|
||||
"width": "1920"
|
||||
},
|
||||
"clip_variant": "<track_name>",
|
||||
"creator_attributes": {
|
||||
"parentInstance": "/shots/sq01/Video_1sq01sh010 shot",
|
||||
"vSyncOn": false,
|
||||
"vSyncTrack": "Video 1"
|
||||
},
|
||||
"creator_identifier": "io.ayon.creators.resolve.plate",
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"folderPath": "/shots/sq01/Video_1sq01sh010",
|
||||
"handleEnd": 10,
|
||||
"handleStart": 10,
|
||||
"has_promised_context": true,
|
||||
"heroTrack": true,
|
||||
"hierarchy": "shots/sq01",
|
||||
"hierarchyData": {
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"sequence": "sq01",
|
||||
"shot": "sh010",
|
||||
"track": "Video_1"
|
||||
},
|
||||
"id": "pyblish.avalon.instance",
|
||||
"instance_id": "9f866936-966b-4a73-8e61-1a5b6e648a3f",
|
||||
"label": "/shots/sq01/Video_1sq01sh010 plate",
|
||||
"newAssetPublishing": true,
|
||||
"newHierarchyIntegration": true,
|
||||
"parent_instance_id": "2ab8f149-e32c-40f5-a6cb-ad1ca567ccc1",
|
||||
"parents": [
|
||||
{
|
||||
"entity_name": "shots",
|
||||
"folder_type": "folder"
|
||||
},
|
||||
{
|
||||
"entity_name": "sq01",
|
||||
"folder_type": "sequence"
|
||||
}
|
||||
],
|
||||
"productName": "plateVideo_1",
|
||||
"productType": "plate",
|
||||
"publish_attributes": {
|
||||
"CollectSlackFamilies": {
|
||||
"additional_message": ""
|
||||
}
|
||||
},
|
||||
"reviewTrack": null,
|
||||
"sequence": "sq01",
|
||||
"shot": "sh###",
|
||||
"sourceResolution": false,
|
||||
"task": "Generic",
|
||||
"track": "{_track_}",
|
||||
"uuid": "647b2ba6-6fca-4219-b163-cd321df9652f",
|
||||
"variant": "Video_1",
|
||||
"workfileFrameStart": 1001
|
||||
},
|
||||
"io.ayon.creators.resolve.shot": {
|
||||
"active": true,
|
||||
"clip_index": "cfea9be4-3ecd-4253-a0a9-1a2bc9a4a184",
|
||||
"clip_source_resolution": {
|
||||
"height": "1080",
|
||||
"pixelAspect": 1.0,
|
||||
"width": "1920"
|
||||
},
|
||||
"clip_variant": "<track_name>",
|
||||
"creator_attributes": {
|
||||
"clipDuration": 101,
|
||||
"clipIn": 509,
|
||||
"clipOut": 610,
|
||||
"folderPath": "/shots/sq01/Video_1sq01sh010",
|
||||
"fps": "from_selection",
|
||||
"frameEnd": 1102,
|
||||
"frameStart": 1001,
|
||||
"handleEnd": 10,
|
||||
"handleStart": 10,
|
||||
"sourceIn": 0,
|
||||
"sourceOut": 101,
|
||||
"workfileFrameStart": 1001
|
||||
},
|
||||
"creator_identifier": "io.ayon.creators.resolve.shot",
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"folderPath": "/shots/sq01/Video_1sq01sh010",
|
||||
"handleEnd": 10,
|
||||
"handleStart": 10,
|
||||
"has_promised_context": true,
|
||||
"heroTrack": true,
|
||||
"hierarchy": "shots/sq01",
|
||||
"hierarchyData": {
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"sequence": "sq01",
|
||||
"shot": "sh010",
|
||||
"track": "Video_1"
|
||||
},
|
||||
"id": "pyblish.avalon.instance",
|
||||
"instance_id": "2ab8f149-e32c-40f5-a6cb-ad1ca567ccc1",
|
||||
"label": "/shots/sq01/Video_1sq01sh010 shot",
|
||||
"newAssetPublishing": true,
|
||||
"newHierarchyIntegration": true,
|
||||
"parents": [
|
||||
{
|
||||
"entity_name": "shots",
|
||||
"folder_type": "folder"
|
||||
},
|
||||
{
|
||||
"entity_name": "sq01",
|
||||
"folder_type": "sequence"
|
||||
}
|
||||
],
|
||||
"productName": "shotMain",
|
||||
"productType": "shot",
|
||||
"publish_attributes": {
|
||||
"CollectSlackFamilies": {
|
||||
"additional_message": ""
|
||||
}
|
||||
},
|
||||
"reviewTrack": null,
|
||||
"sequence": "sq01",
|
||||
"shot": "sh###",
|
||||
"sourceResolution": false,
|
||||
"task": "Generic",
|
||||
"track": "{_track_}",
|
||||
"uuid": "647b2ba6-6fca-4219-b163-cd321df9652f",
|
||||
"variant": "Main",
|
||||
"workfileFrameStart": 1001
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "AyonData",
|
||||
"color": "GREEN",
|
||||
"marked_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 1.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 50.0
|
||||
}
|
||||
},
|
||||
"comment": ""
|
||||
}
|
||||
],
|
||||
"enabled": true,
|
||||
"media_references": {
|
||||
"DEFAULT_MEDIA": {
|
||||
"OTIO_SCHEMA": "ImageSequenceReference.1",
|
||||
"metadata": {},
|
||||
"name": "output.[1000-1100].tif",
|
||||
"available_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 101.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 0.0
|
||||
}
|
||||
},
|
||||
"available_image_bounds": null,
|
||||
"target_url_base": "C:\\Users\\robin\\OneDrive\\Bureau\\dev_ayon\\samples",
|
||||
"name_prefix": "output.",
|
||||
"name_suffix": ".tif",
|
||||
"start_frame": 1000,
|
||||
"frame_step": 1,
|
||||
"rate": 25.0,
|
||||
"frame_zero_padding": 4,
|
||||
"missing_frame_policy": "error"
|
||||
}
|
||||
},
|
||||
"active_media_reference_key": "DEFAULT_MEDIA"
|
||||
}
|
||||
|
|
@ -0,0 +1,363 @@
|
|||
{
|
||||
"OTIO_SCHEMA": "Clip.2",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {}
|
||||
},
|
||||
"name": "output.[1000-1100].tif",
|
||||
"source_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 91.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 5.0
|
||||
}
|
||||
},
|
||||
"effects": [
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Transform",
|
||||
"Enabled": true,
|
||||
"Name": "Transform",
|
||||
"Parameters": [],
|
||||
"Type": 2
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Cropping",
|
||||
"Enabled": true,
|
||||
"Name": "Cropping",
|
||||
"Parameters": [],
|
||||
"Type": 3
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Dynamic Zoom",
|
||||
"Enabled": false,
|
||||
"Name": "Dynamic Zoom",
|
||||
"Parameters": [
|
||||
{
|
||||
"Default Parameter Value": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Key Frames": {
|
||||
"-5": {
|
||||
"Value": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Variant Type": "POINTF"
|
||||
},
|
||||
"955": {
|
||||
"Value": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Variant Type": "POINTF"
|
||||
}
|
||||
},
|
||||
"Parameter ID": "dynamicZoomCenter",
|
||||
"Parameter Value": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Variant Type": "POINTF"
|
||||
},
|
||||
{
|
||||
"Default Parameter Value": 1.0,
|
||||
"Key Frames": {
|
||||
"-5": {
|
||||
"Value": 0.8,
|
||||
"Variant Type": "Double"
|
||||
},
|
||||
"955": {
|
||||
"Value": 1.0,
|
||||
"Variant Type": "Double"
|
||||
}
|
||||
},
|
||||
"Parameter ID": "dynamicZoomScale",
|
||||
"Parameter Value": 1.0,
|
||||
"Variant Type": "Double",
|
||||
"maxValue": 100.0,
|
||||
"minValue": 0.01
|
||||
}
|
||||
],
|
||||
"Type": 59
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Composite",
|
||||
"Enabled": true,
|
||||
"Name": "Composite",
|
||||
"Parameters": [],
|
||||
"Type": 1
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Lens Correction",
|
||||
"Enabled": true,
|
||||
"Name": "Lens Correction",
|
||||
"Parameters": [],
|
||||
"Type": 43
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Retime and Scaling",
|
||||
"Enabled": true,
|
||||
"Name": "Retime and Scaling",
|
||||
"Parameters": [],
|
||||
"Type": 22
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Video Faders",
|
||||
"Enabled": true,
|
||||
"Name": "Video Faders",
|
||||
"Parameters": [],
|
||||
"Type": 36
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
}
|
||||
],
|
||||
"markers": [
|
||||
{
|
||||
"OTIO_SCHEMA": "Marker.2",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Keywords": [],
|
||||
"Note": "{\"resolve_sub_products\": {\"io.ayon.creators.resolve.shot\": {\"id\": \"pyblish.avalon.instance\", \"productType\": \"shot\", \"productName\": \"shotMain\", \"active\": true, \"creator_identifier\": \"io.ayon.creators.resolve.shot\", \"variant\": \"Main\", \"folderPath\": \"/shots/seq_img_notc_blackhandles/sh010\", \"task\": \"Generic\", \"clip_variant\": \"<track_name>\", \"clip_index\": \"a82520bd-f231-4a23-9cb7-8823141232db\", \"clip_source_resolution\": {\"width\": \"1920\", \"height\": \"1080\", \"pixelAspect\": 1.0}, \"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"seq_img_notc_blackhandles\", \"track\": \"{_track_}\", \"shot\": \"sh###\", \"hierarchy\": \"shots/seq_img_notc_blackhandles\", \"sourceResolution\": false, \"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"parents\": [{\"folder_type\": \"folder\", \"entity_name\": \"shots\"}, {\"folder_type\": \"sequence\", \"entity_name\": \"seq_img_notc_blackhandles\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"seq_img_notc_blackhandles\", \"track\": \"Video1\", \"shot\": \"sh010\"}, \"heroTrack\": true, \"uuid\": \"5d6be326-f1d0-4416-b6aa-780d05a8dd6d\", \"reviewTrack\": \"Video1\", \"label\": \"/shots/seq_img_notc_blackhandles/sh010 shot\", \"has_promised_context\": true, \"newHierarchyIntegration\": true, \"newAssetPublishing\": true, \"instance_id\": \"e196263f-c584-40b4-bc27-018051a3bc92\", \"creator_attributes\": {\"folderPath\": \"/shots/seq_img_notc_blackhandles/sh010\", \"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"frameStart\": 1001, \"frameEnd\": 1092, \"clipIn\": 86511, \"clipOut\": 86602, \"clipDuration\": 91, \"sourceIn\": 5, \"sourceOut\": 96, \"fps\": \"from_selection\"}, \"publish_attributes\": {\"CollectSlackFamilies\": {\"additional_message\": \"\"}}}, \"io.ayon.creators.resolve.plate\": {\"id\": \"pyblish.avalon.instance\", \"productType\": \"plate\", \"productName\": \"plateVideo1\", \"active\": true, \"creator_identifier\": \"io.ayon.creators.resolve.plate\", \"variant\": \"Video1\", \"folderPath\": \"/shots/seq_img_notc_blackhandles/sh010\", \"task\": \"Generic\", \"clip_variant\": \"<track_name>\", \"clip_index\": \"a82520bd-f231-4a23-9cb7-8823141232db\", \"clip_source_resolution\": {\"width\": \"1920\", \"height\": \"1080\", \"pixelAspect\": 1.0}, \"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"seq_img_notc_blackhandles\", \"track\": \"{_track_}\", \"shot\": \"sh###\", \"hierarchy\": \"shots/seq_img_notc_blackhandles\", \"sourceResolution\": false, \"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"parents\": [{\"folder_type\": \"folder\", \"entity_name\": \"shots\"}, {\"folder_type\": \"sequence\", \"entity_name\": \"seq_img_notc_blackhandles\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"seq_img_notc_blackhandles\", \"track\": \"Video1\", \"shot\": \"sh010\"}, \"heroTrack\": true, \"uuid\": \"5d6be326-f1d0-4416-b6aa-780d05a8dd6d\", \"reviewTrack\": \"Video1\", \"parent_instance_id\": \"e196263f-c584-40b4-bc27-018051a3bc92\", \"label\": \"/shots/seq_img_notc_blackhandles/sh010 plate\", \"has_promised_context\": true, \"newHierarchyIntegration\": true, \"newAssetPublishing\": true, \"instance_id\": \"ced7e9b8-721a-4377-a827-15fbf7f2831a\", \"creator_attributes\": {\"parentInstance\": \"/shots/seq_img_notc_blackhandles/sh010 shot\", \"vSyncOn\": true, \"vSyncTrack\": \"Video1\"}, \"publish_attributes\": {\"CollectSlackFamilies\": {\"additional_message\": \"\"}}}}, \"clip_index\": \"a82520bd-f231-4a23-9cb7-8823141232db\", \"publish\": true}"
|
||||
},
|
||||
"clip_index": "a82520bd-f231-4a23-9cb7-8823141232db",
|
||||
"publish": true,
|
||||
"resolve_sub_products": {
|
||||
"io.ayon.creators.resolve.plate": {
|
||||
"active": true,
|
||||
"clip_index": "a82520bd-f231-4a23-9cb7-8823141232db",
|
||||
"clip_source_resolution": {
|
||||
"height": "1080",
|
||||
"pixelAspect": 1.0,
|
||||
"width": "1920"
|
||||
},
|
||||
"clip_variant": "<track_name>",
|
||||
"creator_attributes": {
|
||||
"parentInstance": "/shots/seq_img_notc_blackhandles/sh010 shot",
|
||||
"vSyncOn": true,
|
||||
"vSyncTrack": "Video1"
|
||||
},
|
||||
"creator_identifier": "io.ayon.creators.resolve.plate",
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"folderPath": "/shots/seq_img_notc_blackhandles/sh010",
|
||||
"handleEnd": 10,
|
||||
"handleStart": 10,
|
||||
"has_promised_context": true,
|
||||
"heroTrack": true,
|
||||
"hierarchy": "shots/seq_img_notc_blackhandles",
|
||||
"hierarchyData": {
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"sequence": "seq_img_notc_blackhandles",
|
||||
"shot": "sh010",
|
||||
"track": "Video1"
|
||||
},
|
||||
"id": "pyblish.avalon.instance",
|
||||
"instance_id": "ced7e9b8-721a-4377-a827-15fbf7f2831a",
|
||||
"label": "/shots/seq_img_notc_blackhandles/sh010 plate",
|
||||
"newAssetPublishing": true,
|
||||
"newHierarchyIntegration": true,
|
||||
"parent_instance_id": "e196263f-c584-40b4-bc27-018051a3bc92",
|
||||
"parents": [
|
||||
{
|
||||
"entity_name": "shots",
|
||||
"folder_type": "folder"
|
||||
},
|
||||
{
|
||||
"entity_name": "seq_img_notc_blackhandles",
|
||||
"folder_type": "sequence"
|
||||
}
|
||||
],
|
||||
"productName": "plateVideo1",
|
||||
"productType": "plate",
|
||||
"publish_attributes": {
|
||||
"CollectSlackFamilies": {
|
||||
"additional_message": ""
|
||||
}
|
||||
},
|
||||
"reviewTrack": "Video1",
|
||||
"sequence": "seq_img_notc_blackhandles",
|
||||
"shot": "sh###",
|
||||
"sourceResolution": false,
|
||||
"task": "Generic",
|
||||
"track": "{_track_}",
|
||||
"uuid": "5d6be326-f1d0-4416-b6aa-780d05a8dd6d",
|
||||
"variant": "Video1",
|
||||
"workfileFrameStart": 1001
|
||||
},
|
||||
"io.ayon.creators.resolve.shot": {
|
||||
"active": true,
|
||||
"clip_index": "a82520bd-f231-4a23-9cb7-8823141232db",
|
||||
"clip_source_resolution": {
|
||||
"height": "1080",
|
||||
"pixelAspect": 1.0,
|
||||
"width": "1920"
|
||||
},
|
||||
"clip_variant": "<track_name>",
|
||||
"creator_attributes": {
|
||||
"clipDuration": 91,
|
||||
"clipIn": 86511,
|
||||
"clipOut": 86602,
|
||||
"folderPath": "/shots/seq_img_notc_blackhandles/sh010",
|
||||
"fps": "from_selection",
|
||||
"frameEnd": 1092,
|
||||
"frameStart": 1001,
|
||||
"handleEnd": 10,
|
||||
"handleStart": 10,
|
||||
"sourceIn": 5,
|
||||
"sourceOut": 96,
|
||||
"workfileFrameStart": 1001
|
||||
},
|
||||
"creator_identifier": "io.ayon.creators.resolve.shot",
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"folderPath": "/shots/seq_img_notc_blackhandles/sh010",
|
||||
"handleEnd": 10,
|
||||
"handleStart": 10,
|
||||
"has_promised_context": true,
|
||||
"heroTrack": true,
|
||||
"hierarchy": "shots/seq_img_notc_blackhandles",
|
||||
"hierarchyData": {
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"sequence": "seq_img_notc_blackhandles",
|
||||
"shot": "sh010",
|
||||
"track": "Video1"
|
||||
},
|
||||
"id": "pyblish.avalon.instance",
|
||||
"instance_id": "e196263f-c584-40b4-bc27-018051a3bc92",
|
||||
"label": "/shots/seq_img_notc_blackhandles/sh010 shot",
|
||||
"newAssetPublishing": true,
|
||||
"newHierarchyIntegration": true,
|
||||
"parents": [
|
||||
{
|
||||
"entity_name": "shots",
|
||||
"folder_type": "folder"
|
||||
},
|
||||
{
|
||||
"entity_name": "seq_img_notc_blackhandles",
|
||||
"folder_type": "sequence"
|
||||
}
|
||||
],
|
||||
"productName": "shotMain",
|
||||
"productType": "shot",
|
||||
"publish_attributes": {
|
||||
"CollectSlackFamilies": {
|
||||
"additional_message": ""
|
||||
}
|
||||
},
|
||||
"reviewTrack": "Video1",
|
||||
"sequence": "seq_img_notc_blackhandles",
|
||||
"shot": "sh###",
|
||||
"sourceResolution": false,
|
||||
"task": "Generic",
|
||||
"track": "{_track_}",
|
||||
"uuid": "5d6be326-f1d0-4416-b6aa-780d05a8dd6d",
|
||||
"variant": "Main",
|
||||
"workfileFrameStart": 1001
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "AyonData",
|
||||
"color": "GREEN",
|
||||
"marked_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 1.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 50.0
|
||||
}
|
||||
},
|
||||
"comment": ""
|
||||
}
|
||||
],
|
||||
"enabled": true,
|
||||
"media_references": {
|
||||
"DEFAULT_MEDIA": {
|
||||
"OTIO_SCHEMA": "ImageSequenceReference.1",
|
||||
"metadata": {},
|
||||
"name": "output.[1000-1100].tif",
|
||||
"available_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 101.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 0.0
|
||||
}
|
||||
},
|
||||
"available_image_bounds": null,
|
||||
"target_url_base": "C:\\tif_seq",
|
||||
"name_prefix": "output.",
|
||||
"name_suffix": ".tif",
|
||||
"start_frame": 1000,
|
||||
"frame_step": 1,
|
||||
"rate": 25.0,
|
||||
"frame_zero_padding": 4,
|
||||
"missing_frame_policy": "error"
|
||||
}
|
||||
},
|
||||
"active_media_reference_key": "DEFAULT_MEDIA"
|
||||
}
|
||||
|
|
@ -0,0 +1,363 @@
|
|||
{
|
||||
"OTIO_SCHEMA": "Clip.2",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {}
|
||||
},
|
||||
"name": "output.[1000-1100].tif",
|
||||
"source_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 39.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 34.0
|
||||
}
|
||||
},
|
||||
"effects": [
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Transform",
|
||||
"Enabled": true,
|
||||
"Name": "Transform",
|
||||
"Parameters": [],
|
||||
"Type": 2
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Cropping",
|
||||
"Enabled": true,
|
||||
"Name": "Cropping",
|
||||
"Parameters": [],
|
||||
"Type": 3
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Dynamic Zoom",
|
||||
"Enabled": false,
|
||||
"Name": "Dynamic Zoom",
|
||||
"Parameters": [
|
||||
{
|
||||
"Default Parameter Value": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Key Frames": {
|
||||
"-34": {
|
||||
"Value": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Variant Type": "POINTF"
|
||||
},
|
||||
"966": {
|
||||
"Value": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Variant Type": "POINTF"
|
||||
}
|
||||
},
|
||||
"Parameter ID": "dynamicZoomCenter",
|
||||
"Parameter Value": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Variant Type": "POINTF"
|
||||
},
|
||||
{
|
||||
"Default Parameter Value": 1.0,
|
||||
"Key Frames": {
|
||||
"-34": {
|
||||
"Value": 0.8,
|
||||
"Variant Type": "Double"
|
||||
},
|
||||
"966": {
|
||||
"Value": 1.0,
|
||||
"Variant Type": "Double"
|
||||
}
|
||||
},
|
||||
"Parameter ID": "dynamicZoomScale",
|
||||
"Parameter Value": 1.0,
|
||||
"Variant Type": "Double",
|
||||
"maxValue": 100.0,
|
||||
"minValue": 0.01
|
||||
}
|
||||
],
|
||||
"Type": 59
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Composite",
|
||||
"Enabled": true,
|
||||
"Name": "Composite",
|
||||
"Parameters": [],
|
||||
"Type": 1
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Lens Correction",
|
||||
"Enabled": true,
|
||||
"Name": "Lens Correction",
|
||||
"Parameters": [],
|
||||
"Type": 43
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Retime and Scaling",
|
||||
"Enabled": true,
|
||||
"Name": "Retime and Scaling",
|
||||
"Parameters": [],
|
||||
"Type": 22
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Video Faders",
|
||||
"Enabled": true,
|
||||
"Name": "Video Faders",
|
||||
"Parameters": [],
|
||||
"Type": 36
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
}
|
||||
],
|
||||
"markers": [
|
||||
{
|
||||
"OTIO_SCHEMA": "Marker.2",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Keywords": [],
|
||||
"Note": "{\"resolve_sub_products\": {\"io.ayon.creators.resolve.shot\": {\"id\": \"pyblish.avalon.instance\", \"productType\": \"shot\", \"productName\": \"shotMain\", \"active\": true, \"creator_identifier\": \"io.ayon.creators.resolve.shot\", \"variant\": \"Main\", \"folderPath\": \"/shots/sq01/Video_1sq01sh010\", \"task\": \"Generic\", \"clip_variant\": \"<track_name>\", \"clip_index\": \"cfea9be4-3ecd-4253-a0a9-1a2bc9a4a184\", \"clip_source_resolution\": {\"width\": \"1920\", \"height\": \"1080\", \"pixelAspect\": 1.0}, \"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"sq01\", \"track\": \"{_track_}\", \"shot\": \"sh###\", \"hierarchy\": \"shots/sq01\", \"sourceResolution\": false, \"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"parents\": [{\"folder_type\": \"folder\", \"entity_name\": \"shots\"}, {\"folder_type\": \"sequence\", \"entity_name\": \"sq01\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"sq01\", \"track\": \"Video_1\", \"shot\": \"sh010\"}, \"heroTrack\": true, \"uuid\": \"5ca7b240-ec4c-49d1-841d-a96c33a08b1b\", \"reviewTrack\": null, \"label\": \"/shots/sq01/Video_1sq01sh010 shot\", \"has_promised_context\": true, \"newHierarchyIntegration\": true, \"newAssetPublishing\": true, \"instance_id\": \"5b526964-5805-4412-af09-2da696c4978b\", \"creator_attributes\": {\"folderPath\": \"/shots/sq01/Video_1sq01sh010\", \"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"frameStart\": 1001, \"frameEnd\": 1040, \"clipIn\": 543, \"clipOut\": 582, \"clipDuration\": 39, \"sourceIn\": 34, \"sourceOut\": 73, \"fps\": \"from_selection\"}, \"publish_attributes\": {\"CollectSlackFamilies\": {\"additional_message\": \"\"}}}, \"io.ayon.creators.resolve.plate\": {\"id\": \"pyblish.avalon.instance\", \"productType\": \"plate\", \"productName\": \"plateVideo_1\", \"active\": true, \"creator_identifier\": \"io.ayon.creators.resolve.plate\", \"variant\": \"Video_1\", \"folderPath\": \"/shots/sq01/Video_1sq01sh010\", \"task\": \"Generic\", \"clip_variant\": \"<track_name>\", \"clip_index\": \"cfea9be4-3ecd-4253-a0a9-1a2bc9a4a184\", \"clip_source_resolution\": {\"width\": \"1920\", \"height\": \"1080\", \"pixelAspect\": 1.0}, \"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"sq01\", \"track\": \"{_track_}\", \"shot\": \"sh###\", \"hierarchy\": \"shots/sq01\", \"sourceResolution\": false, \"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"parents\": [{\"folder_type\": \"folder\", \"entity_name\": \"shots\"}, {\"folder_type\": \"sequence\", \"entity_name\": \"sq01\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"sq01\", \"track\": \"Video_1\", \"shot\": \"sh010\"}, \"heroTrack\": true, \"uuid\": \"5ca7b240-ec4c-49d1-841d-a96c33a08b1b\", \"reviewTrack\": null, \"parent_instance_id\": \"5b526964-5805-4412-af09-2da696c4978b\", \"label\": \"/shots/sq01/Video_1sq01sh010 plate\", \"has_promised_context\": true, \"newHierarchyIntegration\": true, \"newAssetPublishing\": true, \"instance_id\": \"992ab293-943b-4894-8a7f-c42b54b4d582\", \"creator_attributes\": {\"parentInstance\": \"/shots/sq01/Video_1sq01sh010 shot\", \"vSyncOn\": false, \"vSyncTrack\": \"Video 1\"}, \"publish_attributes\": {\"CollectSlackFamilies\": {\"additional_message\": \"\"}}}}, \"clip_index\": \"cfea9be4-3ecd-4253-a0a9-1a2bc9a4a184\", \"publish\": true}"
|
||||
},
|
||||
"clip_index": "cfea9be4-3ecd-4253-a0a9-1a2bc9a4a184",
|
||||
"publish": true,
|
||||
"resolve_sub_products": {
|
||||
"io.ayon.creators.resolve.plate": {
|
||||
"active": true,
|
||||
"clip_index": "cfea9be4-3ecd-4253-a0a9-1a2bc9a4a184",
|
||||
"clip_source_resolution": {
|
||||
"height": "1080",
|
||||
"pixelAspect": 1.0,
|
||||
"width": "1920"
|
||||
},
|
||||
"clip_variant": "<track_name>",
|
||||
"creator_attributes": {
|
||||
"parentInstance": "/shots/sq01/Video_1sq01sh010 shot",
|
||||
"vSyncOn": false,
|
||||
"vSyncTrack": "Video 1"
|
||||
},
|
||||
"creator_identifier": "io.ayon.creators.resolve.plate",
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"folderPath": "/shots/sq01/Video_1sq01sh010",
|
||||
"handleEnd": 10,
|
||||
"handleStart": 10,
|
||||
"has_promised_context": true,
|
||||
"heroTrack": true,
|
||||
"hierarchy": "shots/sq01",
|
||||
"hierarchyData": {
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"sequence": "sq01",
|
||||
"shot": "sh010",
|
||||
"track": "Video_1"
|
||||
},
|
||||
"id": "pyblish.avalon.instance",
|
||||
"instance_id": "992ab293-943b-4894-8a7f-c42b54b4d582",
|
||||
"label": "/shots/sq01/Video_1sq01sh010 plate",
|
||||
"newAssetPublishing": true,
|
||||
"newHierarchyIntegration": true,
|
||||
"parent_instance_id": "5b526964-5805-4412-af09-2da696c4978b",
|
||||
"parents": [
|
||||
{
|
||||
"entity_name": "shots",
|
||||
"folder_type": "folder"
|
||||
},
|
||||
{
|
||||
"entity_name": "sq01",
|
||||
"folder_type": "sequence"
|
||||
}
|
||||
],
|
||||
"productName": "plateVideo_1",
|
||||
"productType": "plate",
|
||||
"publish_attributes": {
|
||||
"CollectSlackFamilies": {
|
||||
"additional_message": ""
|
||||
}
|
||||
},
|
||||
"reviewTrack": null,
|
||||
"sequence": "sq01",
|
||||
"shot": "sh###",
|
||||
"sourceResolution": false,
|
||||
"task": "Generic",
|
||||
"track": "{_track_}",
|
||||
"uuid": "5ca7b240-ec4c-49d1-841d-a96c33a08b1b",
|
||||
"variant": "Video_1",
|
||||
"workfileFrameStart": 1001
|
||||
},
|
||||
"io.ayon.creators.resolve.shot": {
|
||||
"active": true,
|
||||
"clip_index": "cfea9be4-3ecd-4253-a0a9-1a2bc9a4a184",
|
||||
"clip_source_resolution": {
|
||||
"height": "1080",
|
||||
"pixelAspect": 1.0,
|
||||
"width": "1920"
|
||||
},
|
||||
"clip_variant": "<track_name>",
|
||||
"creator_attributes": {
|
||||
"clipDuration": 39,
|
||||
"clipIn": 543,
|
||||
"clipOut": 582,
|
||||
"folderPath": "/shots/sq01/Video_1sq01sh010",
|
||||
"fps": "from_selection",
|
||||
"frameEnd": 1040,
|
||||
"frameStart": 1001,
|
||||
"handleEnd": 10,
|
||||
"handleStart": 10,
|
||||
"sourceIn": 34,
|
||||
"sourceOut": 73,
|
||||
"workfileFrameStart": 1001
|
||||
},
|
||||
"creator_identifier": "io.ayon.creators.resolve.shot",
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"folderPath": "/shots/sq01/Video_1sq01sh010",
|
||||
"handleEnd": 10,
|
||||
"handleStart": 10,
|
||||
"has_promised_context": true,
|
||||
"heroTrack": true,
|
||||
"hierarchy": "shots/sq01",
|
||||
"hierarchyData": {
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"sequence": "sq01",
|
||||
"shot": "sh010",
|
||||
"track": "Video_1"
|
||||
},
|
||||
"id": "pyblish.avalon.instance",
|
||||
"instance_id": "5b526964-5805-4412-af09-2da696c4978b",
|
||||
"label": "/shots/sq01/Video_1sq01sh010 shot",
|
||||
"newAssetPublishing": true,
|
||||
"newHierarchyIntegration": true,
|
||||
"parents": [
|
||||
{
|
||||
"entity_name": "shots",
|
||||
"folder_type": "folder"
|
||||
},
|
||||
{
|
||||
"entity_name": "sq01",
|
||||
"folder_type": "sequence"
|
||||
}
|
||||
],
|
||||
"productName": "shotMain",
|
||||
"productType": "shot",
|
||||
"publish_attributes": {
|
||||
"CollectSlackFamilies": {
|
||||
"additional_message": ""
|
||||
}
|
||||
},
|
||||
"reviewTrack": null,
|
||||
"sequence": "sq01",
|
||||
"shot": "sh###",
|
||||
"sourceResolution": false,
|
||||
"task": "Generic",
|
||||
"track": "{_track_}",
|
||||
"uuid": "5ca7b240-ec4c-49d1-841d-a96c33a08b1b",
|
||||
"variant": "Main",
|
||||
"workfileFrameStart": 1001
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "AyonData",
|
||||
"color": "GREEN",
|
||||
"marked_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 1.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 53.0
|
||||
}
|
||||
},
|
||||
"comment": ""
|
||||
}
|
||||
],
|
||||
"enabled": true,
|
||||
"media_references": {
|
||||
"DEFAULT_MEDIA": {
|
||||
"OTIO_SCHEMA": "ImageSequenceReference.1",
|
||||
"metadata": {},
|
||||
"name": "output.[1000-1100].tif",
|
||||
"available_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 101.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 0.0
|
||||
}
|
||||
},
|
||||
"available_image_bounds": null,
|
||||
"target_url_base": "C:\\Users\\robin\\OneDrive\\Bureau\\dev_ayon\\samples",
|
||||
"name_prefix": "output.",
|
||||
"name_suffix": ".tif",
|
||||
"start_frame": 1000,
|
||||
"frame_step": 1,
|
||||
"rate": 25.0,
|
||||
"frame_zero_padding": 4,
|
||||
"missing_frame_policy": "error"
|
||||
}
|
||||
},
|
||||
"active_media_reference_key": "DEFAULT_MEDIA"
|
||||
}
|
||||
|
|
@ -0,0 +1,216 @@
|
|||
{
|
||||
"OTIO_SCHEMA": "Clip.2",
|
||||
"metadata": {},
|
||||
"name": "output.[1000-1100].exr",
|
||||
"source_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 104.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 0.0
|
||||
}
|
||||
},
|
||||
"effects": [],
|
||||
"markers": [
|
||||
{
|
||||
"OTIO_SCHEMA": "Marker.2",
|
||||
"metadata": {
|
||||
"clip_index": "18b19490-21ea-4533-9e0c-03f434c66f14",
|
||||
"publish": true,
|
||||
"resolve_sub_products": {
|
||||
"io.ayon.creators.resolve.plate": {
|
||||
"active": true,
|
||||
"clip_index": "18b19490-21ea-4533-9e0c-03f434c66f14",
|
||||
"clip_source_resolution": {
|
||||
"height": "720",
|
||||
"pixelAspect": 1.0,
|
||||
"width": "956"
|
||||
},
|
||||
"clip_variant": "<track_name>",
|
||||
"creator_attributes": {
|
||||
"parentInstance": "/shots/sq_old_otio/sh010 shot",
|
||||
"vSyncOn": true,
|
||||
"vSyncTrack": "Video 1"
|
||||
},
|
||||
"creator_identifier": "io.ayon.creators.resolve.plate",
|
||||
"episode": "ep01",
|
||||
"folder": "/shots/sq_old_otio/sh010",
|
||||
"folderPath": "/shots/sq_old_otio/sh010",
|
||||
"handleEnd": 10,
|
||||
"handleStart": 10,
|
||||
"has_promised_context": true,
|
||||
"heroTrack": true,
|
||||
"hierarchy": "shots/sq_old_otio",
|
||||
"hierarchyData": {
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"sequence": "sq_old_otio",
|
||||
"shot": "sh010",
|
||||
"track": "Video_1"
|
||||
},
|
||||
"id": "pyblish.avalon.instance",
|
||||
"instance_id": "d27c5f77-7218-44ca-8061-5b6d33f96116",
|
||||
"label": "/shots/sq_old_otio/sh010 plate",
|
||||
"newAssetPublishing": true,
|
||||
"newHierarchyIntegration": true,
|
||||
"parent_instance_id": "24792946-9ac4-4c8d-922f-80a83dea4be1",
|
||||
"parents": [
|
||||
{
|
||||
"entity_name": "shots",
|
||||
"folder_type": "folder"
|
||||
},
|
||||
{
|
||||
"entity_name": "sq_old_otio",
|
||||
"folder_type": "sequence"
|
||||
}
|
||||
],
|
||||
"productName": "plateVideo_1",
|
||||
"productType": "plate",
|
||||
"publish_attributes": {
|
||||
"CollectSlackFamilies": {
|
||||
"additional_message": ""
|
||||
}
|
||||
},
|
||||
"reviewTrack": "Video 1",
|
||||
"sequence": "sq_old_otio",
|
||||
"shot": "sh###",
|
||||
"sourceResolution": false,
|
||||
"task": null,
|
||||
"track": "{_track_}",
|
||||
"uuid": "dec1a40b-7ce8-43cd-94b8-08a53056a171",
|
||||
"variant": "Video_1",
|
||||
"workfileFrameStart": 1001
|
||||
},
|
||||
"io.ayon.creators.resolve.shot": {
|
||||
"active": true,
|
||||
"clip_index": "18b19490-21ea-4533-9e0c-03f434c66f14",
|
||||
"clip_source_resolution": {
|
||||
"height": "720",
|
||||
"pixelAspect": 1.0,
|
||||
"width": "956"
|
||||
},
|
||||
"clip_variant": "<track_name>",
|
||||
"creator_attributes": {
|
||||
"clipDuration": 104,
|
||||
"clipIn": 90000,
|
||||
"clipOut": 90104,
|
||||
"fps": "from_selection",
|
||||
"frameEnd": 1105,
|
||||
"frameStart": 1001,
|
||||
"handleEnd": 10,
|
||||
"handleStart": 10,
|
||||
"sourceIn": 0,
|
||||
"sourceOut": 104,
|
||||
"workfileFrameStart": 1001
|
||||
},
|
||||
"creator_identifier": "io.ayon.creators.resolve.shot",
|
||||
"episode": "ep01",
|
||||
"folder": "/shots/sq_old_otio/sh010",
|
||||
"folderPath": "/shots/sq_old_otio/sh010",
|
||||
"handleEnd": 10,
|
||||
"handleStart": 10,
|
||||
"has_promised_context": true,
|
||||
"heroTrack": true,
|
||||
"hierarchy": "shots/sq_old_otio",
|
||||
"hierarchyData": {
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"sequence": "sq_old_otio",
|
||||
"shot": "sh010",
|
||||
"track": "Video_1"
|
||||
},
|
||||
"id": "pyblish.avalon.instance",
|
||||
"instance_id": "24792946-9ac4-4c8d-922f-80a83dea4be1",
|
||||
"label": "/shots/sq_old_otio/sh010 shot",
|
||||
"newAssetPublishing": true,
|
||||
"newHierarchyIntegration": true,
|
||||
"parents": [
|
||||
{
|
||||
"entity_name": "shots",
|
||||
"folder_type": "folder"
|
||||
},
|
||||
{
|
||||
"entity_name": "sq_old_otio",
|
||||
"folder_type": "sequence"
|
||||
}
|
||||
],
|
||||
"productName": "shotMain",
|
||||
"productType": "shot",
|
||||
"publish_attributes": {
|
||||
"CollectSlackFamilies": {
|
||||
"additional_message": ""
|
||||
}
|
||||
},
|
||||
"reviewTrack": "Video 1",
|
||||
"sequence": "sq_old_otio",
|
||||
"shot": "sh###",
|
||||
"sourceResolution": false,
|
||||
"task": null,
|
||||
"track": "{_track_}",
|
||||
"uuid": "dec1a40b-7ce8-43cd-94b8-08a53056a171",
|
||||
"variant": "Main",
|
||||
"workfileFrameStart": 1001
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "AYONData",
|
||||
"color": "MINT",
|
||||
"marked_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 1.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 52.0
|
||||
}
|
||||
},
|
||||
"comment": ""
|
||||
}
|
||||
],
|
||||
"enabled": true,
|
||||
"media_references": {
|
||||
"DEFAULT_MEDIA": {
|
||||
"OTIO_SCHEMA": "ImageSequenceReference.1",
|
||||
"metadata": {
|
||||
"height": 720,
|
||||
"isSequence": true,
|
||||
"padding": 4,
|
||||
"pixelAspect": 1.0,
|
||||
"width": 956
|
||||
},
|
||||
"name": "",
|
||||
"available_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 101.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 1000.0
|
||||
}
|
||||
},
|
||||
"available_image_bounds": null,
|
||||
"target_url_base": "C:\\legacy\\",
|
||||
"name_prefix": "output.",
|
||||
"name_suffix": ".exr",
|
||||
"start_frame": 1000,
|
||||
"frame_step": 1,
|
||||
"rate": 24.0,
|
||||
"frame_zero_padding": 4,
|
||||
"missing_frame_policy": "error"
|
||||
}
|
||||
},
|
||||
"active_media_reference_key": "DEFAULT_MEDIA"
|
||||
}
|
||||
|
|
@ -0,0 +1,358 @@
|
|||
{
|
||||
"OTIO_SCHEMA": "Clip.2",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Link Group ID": 1
|
||||
}
|
||||
},
|
||||
"name": "simple_editorial_setup.mp4",
|
||||
"source_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 171.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 0.0
|
||||
}
|
||||
},
|
||||
"effects": [
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Transform",
|
||||
"Enabled": true,
|
||||
"Name": "Transform",
|
||||
"Parameters": [],
|
||||
"Type": 2
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Cropping",
|
||||
"Enabled": true,
|
||||
"Name": "Cropping",
|
||||
"Parameters": [],
|
||||
"Type": 3
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Dynamic Zoom",
|
||||
"Enabled": false,
|
||||
"Name": "Dynamic Zoom",
|
||||
"Parameters": [
|
||||
{
|
||||
"Default Parameter Value": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Key Frames": {
|
||||
"0": {
|
||||
"Value": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Variant Type": "POINTF"
|
||||
},
|
||||
"1000": {
|
||||
"Value": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Variant Type": "POINTF"
|
||||
}
|
||||
},
|
||||
"Parameter ID": "dynamicZoomCenter",
|
||||
"Parameter Value": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Variant Type": "POINTF"
|
||||
},
|
||||
{
|
||||
"Default Parameter Value": 1.0,
|
||||
"Key Frames": {
|
||||
"0": {
|
||||
"Value": 0.8,
|
||||
"Variant Type": "Double"
|
||||
},
|
||||
"1000": {
|
||||
"Value": 1.0,
|
||||
"Variant Type": "Double"
|
||||
}
|
||||
},
|
||||
"Parameter ID": "dynamicZoomScale",
|
||||
"Parameter Value": 1.0,
|
||||
"Variant Type": "Double",
|
||||
"maxValue": 100.0,
|
||||
"minValue": 0.01
|
||||
}
|
||||
],
|
||||
"Type": 59
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Composite",
|
||||
"Enabled": true,
|
||||
"Name": "Composite",
|
||||
"Parameters": [],
|
||||
"Type": 1
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Lens Correction",
|
||||
"Enabled": true,
|
||||
"Name": "Lens Correction",
|
||||
"Parameters": [],
|
||||
"Type": 43
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Retime and Scaling",
|
||||
"Enabled": true,
|
||||
"Name": "Retime and Scaling",
|
||||
"Parameters": [],
|
||||
"Type": 22
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Video Faders",
|
||||
"Enabled": true,
|
||||
"Name": "Video Faders",
|
||||
"Parameters": [],
|
||||
"Type": 36
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
}
|
||||
],
|
||||
"markers": [
|
||||
{
|
||||
"OTIO_SCHEMA": "Marker.2",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Keywords": [],
|
||||
"Note": "{\"resolve_sub_products\": {\"io.ayon.creators.resolve.shot\": {\"id\": \"pyblish.avalon.instance\", \"productType\": \"shot\", \"productName\": \"shotMain\", \"active\": true, \"creator_identifier\": \"io.ayon.creators.resolve.shot\", \"variant\": \"Main\", \"folderPath\": \"/shots/sq01/Video_1sq01sh010\", \"task\": \"Generic\", \"clip_variant\": \"<track_name>\", \"clip_index\": \"cef0267f-bbf4-4959-9f22-d225e03f2532\", \"clip_source_resolution\": {\"width\": \"640\", \"height\": \"360\", \"pixelAspect\": 1.0}, \"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"sq01\", \"track\": \"{_track_}\", \"shot\": \"sh###\", \"hierarchy\": \"shots/sq01\", \"sourceResolution\": false, \"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"parents\": [{\"folder_type\": \"folder\", \"entity_name\": \"shots\"}, {\"folder_type\": \"sequence\", \"entity_name\": \"sq01\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"sq01\", \"track\": \"Video_1\", \"shot\": \"sh010\"}, \"heroTrack\": true, \"uuid\": \"3af1b00a-5625-468d-af17-8ed29fa8608a\", \"reviewTrack\": null, \"label\": \"/shots/sq01/Video_1sq01sh010 shot\", \"has_promised_context\": true, \"newHierarchyIntegration\": true, \"newAssetPublishing\": true, \"instance_id\": \"b6e33343-2410-4de4-935e-724bc74301e1\", \"creator_attributes\": {\"folderPath\": \"/shots/sq01/Video_1sq01sh010\", \"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"frameStart\": 1001, \"frameEnd\": 1172, \"clipIn\": 1097, \"clipOut\": 1268, \"clipDuration\": 171, \"sourceIn\": 0, \"sourceOut\": 171, \"fps\": \"from_selection\"}, \"publish_attributes\": {\"CollectSlackFamilies\": {\"additional_message\": \"\"}}}, \"io.ayon.creators.resolve.plate\": {\"id\": \"pyblish.avalon.instance\", \"productType\": \"plate\", \"productName\": \"plateVideo_1\", \"active\": true, \"creator_identifier\": \"io.ayon.creators.resolve.plate\", \"variant\": \"Video_1\", \"folderPath\": \"/shots/sq01/Video_1sq01sh010\", \"task\": \"Generic\", \"clip_variant\": \"<track_name>\", \"clip_index\": \"cef0267f-bbf4-4959-9f22-d225e03f2532\", \"clip_source_resolution\": {\"width\": \"640\", \"height\": \"360\", \"pixelAspect\": 1.0}, \"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"sq01\", \"track\": \"{_track_}\", \"shot\": \"sh###\", \"hierarchy\": \"shots/sq01\", \"sourceResolution\": false, \"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"parents\": [{\"folder_type\": \"folder\", \"entity_name\": \"shots\"}, {\"folder_type\": \"sequence\", \"entity_name\": \"sq01\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"sq01\", \"track\": \"Video_1\", \"shot\": \"sh010\"}, \"heroTrack\": true, \"uuid\": \"3af1b00a-5625-468d-af17-8ed29fa8608a\", \"reviewTrack\": null, \"parent_instance_id\": \"b6e33343-2410-4de4-935e-724bc74301e1\", \"label\": \"/shots/sq01/Video_1sq01sh010 plate\", \"has_promised_context\": true, \"newHierarchyIntegration\": true, \"newAssetPublishing\": true, \"instance_id\": \"ef8fe238-c970-4a16-be67-76f446113c4b\", \"creator_attributes\": {\"parentInstance\": \"/shots/sq01/Video_1sq01sh010 shot\", \"vSyncOn\": false, \"vSyncTrack\": \"Video 1\"}, \"publish_attributes\": {\"CollectSlackFamilies\": {\"additional_message\": \"\"}}}}, \"clip_index\": \"cef0267f-bbf4-4959-9f22-d225e03f2532\", \"publish\": true}"
|
||||
},
|
||||
"clip_index": "cef0267f-bbf4-4959-9f22-d225e03f2532",
|
||||
"publish": true,
|
||||
"resolve_sub_products": {
|
||||
"io.ayon.creators.resolve.plate": {
|
||||
"active": true,
|
||||
"clip_index": "cef0267f-bbf4-4959-9f22-d225e03f2532",
|
||||
"clip_source_resolution": {
|
||||
"height": "360",
|
||||
"pixelAspect": 1.0,
|
||||
"width": "640"
|
||||
},
|
||||
"clip_variant": "<track_name>",
|
||||
"creator_attributes": {
|
||||
"parentInstance": "/shots/sq01/Video_1sq01sh010 shot",
|
||||
"vSyncOn": false,
|
||||
"vSyncTrack": "Video 1"
|
||||
},
|
||||
"creator_identifier": "io.ayon.creators.resolve.plate",
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"folderPath": "/shots/sq01/Video_1sq01sh010",
|
||||
"handleEnd": 10,
|
||||
"handleStart": 10,
|
||||
"has_promised_context": true,
|
||||
"heroTrack": true,
|
||||
"hierarchy": "shots/sq01",
|
||||
"hierarchyData": {
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"sequence": "sq01",
|
||||
"shot": "sh010",
|
||||
"track": "Video_1"
|
||||
},
|
||||
"id": "pyblish.avalon.instance",
|
||||
"instance_id": "ef8fe238-c970-4a16-be67-76f446113c4b",
|
||||
"label": "/shots/sq01/Video_1sq01sh010 plate",
|
||||
"newAssetPublishing": true,
|
||||
"newHierarchyIntegration": true,
|
||||
"parent_instance_id": "b6e33343-2410-4de4-935e-724bc74301e1",
|
||||
"parents": [
|
||||
{
|
||||
"entity_name": "shots",
|
||||
"folder_type": "folder"
|
||||
},
|
||||
{
|
||||
"entity_name": "sq01",
|
||||
"folder_type": "sequence"
|
||||
}
|
||||
],
|
||||
"productName": "plateVideo_1",
|
||||
"productType": "plate",
|
||||
"publish_attributes": {
|
||||
"CollectSlackFamilies": {
|
||||
"additional_message": ""
|
||||
}
|
||||
},
|
||||
"reviewTrack": null,
|
||||
"sequence": "sq01",
|
||||
"shot": "sh###",
|
||||
"sourceResolution": false,
|
||||
"task": "Generic",
|
||||
"track": "{_track_}",
|
||||
"uuid": "3af1b00a-5625-468d-af17-8ed29fa8608a",
|
||||
"variant": "Video_1",
|
||||
"workfileFrameStart": 1001
|
||||
},
|
||||
"io.ayon.creators.resolve.shot": {
|
||||
"active": true,
|
||||
"clip_index": "cef0267f-bbf4-4959-9f22-d225e03f2532",
|
||||
"clip_source_resolution": {
|
||||
"height": "360",
|
||||
"pixelAspect": 1.0,
|
||||
"width": "640"
|
||||
},
|
||||
"clip_variant": "<track_name>",
|
||||
"creator_attributes": {
|
||||
"clipDuration": 171,
|
||||
"clipIn": 1097,
|
||||
"clipOut": 1268,
|
||||
"folderPath": "/shots/sq01/Video_1sq01sh010",
|
||||
"fps": "from_selection",
|
||||
"frameEnd": 1172,
|
||||
"frameStart": 1001,
|
||||
"handleEnd": 10,
|
||||
"handleStart": 10,
|
||||
"sourceIn": 0,
|
||||
"sourceOut": 171,
|
||||
"workfileFrameStart": 1001
|
||||
},
|
||||
"creator_identifier": "io.ayon.creators.resolve.shot",
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"folderPath": "/shots/sq01/Video_1sq01sh010",
|
||||
"handleEnd": 10,
|
||||
"handleStart": 10,
|
||||
"has_promised_context": true,
|
||||
"heroTrack": true,
|
||||
"hierarchy": "shots/sq01",
|
||||
"hierarchyData": {
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"sequence": "sq01",
|
||||
"shot": "sh010",
|
||||
"track": "Video_1"
|
||||
},
|
||||
"id": "pyblish.avalon.instance",
|
||||
"instance_id": "b6e33343-2410-4de4-935e-724bc74301e1",
|
||||
"label": "/shots/sq01/Video_1sq01sh010 shot",
|
||||
"newAssetPublishing": true,
|
||||
"newHierarchyIntegration": true,
|
||||
"parents": [
|
||||
{
|
||||
"entity_name": "shots",
|
||||
"folder_type": "folder"
|
||||
},
|
||||
{
|
||||
"entity_name": "sq01",
|
||||
"folder_type": "sequence"
|
||||
}
|
||||
],
|
||||
"productName": "shotMain",
|
||||
"productType": "shot",
|
||||
"publish_attributes": {
|
||||
"CollectSlackFamilies": {
|
||||
"additional_message": ""
|
||||
}
|
||||
},
|
||||
"reviewTrack": null,
|
||||
"sequence": "sq01",
|
||||
"shot": "sh###",
|
||||
"sourceResolution": false,
|
||||
"task": "Generic",
|
||||
"track": "{_track_}",
|
||||
"uuid": "3af1b00a-5625-468d-af17-8ed29fa8608a",
|
||||
"variant": "Main",
|
||||
"workfileFrameStart": 1001
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "AyonData",
|
||||
"color": "GREEN",
|
||||
"marked_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 1.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 85.0
|
||||
}
|
||||
},
|
||||
"comment": ""
|
||||
}
|
||||
],
|
||||
"enabled": true,
|
||||
"media_references": {
|
||||
"DEFAULT_MEDIA": {
|
||||
"OTIO_SCHEMA": "ExternalReference.1",
|
||||
"metadata": {},
|
||||
"name": "simple_editorial_setup.mp4",
|
||||
"available_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 16450.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 0.0
|
||||
}
|
||||
},
|
||||
"available_image_bounds": null,
|
||||
"target_url": "C:\\Users\\robin\\OneDrive\\Bureau\\dev_ayon\\data\\simple_editorial_setup.mp4"
|
||||
}
|
||||
},
|
||||
"active_media_reference_key": "DEFAULT_MEDIA"
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,289 @@
|
|||
{
|
||||
"OTIO_SCHEMA": "Track.1",
|
||||
"metadata": {},
|
||||
"name": "Video 2",
|
||||
"source_range": null,
|
||||
"effects": [],
|
||||
"markers": [],
|
||||
"enabled": true,
|
||||
"children": [
|
||||
{
|
||||
"OTIO_SCHEMA": "Gap.1",
|
||||
"metadata": {},
|
||||
"name": "",
|
||||
"source_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 2.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 0.0
|
||||
}
|
||||
},
|
||||
"effects": [],
|
||||
"markers": [],
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Clip.2",
|
||||
"metadata": {},
|
||||
"name": "output",
|
||||
"source_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 88.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 0.0
|
||||
}
|
||||
},
|
||||
"effects": [],
|
||||
"markers": [],
|
||||
"enabled": true,
|
||||
"media_references": {
|
||||
"DEFAULT_MEDIA": {
|
||||
"OTIO_SCHEMA": "ImageSequenceReference.1",
|
||||
"metadata": {
|
||||
"clip.properties.blendfunc": "0",
|
||||
"clip.properties.colourspacename": "default ()",
|
||||
"clip.properties.domainroot": "",
|
||||
"clip.properties.enabled": "1",
|
||||
"clip.properties.expanded": "1",
|
||||
"clip.properties.opacity": "1",
|
||||
"clip.properties.valuesource": "",
|
||||
"foundry.source.audio": "",
|
||||
"foundry.source.bitmapsize": "0",
|
||||
"foundry.source.bitsperchannel": "0",
|
||||
"foundry.source.channelformat": "integer",
|
||||
"foundry.source.colourtransform": "scene_linear",
|
||||
"foundry.source.duration": "101",
|
||||
"foundry.source.filename": "output.%04d.exr 1000-1100",
|
||||
"foundry.source.filesize": "",
|
||||
"foundry.source.fragments": "101",
|
||||
"foundry.source.framerate": "24",
|
||||
"foundry.source.fullpath": "",
|
||||
"foundry.source.height": "1080",
|
||||
"foundry.source.layers": "colour",
|
||||
"foundry.source.pixelAspect": "1",
|
||||
"foundry.source.pixelAspectRatio": "",
|
||||
"foundry.source.pixelformat": "RGBA (Float16) Open Color IO space: 7",
|
||||
"foundry.source.reelID": "",
|
||||
"foundry.source.resolution": "",
|
||||
"foundry.source.samplerate": "Invalid",
|
||||
"foundry.source.shortfilename": "output.%04d.exr 1000-1100",
|
||||
"foundry.source.shot": "",
|
||||
"foundry.source.shotDate": "",
|
||||
"foundry.source.startTC": "",
|
||||
"foundry.source.starttime": "1000",
|
||||
"foundry.source.timecode": "87399",
|
||||
"foundry.source.umid": "bbfe0c90-5b76-424a-6351-4dac36a8dde7",
|
||||
"foundry.source.umidOriginator": "foundry.source.umid",
|
||||
"foundry.source.width": "1920",
|
||||
"foundry.timeline.autodiskcachemode": "Manual",
|
||||
"foundry.timeline.colorSpace": "scene_linear",
|
||||
"foundry.timeline.duration": "101",
|
||||
"foundry.timeline.framerate": "24",
|
||||
"foundry.timeline.outputformat": "",
|
||||
"foundry.timeline.poster": "0",
|
||||
"foundry.timeline.posterLayer": "colour",
|
||||
"foundry.timeline.readParams": "AAAAAQAAAAAAAAAFAAAACmNvbG9yc3BhY2UAAAAFaW50MzIAAAAAAAAAC2VkZ2VfcGl4ZWxzAAAABWludDMyAAAAAAAAABFpZ25vcmVfcGFydF9uYW1lcwAAAARib29sAAAAAAhub3ByZWZpeAAAAARib29sAAAAAB5vZmZzZXRfbmVnYXRpdmVfZGlzcGxheV93aW5kb3cAAAAEYm9vbAE=",
|
||||
"foundry.timeline.samplerate": "Invalid",
|
||||
"isSequence": true,
|
||||
"media.exr.channels": "B:{1 0 1 1},G:{1 0 1 1},R:{1 0 1 1}",
|
||||
"media.exr.compression": "2",
|
||||
"media.exr.compressionName": "Zip (1 scanline)",
|
||||
"media.exr.dataWindow": "1,1,1918,1078",
|
||||
"media.exr.displayWindow": "0,0,1919,1079",
|
||||
"media.exr.lineOrder": "0",
|
||||
"media.exr.nuke.input.frame_rate": "24",
|
||||
"media.exr.nuke.input.timecode": "01:00:41:15",
|
||||
"media.exr.pixelAspectRatio": "1",
|
||||
"media.exr.screenWindowCenter": "0,0",
|
||||
"media.exr.screenWindowWidth": "1",
|
||||
"media.exr.type": "scanlineimage",
|
||||
"media.exr.version": "1",
|
||||
"media.input.bitsperchannel": "16-bit half float",
|
||||
"media.input.ctime": "2024-09-23 08:37:23",
|
||||
"media.input.filereader": "exr",
|
||||
"media.input.filesize": "1095868",
|
||||
"media.input.frame": "1",
|
||||
"media.input.frame_rate": "24",
|
||||
"media.input.height": "1080",
|
||||
"media.input.mtime": "2024-09-23 08:37:23",
|
||||
"media.input.timecode": "01:00:41:15",
|
||||
"media.input.width": "1920",
|
||||
"media.nuke.full_layer_names": "0",
|
||||
"media.nuke.node_hash": "9b",
|
||||
"media.nuke.version": "15.1v2",
|
||||
"openpype.source.colourtransform": "scene_linear",
|
||||
"openpype.source.height": 1080,
|
||||
"openpype.source.pixelAspect": 1.0,
|
||||
"openpype.source.width": 1920,
|
||||
"padding": 4
|
||||
},
|
||||
"name": "",
|
||||
"available_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 101.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 1000.0
|
||||
}
|
||||
},
|
||||
"available_image_bounds": null,
|
||||
"target_url_base": "C:\\with_tc",
|
||||
"name_prefix": "output.",
|
||||
"name_suffix": ".exr",
|
||||
"start_frame": 1000,
|
||||
"frame_step": 1,
|
||||
"rate": 24.0,
|
||||
"frame_zero_padding": 4,
|
||||
"missing_frame_policy": "error"
|
||||
}
|
||||
},
|
||||
"active_media_reference_key": "DEFAULT_MEDIA"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Clip.2",
|
||||
"metadata": {},
|
||||
"name": "output",
|
||||
"source_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 11.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 0.0
|
||||
}
|
||||
},
|
||||
"effects": [],
|
||||
"markers": [],
|
||||
"enabled": true,
|
||||
"media_references": {
|
||||
"DEFAULT_MEDIA": {
|
||||
"OTIO_SCHEMA": "ImageSequenceReference.1",
|
||||
"metadata": {
|
||||
"clip.properties.blendfunc": "0",
|
||||
"clip.properties.colourspacename": "default ()",
|
||||
"clip.properties.domainroot": "",
|
||||
"clip.properties.enabled": "1",
|
||||
"clip.properties.expanded": "1",
|
||||
"clip.properties.opacity": "1",
|
||||
"clip.properties.valuesource": "",
|
||||
"foundry.source.audio": "",
|
||||
"foundry.source.bitmapsize": "0",
|
||||
"foundry.source.bitsperchannel": "0",
|
||||
"foundry.source.channelformat": "integer",
|
||||
"foundry.source.colourtransform": "scene_linear",
|
||||
"foundry.source.duration": "101",
|
||||
"foundry.source.filename": "output.%04d.exr 1000-1100",
|
||||
"foundry.source.filesize": "",
|
||||
"foundry.source.fragments": "101",
|
||||
"foundry.source.framerate": "24",
|
||||
"foundry.source.fullpath": "",
|
||||
"foundry.source.height": "1080",
|
||||
"foundry.source.layers": "colour",
|
||||
"foundry.source.pixelAspect": "1",
|
||||
"foundry.source.pixelAspectRatio": "",
|
||||
"foundry.source.pixelformat": "RGBA (Float16) Open Color IO space: 7",
|
||||
"foundry.source.reelID": "",
|
||||
"foundry.source.resolution": "",
|
||||
"foundry.source.samplerate": "Invalid",
|
||||
"foundry.source.shortfilename": "output.%04d.exr 1000-1100",
|
||||
"foundry.source.shot": "",
|
||||
"foundry.source.shotDate": "",
|
||||
"foundry.source.startTC": "",
|
||||
"foundry.source.starttime": "1000",
|
||||
"foundry.source.timecode": "87399",
|
||||
"foundry.source.umid": "bbfe0c90-5b76-424a-6351-4dac36a8dde7",
|
||||
"foundry.source.umidOriginator": "foundry.source.umid",
|
||||
"foundry.source.width": "1920",
|
||||
"foundry.timeline.autodiskcachemode": "Manual",
|
||||
"foundry.timeline.colorSpace": "scene_linear",
|
||||
"foundry.timeline.duration": "101",
|
||||
"foundry.timeline.framerate": "24",
|
||||
"foundry.timeline.outputformat": "",
|
||||
"foundry.timeline.poster": "0",
|
||||
"foundry.timeline.posterLayer": "colour",
|
||||
"foundry.timeline.readParams": "AAAAAQAAAAAAAAAFAAAACmNvbG9yc3BhY2UAAAAFaW50MzIAAAAAAAAAC2VkZ2VfcGl4ZWxzAAAABWludDMyAAAAAAAAABFpZ25vcmVfcGFydF9uYW1lcwAAAARib29sAAAAAAhub3ByZWZpeAAAAARib29sAAAAAB5vZmZzZXRfbmVnYXRpdmVfZGlzcGxheV93aW5kb3cAAAAEYm9vbAE=",
|
||||
"foundry.timeline.samplerate": "Invalid",
|
||||
"isSequence": true,
|
||||
"media.exr.channels": "B:{1 0 1 1},G:{1 0 1 1},R:{1 0 1 1}",
|
||||
"media.exr.compression": "2",
|
||||
"media.exr.compressionName": "Zip (1 scanline)",
|
||||
"media.exr.dataWindow": "1,1,1918,1078",
|
||||
"media.exr.displayWindow": "0,0,1919,1079",
|
||||
"media.exr.lineOrder": "0",
|
||||
"media.exr.nuke.input.frame_rate": "24",
|
||||
"media.exr.nuke.input.timecode": "01:00:41:15",
|
||||
"media.exr.pixelAspectRatio": "1",
|
||||
"media.exr.screenWindowCenter": "0,0",
|
||||
"media.exr.screenWindowWidth": "1",
|
||||
"media.exr.type": "scanlineimage",
|
||||
"media.exr.version": "1",
|
||||
"media.input.bitsperchannel": "16-bit half float",
|
||||
"media.input.ctime": "2024-09-23 08:37:23",
|
||||
"media.input.filereader": "exr",
|
||||
"media.input.filesize": "1095868",
|
||||
"media.input.frame": "1",
|
||||
"media.input.frame_rate": "24",
|
||||
"media.input.height": "1080",
|
||||
"media.input.mtime": "2024-09-23 08:37:23",
|
||||
"media.input.timecode": "01:00:41:15",
|
||||
"media.input.width": "1920",
|
||||
"media.nuke.full_layer_names": "0",
|
||||
"media.nuke.node_hash": "9b",
|
||||
"media.nuke.version": "15.1v2",
|
||||
"openpype.source.colourtransform": "scene_linear",
|
||||
"openpype.source.height": 1080,
|
||||
"openpype.source.pixelAspect": 1.0,
|
||||
"openpype.source.width": 1920,
|
||||
"padding": 4
|
||||
},
|
||||
"name": "",
|
||||
"available_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 101.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 1000.0
|
||||
}
|
||||
},
|
||||
"available_image_bounds": null,
|
||||
"target_url_base": "C:\\with_tc",
|
||||
"name_prefix": "output.",
|
||||
"name_suffix": ".exr",
|
||||
"start_frame": 1000,
|
||||
"frame_step": 1,
|
||||
"rate": 24.0,
|
||||
"frame_zero_padding": 4,
|
||||
"missing_frame_policy": "error"
|
||||
}
|
||||
},
|
||||
"active_media_reference_key": "DEFAULT_MEDIA"
|
||||
}
|
||||
],
|
||||
"kind": "Video"
|
||||
}
|
||||
|
|
@ -0,0 +1,356 @@
|
|||
{
|
||||
"OTIO_SCHEMA": "Clip.2",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {}
|
||||
},
|
||||
"name": "qt_embedded_tc.mov",
|
||||
"source_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 44.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 90032.0
|
||||
}
|
||||
},
|
||||
"effects": [
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Transform",
|
||||
"Enabled": true,
|
||||
"Name": "Transform",
|
||||
"Parameters": [],
|
||||
"Type": 2
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Cropping",
|
||||
"Enabled": true,
|
||||
"Name": "Cropping",
|
||||
"Parameters": [],
|
||||
"Type": 3
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Dynamic Zoom",
|
||||
"Enabled": false,
|
||||
"Name": "Dynamic Zoom",
|
||||
"Parameters": [
|
||||
{
|
||||
"Default Parameter Value": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Key Frames": {
|
||||
"-32": {
|
||||
"Value": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Variant Type": "POINTF"
|
||||
},
|
||||
"1009": {
|
||||
"Value": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Variant Type": "POINTF"
|
||||
}
|
||||
},
|
||||
"Parameter ID": "dynamicZoomCenter",
|
||||
"Parameter Value": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Variant Type": "POINTF"
|
||||
},
|
||||
{
|
||||
"Default Parameter Value": 1.0,
|
||||
"Key Frames": {
|
||||
"-32": {
|
||||
"Value": 0.8,
|
||||
"Variant Type": "Double"
|
||||
},
|
||||
"1009": {
|
||||
"Value": 1.0,
|
||||
"Variant Type": "Double"
|
||||
}
|
||||
},
|
||||
"Parameter ID": "dynamicZoomScale",
|
||||
"Parameter Value": 1.0,
|
||||
"Variant Type": "Double",
|
||||
"maxValue": 100.0,
|
||||
"minValue": 0.01
|
||||
}
|
||||
],
|
||||
"Type": 59
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Composite",
|
||||
"Enabled": true,
|
||||
"Name": "Composite",
|
||||
"Parameters": [],
|
||||
"Type": 1
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Lens Correction",
|
||||
"Enabled": true,
|
||||
"Name": "Lens Correction",
|
||||
"Parameters": [],
|
||||
"Type": 43
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Retime and Scaling",
|
||||
"Enabled": true,
|
||||
"Name": "Retime and Scaling",
|
||||
"Parameters": [],
|
||||
"Type": 22
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Video Faders",
|
||||
"Enabled": true,
|
||||
"Name": "Video Faders",
|
||||
"Parameters": [],
|
||||
"Type": 36
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
}
|
||||
],
|
||||
"markers": [
|
||||
{
|
||||
"OTIO_SCHEMA": "Marker.2",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Keywords": [],
|
||||
"Note": "{\"resolve_sub_products\": {\"io.ayon.creators.resolve.shot\": {\"id\": \"pyblish.avalon.instance\", \"productType\": \"shot\", \"productName\": \"shotMain\", \"active\": true, \"creator_identifier\": \"io.ayon.creators.resolve.shot\", \"variant\": \"Main\", \"folderPath\": \"/shots/sq01/Video_1sq01sh010\", \"task\": \"Generic\", \"clip_variant\": \"<track_name>\", \"clip_index\": \"297fbf7a-7636-44b5-a308-809098298fae\", \"clip_source_resolution\": {\"width\": \"956\", \"height\": \"720\", \"pixelAspect\": 1.0}, \"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"sq01\", \"track\": \"{_track_}\", \"shot\": \"sh###\", \"hierarchy\": \"shots/sq01\", \"sourceResolution\": false, \"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"parents\": [{\"folder_type\": \"folder\", \"entity_name\": \"shots\"}, {\"folder_type\": \"sequence\", \"entity_name\": \"sq01\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"sq01\", \"track\": \"Video_1\", \"shot\": \"sh010\"}, \"heroTrack\": true, \"uuid\": \"3e459c3f-cc87-42c6-95c0-f11435ec8ace\", \"reviewTrack\": null, \"label\": \"/shots/sq01/Video_1sq01sh010 shot\", \"has_promised_context\": true, \"newHierarchyIntegration\": true, \"newAssetPublishing\": true, \"instance_id\": \"acebdee4-5f4a-4ebd-8c22-6ef2725c2070\", \"creator_attributes\": {\"folderPath\": \"/shots/sq01/Video_1sq01sh010\", \"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"frameStart\": 1001, \"frameEnd\": 1045, \"clipIn\": 509, \"clipOut\": 553, \"clipDuration\": 44, \"sourceIn\": 32, \"sourceOut\": 76, \"fps\": \"from_selection\"}, \"publish_attributes\": {\"CollectSlackFamilies\": {\"additional_message\": \"\"}}}, \"io.ayon.creators.resolve.plate\": {\"id\": \"pyblish.avalon.instance\", \"productType\": \"plate\", \"productName\": \"plateVideo_1\", \"active\": true, \"creator_identifier\": \"io.ayon.creators.resolve.plate\", \"variant\": \"Video_1\", \"folderPath\": \"/shots/sq01/Video_1sq01sh010\", \"task\": \"Generic\", \"clip_variant\": \"<track_name>\", \"clip_index\": \"297fbf7a-7636-44b5-a308-809098298fae\", \"clip_source_resolution\": {\"width\": \"956\", \"height\": \"720\", \"pixelAspect\": 1.0}, \"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"sq01\", \"track\": \"{_track_}\", \"shot\": \"sh###\", \"hierarchy\": \"shots/sq01\", \"sourceResolution\": false, \"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"parents\": [{\"folder_type\": \"folder\", \"entity_name\": \"shots\"}, {\"folder_type\": \"sequence\", \"entity_name\": \"sq01\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"sq01\", \"track\": \"Video_1\", \"shot\": \"sh010\"}, \"heroTrack\": true, \"uuid\": \"3e459c3f-cc87-42c6-95c0-f11435ec8ace\", \"reviewTrack\": null, \"parent_instance_id\": \"acebdee4-5f4a-4ebd-8c22-6ef2725c2070\", \"label\": \"/shots/sq01/Video_1sq01sh010 plate\", \"has_promised_context\": true, \"newHierarchyIntegration\": true, \"newAssetPublishing\": true, \"instance_id\": \"ffd09d3c-227c-4be0-8788-dec30daf7f78\", \"creator_attributes\": {\"parentInstance\": \"/shots/sq01/Video_1sq01sh010 shot\", \"vSyncOn\": false, \"vSyncTrack\": \"Video 1\"}, \"publish_attributes\": {\"CollectSlackFamilies\": {\"additional_message\": \"\"}}}}, \"clip_index\": \"297fbf7a-7636-44b5-a308-809098298fae\", \"publish\": true}"
|
||||
},
|
||||
"clip_index": "297fbf7a-7636-44b5-a308-809098298fae",
|
||||
"publish": true,
|
||||
"resolve_sub_products": {
|
||||
"io.ayon.creators.resolve.plate": {
|
||||
"active": true,
|
||||
"clip_index": "297fbf7a-7636-44b5-a308-809098298fae",
|
||||
"clip_source_resolution": {
|
||||
"height": "720",
|
||||
"pixelAspect": 1.0,
|
||||
"width": "956"
|
||||
},
|
||||
"clip_variant": "<track_name>",
|
||||
"creator_attributes": {
|
||||
"parentInstance": "/shots/sq01/Video_1sq01sh010 shot",
|
||||
"vSyncOn": false,
|
||||
"vSyncTrack": "Video 1"
|
||||
},
|
||||
"creator_identifier": "io.ayon.creators.resolve.plate",
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"folderPath": "/shots/sq01/Video_1sq01sh010",
|
||||
"handleEnd": 10,
|
||||
"handleStart": 10,
|
||||
"has_promised_context": true,
|
||||
"heroTrack": true,
|
||||
"hierarchy": "shots/sq01",
|
||||
"hierarchyData": {
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"sequence": "sq01",
|
||||
"shot": "sh010",
|
||||
"track": "Video_1"
|
||||
},
|
||||
"id": "pyblish.avalon.instance",
|
||||
"instance_id": "ffd09d3c-227c-4be0-8788-dec30daf7f78",
|
||||
"label": "/shots/sq01/Video_1sq01sh010 plate",
|
||||
"newAssetPublishing": true,
|
||||
"newHierarchyIntegration": true,
|
||||
"parent_instance_id": "acebdee4-5f4a-4ebd-8c22-6ef2725c2070",
|
||||
"parents": [
|
||||
{
|
||||
"entity_name": "shots",
|
||||
"folder_type": "folder"
|
||||
},
|
||||
{
|
||||
"entity_name": "sq01",
|
||||
"folder_type": "sequence"
|
||||
}
|
||||
],
|
||||
"productName": "plateVideo_1",
|
||||
"productType": "plate",
|
||||
"publish_attributes": {
|
||||
"CollectSlackFamilies": {
|
||||
"additional_message": ""
|
||||
}
|
||||
},
|
||||
"reviewTrack": null,
|
||||
"sequence": "sq01",
|
||||
"shot": "sh###",
|
||||
"sourceResolution": false,
|
||||
"task": "Generic",
|
||||
"track": "{_track_}",
|
||||
"uuid": "3e459c3f-cc87-42c6-95c0-f11435ec8ace",
|
||||
"variant": "Video_1",
|
||||
"workfileFrameStart": 1001
|
||||
},
|
||||
"io.ayon.creators.resolve.shot": {
|
||||
"active": true,
|
||||
"clip_index": "297fbf7a-7636-44b5-a308-809098298fae",
|
||||
"clip_source_resolution": {
|
||||
"height": "720",
|
||||
"pixelAspect": 1.0,
|
||||
"width": "956"
|
||||
},
|
||||
"clip_variant": "<track_name>",
|
||||
"creator_attributes": {
|
||||
"clipDuration": 44,
|
||||
"clipIn": 509,
|
||||
"clipOut": 553,
|
||||
"folderPath": "/shots/sq01/Video_1sq01sh010",
|
||||
"fps": "from_selection",
|
||||
"frameEnd": 1045,
|
||||
"frameStart": 1001,
|
||||
"handleEnd": 10,
|
||||
"handleStart": 10,
|
||||
"sourceIn": 32,
|
||||
"sourceOut": 76,
|
||||
"workfileFrameStart": 1001
|
||||
},
|
||||
"creator_identifier": "io.ayon.creators.resolve.shot",
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"folderPath": "/shots/sq01/Video_1sq01sh010",
|
||||
"handleEnd": 10,
|
||||
"handleStart": 10,
|
||||
"has_promised_context": true,
|
||||
"heroTrack": true,
|
||||
"hierarchy": "shots/sq01",
|
||||
"hierarchyData": {
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"sequence": "sq01",
|
||||
"shot": "sh010",
|
||||
"track": "Video_1"
|
||||
},
|
||||
"id": "pyblish.avalon.instance",
|
||||
"instance_id": "acebdee4-5f4a-4ebd-8c22-6ef2725c2070",
|
||||
"label": "/shots/sq01/Video_1sq01sh010 shot",
|
||||
"newAssetPublishing": true,
|
||||
"newHierarchyIntegration": true,
|
||||
"parents": [
|
||||
{
|
||||
"entity_name": "shots",
|
||||
"folder_type": "folder"
|
||||
},
|
||||
{
|
||||
"entity_name": "sq01",
|
||||
"folder_type": "sequence"
|
||||
}
|
||||
],
|
||||
"productName": "shotMain",
|
||||
"productType": "shot",
|
||||
"publish_attributes": {
|
||||
"CollectSlackFamilies": {
|
||||
"additional_message": ""
|
||||
}
|
||||
},
|
||||
"reviewTrack": null,
|
||||
"sequence": "sq01",
|
||||
"shot": "sh###",
|
||||
"sourceResolution": false,
|
||||
"task": "Generic",
|
||||
"track": "{_track_}",
|
||||
"uuid": "3e459c3f-cc87-42c6-95c0-f11435ec8ace",
|
||||
"variant": "Main",
|
||||
"workfileFrameStart": 1001
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "AyonData",
|
||||
"color": "GREEN",
|
||||
"marked_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 1.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 90054.0
|
||||
}
|
||||
},
|
||||
"comment": ""
|
||||
}
|
||||
],
|
||||
"enabled": true,
|
||||
"media_references": {
|
||||
"DEFAULT_MEDIA": {
|
||||
"OTIO_SCHEMA": "ExternalReference.1",
|
||||
"metadata": {},
|
||||
"name": "qt_embedded_tc.mov",
|
||||
"available_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 100.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 86400.0
|
||||
}
|
||||
},
|
||||
"available_image_bounds": null,
|
||||
"target_url": "C:\\Users\\robin\\OneDrive\\Bureau\\dev_ayon\\data\\qt_embedded_tc.mov"
|
||||
}
|
||||
},
|
||||
"active_media_reference_key": "DEFAULT_MEDIA"
|
||||
}
|
||||
|
|
@ -0,0 +1,356 @@
|
|||
{
|
||||
"OTIO_SCHEMA": "Clip.2",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {}
|
||||
},
|
||||
"name": "qt_embedded_tc.mov",
|
||||
"source_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 68.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 86414.0
|
||||
}
|
||||
},
|
||||
"effects": [
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Transform",
|
||||
"Enabled": true,
|
||||
"Name": "Transform",
|
||||
"Parameters": [],
|
||||
"Type": 2
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Cropping",
|
||||
"Enabled": true,
|
||||
"Name": "Cropping",
|
||||
"Parameters": [],
|
||||
"Type": 3
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Dynamic Zoom",
|
||||
"Enabled": false,
|
||||
"Name": "Dynamic Zoom",
|
||||
"Parameters": [
|
||||
{
|
||||
"Default Parameter Value": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Key Frames": {
|
||||
"-14": {
|
||||
"Value": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Variant Type": "POINTF"
|
||||
},
|
||||
"986": {
|
||||
"Value": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Variant Type": "POINTF"
|
||||
}
|
||||
},
|
||||
"Parameter ID": "dynamicZoomCenter",
|
||||
"Parameter Value": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Variant Type": "POINTF"
|
||||
},
|
||||
{
|
||||
"Default Parameter Value": 1.0,
|
||||
"Key Frames": {
|
||||
"-14": {
|
||||
"Value": 0.8,
|
||||
"Variant Type": "Double"
|
||||
},
|
||||
"986": {
|
||||
"Value": 1.0,
|
||||
"Variant Type": "Double"
|
||||
}
|
||||
},
|
||||
"Parameter ID": "dynamicZoomScale",
|
||||
"Parameter Value": 1.0,
|
||||
"Variant Type": "Double",
|
||||
"maxValue": 100.0,
|
||||
"minValue": 0.01
|
||||
}
|
||||
],
|
||||
"Type": 59
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Composite",
|
||||
"Enabled": true,
|
||||
"Name": "Composite",
|
||||
"Parameters": [],
|
||||
"Type": 1
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Lens Correction",
|
||||
"Enabled": true,
|
||||
"Name": "Lens Correction",
|
||||
"Parameters": [],
|
||||
"Type": 43
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Retime and Scaling",
|
||||
"Enabled": true,
|
||||
"Name": "Retime and Scaling",
|
||||
"Parameters": [],
|
||||
"Type": 22
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Video Faders",
|
||||
"Enabled": true,
|
||||
"Name": "Video Faders",
|
||||
"Parameters": [],
|
||||
"Type": 36
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
}
|
||||
],
|
||||
"markers": [
|
||||
{
|
||||
"OTIO_SCHEMA": "Marker.2",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Keywords": [],
|
||||
"Note": "{\"resolve_sub_products\": {\"io.ayon.creators.resolve.shot\": {\"id\": \"pyblish.avalon.instance\", \"productType\": \"shot\", \"productName\": \"shotMain\", \"active\": true, \"creator_identifier\": \"io.ayon.creators.resolve.shot\", \"variant\": \"Main\", \"folderPath\": \"/shots/seq_qt_tc/sh010\", \"task\": \"Generic\", \"clip_variant\": \"<track_name>\", \"clip_index\": \"12cce00c-eadf-4abd-ac80-0816a24506ab\", \"clip_source_resolution\": {\"width\": \"956\", \"height\": \"720\", \"pixelAspect\": 1.0}, \"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"seq_qt_tc\", \"track\": \"{_track_}\", \"shot\": \"sh###\", \"hierarchy\": \"shots/seq_qt_tc\", \"sourceResolution\": false, \"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"parents\": [{\"folder_type\": \"folder\", \"entity_name\": \"shots\"}, {\"folder_type\": \"sequence\", \"entity_name\": \"seq_qt_tc\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"seq_qt_tc\", \"track\": \"Video1\", \"shot\": \"sh010\"}, \"heroTrack\": true, \"uuid\": \"5dc397e0-1142-4a35-969d-d4c35c512f0f\", \"reviewTrack\": \"Video1\", \"label\": \"/shots/seq_qt_tc/sh010 shot\", \"has_promised_context\": true, \"newHierarchyIntegration\": true, \"newAssetPublishing\": true, \"instance_id\": \"6f4bbf76-6638-4645-9059-0f516c0c12c2\", \"creator_attributes\": {\"folderPath\": \"/shots/seq_qt_tc/sh010\", \"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"frameStart\": 1001, \"frameEnd\": 1069, \"clipIn\": 86516, \"clipOut\": 86584, \"clipDuration\": 68, \"sourceIn\": 14, \"sourceOut\": 82, \"fps\": \"from_selection\"}, \"publish_attributes\": {\"CollectSlackFamilies\": {\"additional_message\": \"\"}}}, \"io.ayon.creators.resolve.plate\": {\"id\": \"pyblish.avalon.instance\", \"productType\": \"plate\", \"productName\": \"plateVideo1\", \"active\": true, \"creator_identifier\": \"io.ayon.creators.resolve.plate\", \"variant\": \"Video1\", \"folderPath\": \"/shots/seq_qt_tc/sh010\", \"task\": \"Generic\", \"clip_variant\": \"<track_name>\", \"clip_index\": \"12cce00c-eadf-4abd-ac80-0816a24506ab\", \"clip_source_resolution\": {\"width\": \"956\", \"height\": \"720\", \"pixelAspect\": 1.0}, \"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"seq_qt_tc\", \"track\": \"{_track_}\", \"shot\": \"sh###\", \"hierarchy\": \"shots/seq_qt_tc\", \"sourceResolution\": false, \"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"parents\": [{\"folder_type\": \"folder\", \"entity_name\": \"shots\"}, {\"folder_type\": \"sequence\", \"entity_name\": \"seq_qt_tc\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"seq_qt_tc\", \"track\": \"Video1\", \"shot\": \"sh010\"}, \"heroTrack\": true, \"uuid\": \"5dc397e0-1142-4a35-969d-d4c35c512f0f\", \"reviewTrack\": \"Video1\", \"parent_instance_id\": \"6f4bbf76-6638-4645-9059-0f516c0c12c2\", \"label\": \"/shots/seq_qt_tc/sh010 plate\", \"has_promised_context\": true, \"newHierarchyIntegration\": true, \"newAssetPublishing\": true, \"instance_id\": \"1d11a6b5-cc2b-49d8-8bcb-35187c785b22\", \"creator_attributes\": {\"parentInstance\": \"/shots/seq_qt_tc/sh010 shot\", \"vSyncOn\": true, \"vSyncTrack\": \"Video1\"}, \"publish_attributes\": {\"CollectSlackFamilies\": {\"additional_message\": \"\"}}}}, \"clip_index\": \"12cce00c-eadf-4abd-ac80-0816a24506ab\", \"publish\": true}"
|
||||
},
|
||||
"clip_index": "12cce00c-eadf-4abd-ac80-0816a24506ab",
|
||||
"publish": true,
|
||||
"resolve_sub_products": {
|
||||
"io.ayon.creators.resolve.plate": {
|
||||
"active": true,
|
||||
"clip_index": "12cce00c-eadf-4abd-ac80-0816a24506ab",
|
||||
"clip_source_resolution": {
|
||||
"height": "720",
|
||||
"pixelAspect": 1.0,
|
||||
"width": "956"
|
||||
},
|
||||
"clip_variant": "<track_name>",
|
||||
"creator_attributes": {
|
||||
"parentInstance": "/shots/seq_qt_tc/sh010 shot",
|
||||
"vSyncOn": true,
|
||||
"vSyncTrack": "Video1"
|
||||
},
|
||||
"creator_identifier": "io.ayon.creators.resolve.plate",
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"folderPath": "/shots/seq_qt_tc/sh010",
|
||||
"handleEnd": 10,
|
||||
"handleStart": 10,
|
||||
"has_promised_context": true,
|
||||
"heroTrack": true,
|
||||
"hierarchy": "shots/seq_qt_tc",
|
||||
"hierarchyData": {
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"sequence": "seq_qt_tc",
|
||||
"shot": "sh010",
|
||||
"track": "Video1"
|
||||
},
|
||||
"id": "pyblish.avalon.instance",
|
||||
"instance_id": "1d11a6b5-cc2b-49d8-8bcb-35187c785b22",
|
||||
"label": "/shots/seq_qt_tc/sh010 plate",
|
||||
"newAssetPublishing": true,
|
||||
"newHierarchyIntegration": true,
|
||||
"parent_instance_id": "6f4bbf76-6638-4645-9059-0f516c0c12c2",
|
||||
"parents": [
|
||||
{
|
||||
"entity_name": "shots",
|
||||
"folder_type": "folder"
|
||||
},
|
||||
{
|
||||
"entity_name": "seq_qt_tc",
|
||||
"folder_type": "sequence"
|
||||
}
|
||||
],
|
||||
"productName": "plateVideo1",
|
||||
"productType": "plate",
|
||||
"publish_attributes": {
|
||||
"CollectSlackFamilies": {
|
||||
"additional_message": ""
|
||||
}
|
||||
},
|
||||
"reviewTrack": "Video1",
|
||||
"sequence": "seq_qt_tc",
|
||||
"shot": "sh###",
|
||||
"sourceResolution": false,
|
||||
"task": "Generic",
|
||||
"track": "{_track_}",
|
||||
"uuid": "5dc397e0-1142-4a35-969d-d4c35c512f0f",
|
||||
"variant": "Video1",
|
||||
"workfileFrameStart": 1001
|
||||
},
|
||||
"io.ayon.creators.resolve.shot": {
|
||||
"active": true,
|
||||
"clip_index": "12cce00c-eadf-4abd-ac80-0816a24506ab",
|
||||
"clip_source_resolution": {
|
||||
"height": "720",
|
||||
"pixelAspect": 1.0,
|
||||
"width": "956"
|
||||
},
|
||||
"clip_variant": "<track_name>",
|
||||
"creator_attributes": {
|
||||
"clipDuration": 68,
|
||||
"clipIn": 86516,
|
||||
"clipOut": 86584,
|
||||
"folderPath": "/shots/seq_qt_tc/sh010",
|
||||
"fps": "from_selection",
|
||||
"frameEnd": 1069,
|
||||
"frameStart": 1001,
|
||||
"handleEnd": 10,
|
||||
"handleStart": 10,
|
||||
"sourceIn": 14,
|
||||
"sourceOut": 82,
|
||||
"workfileFrameStart": 1001
|
||||
},
|
||||
"creator_identifier": "io.ayon.creators.resolve.shot",
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"folderPath": "/shots/seq_qt_tc/sh010",
|
||||
"handleEnd": 10,
|
||||
"handleStart": 10,
|
||||
"has_promised_context": true,
|
||||
"heroTrack": true,
|
||||
"hierarchy": "shots/seq_qt_tc",
|
||||
"hierarchyData": {
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"sequence": "seq_qt_tc",
|
||||
"shot": "sh010",
|
||||
"track": "Video1"
|
||||
},
|
||||
"id": "pyblish.avalon.instance",
|
||||
"instance_id": "6f4bbf76-6638-4645-9059-0f516c0c12c2",
|
||||
"label": "/shots/seq_qt_tc/sh010 shot",
|
||||
"newAssetPublishing": true,
|
||||
"newHierarchyIntegration": true,
|
||||
"parents": [
|
||||
{
|
||||
"entity_name": "shots",
|
||||
"folder_type": "folder"
|
||||
},
|
||||
{
|
||||
"entity_name": "seq_qt_tc",
|
||||
"folder_type": "sequence"
|
||||
}
|
||||
],
|
||||
"productName": "shotMain",
|
||||
"productType": "shot",
|
||||
"publish_attributes": {
|
||||
"CollectSlackFamilies": {
|
||||
"additional_message": ""
|
||||
}
|
||||
},
|
||||
"reviewTrack": "Video1",
|
||||
"sequence": "seq_qt_tc",
|
||||
"shot": "sh###",
|
||||
"sourceResolution": false,
|
||||
"task": "Generic",
|
||||
"track": "{_track_}",
|
||||
"uuid": "5dc397e0-1142-4a35-969d-d4c35c512f0f",
|
||||
"variant": "Main",
|
||||
"workfileFrameStart": 1001
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "AyonData",
|
||||
"color": "GREEN",
|
||||
"marked_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 1.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 86448.0
|
||||
}
|
||||
},
|
||||
"comment": ""
|
||||
}
|
||||
],
|
||||
"enabled": true,
|
||||
"media_references": {
|
||||
"DEFAULT_MEDIA": {
|
||||
"OTIO_SCHEMA": "ExternalReference.1",
|
||||
"metadata": {},
|
||||
"name": "qt_embedded_tc.mov",
|
||||
"available_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 100.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 86400.0
|
||||
}
|
||||
},
|
||||
"available_image_bounds": null,
|
||||
"target_url": "C:\\data\\qt_embedded_tc.mov"
|
||||
}
|
||||
},
|
||||
"active_media_reference_key": "DEFAULT_MEDIA"
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,365 @@
|
|||
{
|
||||
"OTIO_SCHEMA": "Clip.2",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Link Group ID": 1
|
||||
}
|
||||
},
|
||||
"name": "simple_editorial_setup.mp4",
|
||||
"source_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 171.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 0.0
|
||||
}
|
||||
},
|
||||
"effects": [
|
||||
{
|
||||
"OTIO_SCHEMA": "LinearTimeWarp.1",
|
||||
"metadata": {},
|
||||
"name": "",
|
||||
"effect_name": "",
|
||||
"time_scalar": 2.5
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Transform",
|
||||
"Enabled": true,
|
||||
"Name": "Transform",
|
||||
"Parameters": [],
|
||||
"Type": 2
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Cropping",
|
||||
"Enabled": true,
|
||||
"Name": "Cropping",
|
||||
"Parameters": [],
|
||||
"Type": 3
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Dynamic Zoom",
|
||||
"Enabled": false,
|
||||
"Name": "Dynamic Zoom",
|
||||
"Parameters": [
|
||||
{
|
||||
"Default Parameter Value": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Key Frames": {
|
||||
"0": {
|
||||
"Value": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Variant Type": "POINTF"
|
||||
},
|
||||
"1000": {
|
||||
"Value": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Variant Type": "POINTF"
|
||||
}
|
||||
},
|
||||
"Parameter ID": "dynamicZoomCenter",
|
||||
"Parameter Value": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Variant Type": "POINTF"
|
||||
},
|
||||
{
|
||||
"Default Parameter Value": 1.0,
|
||||
"Key Frames": {
|
||||
"0": {
|
||||
"Value": 0.8,
|
||||
"Variant Type": "Double"
|
||||
},
|
||||
"1000": {
|
||||
"Value": 1.0,
|
||||
"Variant Type": "Double"
|
||||
}
|
||||
},
|
||||
"Parameter ID": "dynamicZoomScale",
|
||||
"Parameter Value": 1.0,
|
||||
"Variant Type": "Double",
|
||||
"maxValue": 100.0,
|
||||
"minValue": 0.01
|
||||
}
|
||||
],
|
||||
"Type": 59
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Composite",
|
||||
"Enabled": true,
|
||||
"Name": "Composite",
|
||||
"Parameters": [],
|
||||
"Type": 1
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Lens Correction",
|
||||
"Enabled": true,
|
||||
"Name": "Lens Correction",
|
||||
"Parameters": [],
|
||||
"Type": 43
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Retime and Scaling",
|
||||
"Enabled": true,
|
||||
"Name": "Retime and Scaling",
|
||||
"Parameters": [],
|
||||
"Type": 22
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Video Faders",
|
||||
"Enabled": true,
|
||||
"Name": "Video Faders",
|
||||
"Parameters": [],
|
||||
"Type": 36
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
}
|
||||
],
|
||||
"markers": [
|
||||
{
|
||||
"OTIO_SCHEMA": "Marker.2",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Keywords": [],
|
||||
"Note": "{\"resolve_sub_products\": {\"io.ayon.creators.resolve.shot\": {\"id\": \"pyblish.avalon.instance\", \"productType\": \"shot\", \"productName\": \"shotMain\", \"active\": true, \"creator_identifier\": \"io.ayon.creators.resolve.shot\", \"variant\": \"Main\", \"folderPath\": \"/shots/sq01/Video_1sq01sh010\", \"task\": \"Generic\", \"clip_variant\": \"<track_name>\", \"clip_index\": \"cef0267f-bbf4-4959-9f22-d225e03f2532\", \"clip_source_resolution\": {\"width\": \"640\", \"height\": \"360\", \"pixelAspect\": 1.0}, \"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"sq01\", \"track\": \"{_track_}\", \"shot\": \"sh###\", \"hierarchy\": \"shots/sq01\", \"sourceResolution\": false, \"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"parents\": [{\"folder_type\": \"folder\", \"entity_name\": \"shots\"}, {\"folder_type\": \"sequence\", \"entity_name\": \"sq01\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"sq01\", \"track\": \"Video_1\", \"shot\": \"sh010\"}, \"heroTrack\": true, \"uuid\": \"2a780b95-14cc-45de-acc0-3ecd1f504325\", \"reviewTrack\": null, \"label\": \"/shots/sq01/Video_1sq01sh010 shot\", \"has_promised_context\": true, \"newHierarchyIntegration\": true, \"newAssetPublishing\": true, \"instance_id\": \"e8af785a-484f-452b-8c9c-ac31ef0696c4\", \"creator_attributes\": {\"folderPath\": \"/shots/sq01/Video_1sq01sh010\", \"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"frameStart\": 1001, \"frameEnd\": 1172, \"clipIn\": 805, \"clipOut\": 976, \"clipDuration\": 171, \"sourceIn\": 0, \"sourceOut\": 171, \"fps\": \"from_selection\"}, \"publish_attributes\": {\"CollectSlackFamilies\": {\"additional_message\": \"\"}}}, \"io.ayon.creators.resolve.plate\": {\"id\": \"pyblish.avalon.instance\", \"productType\": \"plate\", \"productName\": \"plateVideo_1\", \"active\": true, \"creator_identifier\": \"io.ayon.creators.resolve.plate\", \"variant\": \"Video_1\", \"folderPath\": \"/shots/sq01/Video_1sq01sh010\", \"task\": \"Generic\", \"clip_variant\": \"<track_name>\", \"clip_index\": \"cef0267f-bbf4-4959-9f22-d225e03f2532\", \"clip_source_resolution\": {\"width\": \"640\", \"height\": \"360\", \"pixelAspect\": 1.0}, \"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"sq01\", \"track\": \"{_track_}\", \"shot\": \"sh###\", \"hierarchy\": \"shots/sq01\", \"sourceResolution\": false, \"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"parents\": [{\"folder_type\": \"folder\", \"entity_name\": \"shots\"}, {\"folder_type\": \"sequence\", \"entity_name\": \"sq01\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"sq01\", \"track\": \"Video_1\", \"shot\": \"sh010\"}, \"heroTrack\": true, \"uuid\": \"2a780b95-14cc-45de-acc0-3ecd1f504325\", \"reviewTrack\": null, \"parent_instance_id\": \"e8af785a-484f-452b-8c9c-ac31ef0696c4\", \"label\": \"/shots/sq01/Video_1sq01sh010 plate\", \"has_promised_context\": true, \"newHierarchyIntegration\": true, \"newAssetPublishing\": true, \"instance_id\": \"a34e7048-3d86-4c29-88c7-f65b1ba3d777\", \"creator_attributes\": {\"parentInstance\": \"/shots/sq01/Video_1sq01sh010 shot\", \"vSyncOn\": false, \"vSyncTrack\": \"Video 1\"}, \"publish_attributes\": {\"CollectSlackFamilies\": {\"additional_message\": \"\"}}}}, \"clip_index\": \"cef0267f-bbf4-4959-9f22-d225e03f2532\", \"publish\": true}"
|
||||
},
|
||||
"clip_index": "cef0267f-bbf4-4959-9f22-d225e03f2532",
|
||||
"publish": true,
|
||||
"resolve_sub_products": {
|
||||
"io.ayon.creators.resolve.plate": {
|
||||
"active": true,
|
||||
"clip_index": "cef0267f-bbf4-4959-9f22-d225e03f2532",
|
||||
"clip_source_resolution": {
|
||||
"height": "360",
|
||||
"pixelAspect": 1.0,
|
||||
"width": "640"
|
||||
},
|
||||
"clip_variant": "<track_name>",
|
||||
"creator_attributes": {
|
||||
"parentInstance": "/shots/sq01/Video_1sq01sh010 shot",
|
||||
"vSyncOn": false,
|
||||
"vSyncTrack": "Video 1"
|
||||
},
|
||||
"creator_identifier": "io.ayon.creators.resolve.plate",
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"folderPath": "/shots/sq01/Video_1sq01sh010",
|
||||
"handleEnd": 10,
|
||||
"handleStart": 10,
|
||||
"has_promised_context": true,
|
||||
"heroTrack": true,
|
||||
"hierarchy": "shots/sq01",
|
||||
"hierarchyData": {
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"sequence": "sq01",
|
||||
"shot": "sh010",
|
||||
"track": "Video_1"
|
||||
},
|
||||
"id": "pyblish.avalon.instance",
|
||||
"instance_id": "a34e7048-3d86-4c29-88c7-f65b1ba3d777",
|
||||
"label": "/shots/sq01/Video_1sq01sh010 plate",
|
||||
"newAssetPublishing": true,
|
||||
"newHierarchyIntegration": true,
|
||||
"parent_instance_id": "e8af785a-484f-452b-8c9c-ac31ef0696c4",
|
||||
"parents": [
|
||||
{
|
||||
"entity_name": "shots",
|
||||
"folder_type": "folder"
|
||||
},
|
||||
{
|
||||
"entity_name": "sq01",
|
||||
"folder_type": "sequence"
|
||||
}
|
||||
],
|
||||
"productName": "plateVideo_1",
|
||||
"productType": "plate",
|
||||
"publish_attributes": {
|
||||
"CollectSlackFamilies": {
|
||||
"additional_message": ""
|
||||
}
|
||||
},
|
||||
"reviewTrack": null,
|
||||
"sequence": "sq01",
|
||||
"shot": "sh###",
|
||||
"sourceResolution": false,
|
||||
"task": "Generic",
|
||||
"track": "{_track_}",
|
||||
"uuid": "2a780b95-14cc-45de-acc0-3ecd1f504325",
|
||||
"variant": "Video_1",
|
||||
"workfileFrameStart": 1001
|
||||
},
|
||||
"io.ayon.creators.resolve.shot": {
|
||||
"active": true,
|
||||
"clip_index": "cef0267f-bbf4-4959-9f22-d225e03f2532",
|
||||
"clip_source_resolution": {
|
||||
"height": "360",
|
||||
"pixelAspect": 1.0,
|
||||
"width": "640"
|
||||
},
|
||||
"clip_variant": "<track_name>",
|
||||
"creator_attributes": {
|
||||
"clipDuration": 171,
|
||||
"clipIn": 805,
|
||||
"clipOut": 976,
|
||||
"folderPath": "/shots/sq01/Video_1sq01sh010",
|
||||
"fps": "from_selection",
|
||||
"frameEnd": 1172,
|
||||
"frameStart": 1001,
|
||||
"handleEnd": 10,
|
||||
"handleStart": 10,
|
||||
"sourceIn": 0,
|
||||
"sourceOut": 171,
|
||||
"workfileFrameStart": 1001
|
||||
},
|
||||
"creator_identifier": "io.ayon.creators.resolve.shot",
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"folderPath": "/shots/sq01/Video_1sq01sh010",
|
||||
"handleEnd": 10,
|
||||
"handleStart": 10,
|
||||
"has_promised_context": true,
|
||||
"heroTrack": true,
|
||||
"hierarchy": "shots/sq01",
|
||||
"hierarchyData": {
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"sequence": "sq01",
|
||||
"shot": "sh010",
|
||||
"track": "Video_1"
|
||||
},
|
||||
"id": "pyblish.avalon.instance",
|
||||
"instance_id": "e8af785a-484f-452b-8c9c-ac31ef0696c4",
|
||||
"label": "/shots/sq01/Video_1sq01sh010 shot",
|
||||
"newAssetPublishing": true,
|
||||
"newHierarchyIntegration": true,
|
||||
"parents": [
|
||||
{
|
||||
"entity_name": "shots",
|
||||
"folder_type": "folder"
|
||||
},
|
||||
{
|
||||
"entity_name": "sq01",
|
||||
"folder_type": "sequence"
|
||||
}
|
||||
],
|
||||
"productName": "shotMain",
|
||||
"productType": "shot",
|
||||
"publish_attributes": {
|
||||
"CollectSlackFamilies": {
|
||||
"additional_message": ""
|
||||
}
|
||||
},
|
||||
"reviewTrack": null,
|
||||
"sequence": "sq01",
|
||||
"shot": "sh###",
|
||||
"sourceResolution": false,
|
||||
"task": "Generic",
|
||||
"track": "{_track_}",
|
||||
"uuid": "2a780b95-14cc-45de-acc0-3ecd1f504325",
|
||||
"variant": "Main",
|
||||
"workfileFrameStart": 1001
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "AyonData",
|
||||
"color": "GREEN",
|
||||
"marked_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 1.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 85.0
|
||||
}
|
||||
},
|
||||
"comment": ""
|
||||
}
|
||||
],
|
||||
"enabled": true,
|
||||
"media_references": {
|
||||
"DEFAULT_MEDIA": {
|
||||
"OTIO_SCHEMA": "ExternalReference.1",
|
||||
"metadata": {},
|
||||
"name": "simple_editorial_setup.mp4",
|
||||
"available_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 16450.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 0.0
|
||||
}
|
||||
},
|
||||
"available_image_bounds": null,
|
||||
"target_url": "C:\\Users\\robin\\OneDrive\\Bureau\\dev_ayon\\data\\simple_editorial_setup.mp4"
|
||||
}
|
||||
},
|
||||
"active_media_reference_key": "DEFAULT_MEDIA"
|
||||
}
|
||||
|
|
@ -0,0 +1,356 @@
|
|||
{
|
||||
"OTIO_SCHEMA": "Clip.2",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {}
|
||||
},
|
||||
"name": "3 jours dans les coulisses du ZEvent 2024.mp4",
|
||||
"source_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 50.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 0.0
|
||||
}
|
||||
},
|
||||
"effects": [
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Transform",
|
||||
"Enabled": true,
|
||||
"Name": "Transform",
|
||||
"Parameters": [],
|
||||
"Type": 2
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Cropping",
|
||||
"Enabled": true,
|
||||
"Name": "Cropping",
|
||||
"Parameters": [],
|
||||
"Type": 3
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Dynamic Zoom",
|
||||
"Enabled": false,
|
||||
"Name": "Dynamic Zoom",
|
||||
"Parameters": [
|
||||
{
|
||||
"Default Parameter Value": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Key Frames": {
|
||||
"0": {
|
||||
"Value": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Variant Type": "POINTF"
|
||||
},
|
||||
"1000": {
|
||||
"Value": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Variant Type": "POINTF"
|
||||
}
|
||||
},
|
||||
"Parameter ID": "dynamicZoomCenter",
|
||||
"Parameter Value": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"Variant Type": "POINTF"
|
||||
},
|
||||
{
|
||||
"Default Parameter Value": 1.0,
|
||||
"Key Frames": {
|
||||
"0": {
|
||||
"Value": 0.8,
|
||||
"Variant Type": "Double"
|
||||
},
|
||||
"1000": {
|
||||
"Value": 1.0,
|
||||
"Variant Type": "Double"
|
||||
}
|
||||
},
|
||||
"Parameter ID": "dynamicZoomScale",
|
||||
"Parameter Value": 1.0,
|
||||
"Variant Type": "Double",
|
||||
"maxValue": 100.0,
|
||||
"minValue": 0.01
|
||||
}
|
||||
],
|
||||
"Type": 59
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Composite",
|
||||
"Enabled": true,
|
||||
"Name": "Composite",
|
||||
"Parameters": [],
|
||||
"Type": 1
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Lens Correction",
|
||||
"Enabled": true,
|
||||
"Name": "Lens Correction",
|
||||
"Parameters": [],
|
||||
"Type": 43
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Retime and Scaling",
|
||||
"Enabled": true,
|
||||
"Name": "Retime and Scaling",
|
||||
"Parameters": [],
|
||||
"Type": 22
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Effect.1",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Effect Name": "Video Faders",
|
||||
"Enabled": true,
|
||||
"Name": "Video Faders",
|
||||
"Parameters": [],
|
||||
"Type": 36
|
||||
}
|
||||
},
|
||||
"name": "",
|
||||
"effect_name": "Resolve Effect"
|
||||
}
|
||||
],
|
||||
"markers": [
|
||||
{
|
||||
"OTIO_SCHEMA": "Marker.2",
|
||||
"metadata": {
|
||||
"Resolve_OTIO": {
|
||||
"Keywords": [],
|
||||
"Note": "{\"resolve_sub_products\": {\"io.ayon.creators.resolve.shot\": {\"id\": \"pyblish.avalon.instance\", \"productType\": \"shot\", \"productName\": \"shotMain\", \"active\": true, \"creator_identifier\": \"io.ayon.creators.resolve.shot\", \"variant\": \"Main\", \"folderPath\": \"/shots/seq_qt_no_tc/sh010\", \"task\": \"Generic\", \"clip_variant\": \"<track_name>\", \"clip_index\": \"c3d9fb4f-afdf-49e3-9733-bf80e40e0de3\", \"clip_source_resolution\": {\"width\": \"640\", \"height\": \"360\", \"pixelAspect\": 1.0}, \"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"seq_qt_no_tc\", \"track\": \"{_track_}\", \"shot\": \"sh###\", \"hierarchy\": \"shots/seq_qt_no_tc\", \"sourceResolution\": true, \"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"parents\": [{\"folder_type\": \"folder\", \"entity_name\": \"shots\"}, {\"folder_type\": \"sequence\", \"entity_name\": \"seq_qt_no_tc\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"seq_qt_no_tc\", \"track\": \"Video1\", \"shot\": \"sh010\"}, \"heroTrack\": true, \"uuid\": \"5ab44838-a173-422a-8750-d5265e5a4ab5\", \"reviewTrack\": \"Video1\", \"label\": \"/shots/seq_qt_no_tc/sh010 shot\", \"has_promised_context\": true, \"newHierarchyIntegration\": true, \"newAssetPublishing\": true, \"instance_id\": \"ba8e76cd-7319-449d-93b5-93fd65cf3e83\", \"creator_attributes\": {\"folderPath\": \"/shots/seq_qt_no_tc/sh010\", \"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"frameStart\": 1001, \"frameEnd\": 1051, \"clipIn\": 86477, \"clipOut\": 86527, \"clipDuration\": 50, \"sourceIn\": 0, \"sourceOut\": 50, \"fps\": \"from_selection\"}, \"publish_attributes\": {\"CollectSlackFamilies\": {\"additional_message\": \"\"}}}, \"io.ayon.creators.resolve.plate\": {\"id\": \"pyblish.avalon.instance\", \"productType\": \"plate\", \"productName\": \"plateVideo1\", \"active\": true, \"creator_identifier\": \"io.ayon.creators.resolve.plate\", \"variant\": \"Video1\", \"folderPath\": \"/shots/seq_qt_no_tc/sh010\", \"task\": \"Generic\", \"clip_variant\": \"<track_name>\", \"clip_index\": \"c3d9fb4f-afdf-49e3-9733-bf80e40e0de3\", \"clip_source_resolution\": {\"width\": \"640\", \"height\": \"360\", \"pixelAspect\": 1.0}, \"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"seq_qt_no_tc\", \"track\": \"{_track_}\", \"shot\": \"sh###\", \"hierarchy\": \"shots/seq_qt_no_tc\", \"sourceResolution\": true, \"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"parents\": [{\"folder_type\": \"folder\", \"entity_name\": \"shots\"}, {\"folder_type\": \"sequence\", \"entity_name\": \"seq_qt_no_tc\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"seq_qt_no_tc\", \"track\": \"Video1\", \"shot\": \"sh010\"}, \"heroTrack\": true, \"uuid\": \"5ab44838-a173-422a-8750-d5265e5a4ab5\", \"reviewTrack\": \"Video1\", \"parent_instance_id\": \"ba8e76cd-7319-449d-93b5-93fd65cf3e83\", \"label\": \"/shots/seq_qt_no_tc/sh010 plate\", \"has_promised_context\": true, \"newHierarchyIntegration\": true, \"newAssetPublishing\": true, \"instance_id\": \"4a1cd220-c638-4e77-855c-cebd43b5dbc3\", \"creator_attributes\": {\"parentInstance\": \"/shots/seq_qt_no_tc/sh010 shot\", \"vSyncOn\": true, \"vSyncTrack\": \"Video1\"}, \"publish_attributes\": {\"CollectSlackFamilies\": {\"additional_message\": \"\"}}}}, \"clip_index\": \"c3d9fb4f-afdf-49e3-9733-bf80e40e0de3\", \"publish\": true}"
|
||||
},
|
||||
"clip_index": "c3d9fb4f-afdf-49e3-9733-bf80e40e0de3",
|
||||
"publish": true,
|
||||
"resolve_sub_products": {
|
||||
"io.ayon.creators.resolve.plate": {
|
||||
"active": true,
|
||||
"clip_index": "c3d9fb4f-afdf-49e3-9733-bf80e40e0de3",
|
||||
"clip_source_resolution": {
|
||||
"height": "360",
|
||||
"pixelAspect": 1.0,
|
||||
"width": "640"
|
||||
},
|
||||
"clip_variant": "<track_name>",
|
||||
"creator_attributes": {
|
||||
"parentInstance": "/shots/seq_qt_no_tc/sh010 shot",
|
||||
"vSyncOn": true,
|
||||
"vSyncTrack": "Video1"
|
||||
},
|
||||
"creator_identifier": "io.ayon.creators.resolve.plate",
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"folderPath": "/shots/seq_qt_no_tc/sh010",
|
||||
"handleEnd": 10,
|
||||
"handleStart": 10,
|
||||
"has_promised_context": true,
|
||||
"heroTrack": true,
|
||||
"hierarchy": "shots/seq_qt_no_tc",
|
||||
"hierarchyData": {
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"sequence": "seq_qt_no_tc",
|
||||
"shot": "sh010",
|
||||
"track": "Video1"
|
||||
},
|
||||
"id": "pyblish.avalon.instance",
|
||||
"instance_id": "4a1cd220-c638-4e77-855c-cebd43b5dbc3",
|
||||
"label": "/shots/seq_qt_no_tc/sh010 plate",
|
||||
"newAssetPublishing": true,
|
||||
"newHierarchyIntegration": true,
|
||||
"parent_instance_id": "ba8e76cd-7319-449d-93b5-93fd65cf3e83",
|
||||
"parents": [
|
||||
{
|
||||
"entity_name": "shots",
|
||||
"folder_type": "folder"
|
||||
},
|
||||
{
|
||||
"entity_name": "seq_qt_no_tc",
|
||||
"folder_type": "sequence"
|
||||
}
|
||||
],
|
||||
"productName": "plateVideo1",
|
||||
"productType": "plate",
|
||||
"publish_attributes": {
|
||||
"CollectSlackFamilies": {
|
||||
"additional_message": ""
|
||||
}
|
||||
},
|
||||
"reviewTrack": "Video1",
|
||||
"sequence": "seq_qt_no_tc",
|
||||
"shot": "sh###",
|
||||
"sourceResolution": true,
|
||||
"task": "Generic",
|
||||
"track": "{_track_}",
|
||||
"uuid": "5ab44838-a173-422a-8750-d5265e5a4ab5",
|
||||
"variant": "Video1",
|
||||
"workfileFrameStart": 1001
|
||||
},
|
||||
"io.ayon.creators.resolve.shot": {
|
||||
"active": true,
|
||||
"clip_index": "c3d9fb4f-afdf-49e3-9733-bf80e40e0de3",
|
||||
"clip_source_resolution": {
|
||||
"height": "360",
|
||||
"pixelAspect": 1.0,
|
||||
"width": "640"
|
||||
},
|
||||
"clip_variant": "<track_name>",
|
||||
"creator_attributes": {
|
||||
"clipDuration": 50,
|
||||
"clipIn": 86477,
|
||||
"clipOut": 86527,
|
||||
"folderPath": "/shots/seq_qt_no_tc/sh010",
|
||||
"fps": "from_selection",
|
||||
"frameEnd": 1051,
|
||||
"frameStart": 1001,
|
||||
"handleEnd": 10,
|
||||
"handleStart": 10,
|
||||
"sourceIn": 0,
|
||||
"sourceOut": 50,
|
||||
"workfileFrameStart": 1001
|
||||
},
|
||||
"creator_identifier": "io.ayon.creators.resolve.shot",
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"folderPath": "/shots/seq_qt_no_tc/sh010",
|
||||
"handleEnd": 10,
|
||||
"handleStart": 10,
|
||||
"has_promised_context": true,
|
||||
"heroTrack": true,
|
||||
"hierarchy": "shots/seq_qt_no_tc",
|
||||
"hierarchyData": {
|
||||
"episode": "ep01",
|
||||
"folder": "shots",
|
||||
"sequence": "seq_qt_no_tc",
|
||||
"shot": "sh010",
|
||||
"track": "Video1"
|
||||
},
|
||||
"id": "pyblish.avalon.instance",
|
||||
"instance_id": "ba8e76cd-7319-449d-93b5-93fd65cf3e83",
|
||||
"label": "/shots/seq_qt_no_tc/sh010 shot",
|
||||
"newAssetPublishing": true,
|
||||
"newHierarchyIntegration": true,
|
||||
"parents": [
|
||||
{
|
||||
"entity_name": "shots",
|
||||
"folder_type": "folder"
|
||||
},
|
||||
{
|
||||
"entity_name": "seq_qt_no_tc",
|
||||
"folder_type": "sequence"
|
||||
}
|
||||
],
|
||||
"productName": "shotMain",
|
||||
"productType": "shot",
|
||||
"publish_attributes": {
|
||||
"CollectSlackFamilies": {
|
||||
"additional_message": ""
|
||||
}
|
||||
},
|
||||
"reviewTrack": "Video1",
|
||||
"sequence": "seq_qt_no_tc",
|
||||
"shot": "sh###",
|
||||
"sourceResolution": true,
|
||||
"task": "Generic",
|
||||
"track": "{_track_}",
|
||||
"uuid": "5ab44838-a173-422a-8750-d5265e5a4ab5",
|
||||
"variant": "Main",
|
||||
"workfileFrameStart": 1001
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "AyonData",
|
||||
"color": "GREEN",
|
||||
"marked_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 1.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 25.0
|
||||
}
|
||||
},
|
||||
"comment": ""
|
||||
}
|
||||
],
|
||||
"enabled": true,
|
||||
"media_references": {
|
||||
"DEFAULT_MEDIA": {
|
||||
"OTIO_SCHEMA": "ExternalReference.1",
|
||||
"metadata": {},
|
||||
"name": "3 jours dans les coulisses du ZEvent 2024.mp4",
|
||||
"available_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 30822.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 25.0,
|
||||
"value": 0.0
|
||||
}
|
||||
},
|
||||
"available_image_bounds": null,
|
||||
"target_url": "C:\\data\\movie.mp4"
|
||||
}
|
||||
},
|
||||
"active_media_reference_key": "DEFAULT_MEDIA"
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue