mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge branch 'develop' into enhancement/farm-env-variables
This commit is contained in:
commit
8111b4d47a
28 changed files with 165 additions and 98 deletions
|
|
@ -535,8 +535,8 @@ class AYONAddon(ABC):
|
|||
Implementation of this method is optional.
|
||||
|
||||
Note:
|
||||
The logic can be similar to logic in tray, but tray does not require
|
||||
to be logged in.
|
||||
The logic can be similar to logic in tray, but tray does not
|
||||
require to be logged in.
|
||||
|
||||
Args:
|
||||
process_context (ProcessContext): Context of child
|
||||
|
|
|
|||
|
|
@ -146,7 +146,8 @@ def publish_report_viewer():
|
|||
@main_cli.command()
|
||||
@click.argument("output_path")
|
||||
@click.option("--project", help="Define project context")
|
||||
@click.option("--folder", help="Define folder in project (project must be set)")
|
||||
@click.option(
|
||||
"--folder", help="Define folder in project (project must be set)")
|
||||
@click.option(
|
||||
"--strict",
|
||||
is_flag=True,
|
||||
|
|
|
|||
|
|
@ -616,7 +616,9 @@ class EnumDef(AbstractAttrDef):
|
|||
return data
|
||||
|
||||
@staticmethod
|
||||
def prepare_enum_items(items: "EnumItemsInputType") -> List["EnumItemDict"]:
|
||||
def prepare_enum_items(
|
||||
items: "EnumItemsInputType"
|
||||
) -> List["EnumItemDict"]:
|
||||
"""Convert items to unified structure.
|
||||
|
||||
Output is a list where each item is dictionary with 'value'
|
||||
|
|
|
|||
|
|
@ -276,12 +276,7 @@ class ASettingRegistry(ABC):
|
|||
@abstractmethod
|
||||
def _delete_item(self, name):
|
||||
# type: (str) -> None
|
||||
"""Delete item from settings.
|
||||
|
||||
Note:
|
||||
see :meth:`ayon_core.lib.user_settings.ARegistrySettings.delete_item`
|
||||
|
||||
"""
|
||||
"""Delete item from settings."""
|
||||
pass
|
||||
|
||||
def __delitem__(self, name):
|
||||
|
|
@ -433,12 +428,7 @@ class IniSettingRegistry(ASettingRegistry):
|
|||
config.write(cfg)
|
||||
|
||||
def _delete_item(self, name):
|
||||
"""Delete item from default section.
|
||||
|
||||
Note:
|
||||
See :meth:`~ayon_core.lib.IniSettingsRegistry.delete_item_from_section`
|
||||
|
||||
"""
|
||||
"""Delete item from default section."""
|
||||
self.delete_item_from_section("MAIN", name)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1283,12 +1283,16 @@ class CreateContext:
|
|||
|
||||
@contextmanager
|
||||
def bulk_pre_create_attr_defs_change(self, sender=None):
|
||||
with self._bulk_context("pre_create_attrs_change", sender) as bulk_info:
|
||||
with self._bulk_context(
|
||||
"pre_create_attrs_change", sender
|
||||
) as bulk_info:
|
||||
yield bulk_info
|
||||
|
||||
@contextmanager
|
||||
def bulk_create_attr_defs_change(self, sender=None):
|
||||
with self._bulk_context("create_attrs_change", sender) as bulk_info:
|
||||
with self._bulk_context(
|
||||
"create_attrs_change", sender
|
||||
) as bulk_info:
|
||||
yield bulk_info
|
||||
|
||||
@contextmanager
|
||||
|
|
@ -1946,9 +1950,9 @@ class CreateContext:
|
|||
creator are just removed from context.
|
||||
|
||||
Args:
|
||||
instances (List[CreatedInstance]): Instances that should be removed.
|
||||
Remove logic is done using creator, which may require to
|
||||
do other cleanup than just remove instance from context.
|
||||
instances (List[CreatedInstance]): Instances that should be
|
||||
removed. Remove logic is done using creator, which may require
|
||||
to do other cleanup than just remove instance from context.
|
||||
sender (Optional[str]): Sender of the event.
|
||||
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
import ayon_api
|
||||
from ayon_core.lib import StringTemplate, filter_profiles, prepare_template_data
|
||||
from ayon_core.lib import (
|
||||
StringTemplate,
|
||||
filter_profiles,
|
||||
prepare_template_data,
|
||||
)
|
||||
from ayon_core.settings import get_project_settings
|
||||
|
||||
from .constants import DEFAULT_PRODUCT_TEMPLATE
|
||||
|
|
|
|||
|
|
@ -222,6 +222,9 @@ def remap_range_on_file_sequence(otio_clip, in_out_range):
|
|||
source_range = otio_clip.source_range
|
||||
available_range_rate = available_range.start_time.rate
|
||||
media_in = available_range.start_time.value
|
||||
available_range_start_frame = (
|
||||
available_range.start_time.to_frames()
|
||||
)
|
||||
|
||||
# Temporary.
|
||||
# Some AYON custom OTIO exporter were implemented with relative
|
||||
|
|
@ -230,7 +233,7 @@ def remap_range_on_file_sequence(otio_clip, in_out_range):
|
|||
# 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 available_range_start_frame == media_ref.start_frame
|
||||
and source_range.start_time.to_frames() < media_ref.start_frame
|
||||
):
|
||||
media_in = 0
|
||||
|
|
@ -303,8 +306,12 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end):
|
|||
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_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
|
||||
|
|
|
|||
|
|
@ -18,13 +18,13 @@ def parse_ayon_entity_uri(uri: str) -> Optional[dict]:
|
|||
|
||||
Example:
|
||||
>>> parse_ayon_entity_uri(
|
||||
>>> "ayon://test/char/villain?product=modelMain&version=2&representation=usd" # noqa: E501
|
||||
>>> "ayon://test/char/villain?product=modelMain&version=2&representation=usd"
|
||||
>>> )
|
||||
{'project': 'test', 'folderPath': '/char/villain',
|
||||
'product': 'modelMain', 'version': 1,
|
||||
'representation': 'usd'}
|
||||
>>> parse_ayon_entity_uri(
|
||||
>>> "ayon+entity://project/folder?product=renderMain&version=3&representation=exr" # noqa: E501
|
||||
>>> "ayon+entity://project/folder?product=renderMain&version=3&representation=exr"
|
||||
>>> )
|
||||
{'project': 'project', 'folderPath': '/folder',
|
||||
'product': 'renderMain', 'version': 3,
|
||||
|
|
@ -34,7 +34,7 @@ def parse_ayon_entity_uri(uri: str) -> Optional[dict]:
|
|||
dict[str, Union[str, int]]: The individual key with their values as
|
||||
found in the ayon entity URI.
|
||||
|
||||
"""
|
||||
""" # noqa: E501
|
||||
|
||||
if not (uri.startswith("ayon+entity://") or uri.startswith("ayon://")):
|
||||
return {}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,10 @@ import attr
|
|||
import ayon_api
|
||||
import clique
|
||||
from ayon_core.lib import Logger, collect_frames
|
||||
from ayon_core.pipeline import get_current_project_name, get_representation_path
|
||||
from ayon_core.pipeline import (
|
||||
get_current_project_name,
|
||||
get_representation_path,
|
||||
)
|
||||
from ayon_core.pipeline.create import get_product_name
|
||||
from ayon_core.pipeline.farm.patterning import match_aov_pattern
|
||||
from ayon_core.pipeline.publish import KnownPublishError
|
||||
|
|
@ -771,9 +774,14 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data,
|
|||
|
||||
project_settings = instance.context.data.get("project_settings")
|
||||
|
||||
use_legacy_product_name = True
|
||||
try:
|
||||
use_legacy_product_name = project_settings["core"]["tools"]["creator"]["use_legacy_product_names_for_renders"] # noqa: E501
|
||||
use_legacy_product_name = (
|
||||
project_settings
|
||||
["core"]
|
||||
["tools"]
|
||||
["creator"]
|
||||
["use_legacy_product_names_for_renders"]
|
||||
)
|
||||
except KeyError:
|
||||
warnings.warn(
|
||||
("use_legacy_for_renders not found in project settings. "
|
||||
|
|
@ -789,7 +797,9 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data,
|
|||
dynamic_data=dynamic_data)
|
||||
|
||||
else:
|
||||
product_name, group_name = get_product_name_and_group_from_template(
|
||||
(
|
||||
product_name, group_name
|
||||
) = get_product_name_and_group_from_template(
|
||||
task_entity=instance.data["taskEntity"],
|
||||
project_name=instance.context.data["projectName"],
|
||||
host_name=instance.context.data["hostName"],
|
||||
|
|
@ -932,7 +942,7 @@ def _collect_expected_files_for_aov(files):
|
|||
# but we really expect only one collection.
|
||||
# Nothing else make sense.
|
||||
if len(cols) != 1:
|
||||
raise ValueError("Only one image sequence type is expected.") # noqa: E501
|
||||
raise ValueError("Only one image sequence type is expected.")
|
||||
return list(cols[0])
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,8 @@ class CollectHierarchy(pyblish.api.ContextPlugin):
|
|||
|
||||
shot_data = {
|
||||
"entity_type": "folder",
|
||||
# WARNING unless overwritten, default folder type is hardcoded to shot
|
||||
# WARNING unless overwritten, default folder type is hardcoded
|
||||
# to shot
|
||||
"folder_type": instance.data.get("folder_type") or "Shot",
|
||||
"tasks": instance.data.get("tasks") or {},
|
||||
"comments": instance.data.get("comments", []),
|
||||
|
|
|
|||
|
|
@ -129,26 +129,33 @@ class ExtractOTIOReview(
|
|||
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
|
||||
))
|
||||
self.to_width, self.to_height = (
|
||||
res_data["width"], res_data["height"]
|
||||
)
|
||||
self.log.debug(
|
||||
"> self.to_width x self.to_height:"
|
||||
f" {self.to_width} x {self.to_height}"
|
||||
)
|
||||
|
||||
available_range = r_otio_cl.available_range()
|
||||
available_range_start_frame = (
|
||||
available_range.start_time.to_frames()
|
||||
)
|
||||
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)
|
||||
src_frame_start = src_range.start_time.to_frames()
|
||||
|
||||
# Temporary.
|
||||
# Some AYON custom OTIO exporter were implemented with relative
|
||||
# source range for image sequence. Following code maintain
|
||||
# backward-compatibility by adjusting available range
|
||||
# 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
|
||||
and available_range_start_frame == media_ref.start_frame
|
||||
and src_frame_start < media_ref.start_frame
|
||||
):
|
||||
available_range = otio.opentime.TimeRange(
|
||||
otio.opentime.RationalTime(0, rate=self.actual_fps),
|
||||
|
|
@ -246,7 +253,8 @@ class ExtractOTIOReview(
|
|||
# Extraction via FFmpeg.
|
||||
else:
|
||||
path = media_ref.target_url
|
||||
# Set extract range from 0 (FFmpeg ignores embedded timecode).
|
||||
# Set extract range from 0 (FFmpeg ignores
|
||||
# embedded timecode).
|
||||
extract_range = otio.opentime.TimeRange(
|
||||
otio.opentime.RationalTime(
|
||||
(
|
||||
|
|
@ -414,7 +422,8 @@ class ExtractOTIOReview(
|
|||
to defined image sequence format.
|
||||
|
||||
Args:
|
||||
sequence (list): input dir path string, collection object, fps 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
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ class ValidateProductUniqueness(pyblish.api.ContextPlugin):
|
|||
"""Validate all product names are unique.
|
||||
|
||||
This only validates whether the instances currently set to publish from
|
||||
the workfile overlap one another for the folder + product they are publishing
|
||||
to.
|
||||
the workfile overlap one another for the folder + product they are
|
||||
publishing to.
|
||||
|
||||
This does not perform any check against existing publishes in the database
|
||||
since it is allowed to publish into existing products resulting in
|
||||
|
|
@ -72,8 +72,10 @@ class ValidateProductUniqueness(pyblish.api.ContextPlugin):
|
|||
# All is ok
|
||||
return
|
||||
|
||||
msg = ("Instance product names {} are not unique. ".format(non_unique) +
|
||||
"Please remove or rename duplicates.")
|
||||
msg = (
|
||||
f"Instance product names {non_unique} are not unique."
|
||||
" Please remove or rename duplicates."
|
||||
)
|
||||
formatting_data = {
|
||||
"non_unique": ",".join(non_unique)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,7 +79,8 @@ class ModifiedBurnins(ffmpeg_burnins.Burnins):
|
|||
- Datatypes explanation:
|
||||
<color> string format must be supported by FFmpeg.
|
||||
Examples: "#000000", "0x000000", "black"
|
||||
<font> must be accesible by ffmpeg = name of registered Font in system or path to font file.
|
||||
<font> must be accesible by ffmpeg = name of registered Font in system
|
||||
or path to font file.
|
||||
Examples: "Arial", "C:/Windows/Fonts/arial.ttf"
|
||||
|
||||
- Possible keys:
|
||||
|
|
@ -87,17 +88,21 @@ class ModifiedBurnins(ffmpeg_burnins.Burnins):
|
|||
"bg_opacity" - Opacity of background (box around text) - <float, Range:0-1>
|
||||
"bg_color" - Background color - <color>
|
||||
"bg_padding" - Background padding in pixels - <int>
|
||||
"x_offset" - offsets burnin vertically by entered pixels from border - <int>
|
||||
"y_offset" - offsets burnin horizontally by entered pixels from border - <int>
|
||||
"x_offset" - offsets burnin vertically by entered pixels
|
||||
from border - <int>
|
||||
"y_offset" - offsets burnin horizontally by entered pixels
|
||||
from border - <int>
|
||||
- x_offset & y_offset should be set at least to same value as bg_padding!!
|
||||
"font" - Font Family for text - <font>
|
||||
"font_size" - Font size in pixels - <int>
|
||||
"font_color" - Color of text - <color>
|
||||
"frame_offset" - Default start frame - <int>
|
||||
- required IF start frame is not set when using frames or timecode burnins
|
||||
- required IF start frame is not set when using frames
|
||||
or timecode burnins
|
||||
|
||||
On initializing class can be set General options through "options_init" arg.
|
||||
General can be overridden when adding burnin
|
||||
On initializing class can be set General options through
|
||||
"options_init" arg.
|
||||
General options can be overridden when adding burnin.
|
||||
|
||||
'''
|
||||
TOP_CENTERED = ffmpeg_burnins.TOP_CENTERED
|
||||
|
|
|
|||
|
|
@ -190,6 +190,7 @@ def get_current_project_settings():
|
|||
project_name = os.environ.get("AYON_PROJECT_NAME")
|
||||
if not project_name:
|
||||
raise ValueError(
|
||||
"Missing context project in environemt variable `AYON_PROJECT_NAME`."
|
||||
"Missing context project in environment"
|
||||
" variable `AYON_PROJECT_NAME`."
|
||||
)
|
||||
return get_project_settings(project_name)
|
||||
|
|
|
|||
|
|
@ -217,7 +217,9 @@ class ProductTypeDescriptionWidget(QtWidgets.QWidget):
|
|||
|
||||
product_type_label = QtWidgets.QLabel(self)
|
||||
product_type_label.setObjectName("CreatorProductTypeLabel")
|
||||
product_type_label.setAlignment(QtCore.Qt.AlignBottom | QtCore.Qt.AlignLeft)
|
||||
product_type_label.setAlignment(
|
||||
QtCore.Qt.AlignBottom | QtCore.Qt.AlignLeft
|
||||
)
|
||||
|
||||
help_label = QtWidgets.QLabel(self)
|
||||
help_label.setAlignment(QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft)
|
||||
|
|
|
|||
|
|
@ -21,9 +21,9 @@ except ImportError:
|
|||
|
||||
Application action based on 'ApplicationManager' system.
|
||||
|
||||
Handling of applications in launcher is not ideal and should be completely
|
||||
redone from scratch. This is just a temporary solution to keep backwards
|
||||
compatibility with AYON launcher.
|
||||
Handling of applications in launcher is not ideal and should be
|
||||
completely redone from scratch. This is just a temporary solution
|
||||
to keep backwards compatibility with AYON launcher.
|
||||
|
||||
Todos:
|
||||
Move handling of errors to frontend.
|
||||
|
|
|
|||
|
|
@ -517,7 +517,11 @@ class CustomPaintMultiselectComboBox(QtWidgets.QComboBox):
|
|||
def setItemCheckState(self, index, state):
|
||||
self.setItemData(index, state, QtCore.Qt.CheckStateRole)
|
||||
|
||||
def set_value(self, values: Optional[Iterable[Any]], role: Optional[int] = None):
|
||||
def set_value(
|
||||
self,
|
||||
values: Optional[Iterable[Any]],
|
||||
role: Optional[int] = None,
|
||||
):
|
||||
if role is None:
|
||||
role = self._value_role
|
||||
|
||||
|
|
|
|||
|
|
@ -499,8 +499,10 @@ class ProductsModel(QtGui.QStandardItemModel):
|
|||
version_item.version_id
|
||||
for version_item in last_version_by_product_id.values()
|
||||
}
|
||||
repre_count_by_version_id = self._controller.get_versions_representation_count(
|
||||
project_name, version_ids
|
||||
repre_count_by_version_id = (
|
||||
self._controller.get_versions_representation_count(
|
||||
project_name, version_ids
|
||||
)
|
||||
)
|
||||
sync_availability_by_version_id = (
|
||||
self._controller.get_version_sync_availability(
|
||||
|
|
|
|||
|
|
@ -339,7 +339,9 @@ class OverviewWidget(QtWidgets.QFrame):
|
|||
self._change_visibility_for_state()
|
||||
self._product_content_layout.addWidget(self._create_widget, 7)
|
||||
self._product_content_layout.addWidget(self._product_views_widget, 3)
|
||||
self._product_content_layout.addWidget(self._product_attributes_wrap, 7)
|
||||
self._product_content_layout.addWidget(
|
||||
self._product_attributes_wrap, 7
|
||||
)
|
||||
|
||||
def _change_visibility_for_state(self):
|
||||
self._create_widget.setVisible(
|
||||
|
|
|
|||
|
|
@ -214,8 +214,8 @@ class TasksCombobox(QtWidgets.QComboBox):
|
|||
Combobox gives ability to select only from intersection of task names for
|
||||
folder paths in selected instances.
|
||||
|
||||
If folder paths in selected instances does not have same tasks then combobox
|
||||
will be empty.
|
||||
If folder paths in selected instances does not have same tasks
|
||||
then combobox will be empty.
|
||||
"""
|
||||
value_changed = QtCore.Signal()
|
||||
|
||||
|
|
@ -604,7 +604,7 @@ class VariantInputWidget(PlaceholderLineEdit):
|
|||
|
||||
|
||||
class GlobalAttrsWidget(QtWidgets.QWidget):
|
||||
"""Global attributes mainly to define context and product name of instances.
|
||||
"""Global attributes to define context and product name of instances.
|
||||
|
||||
product name is or may be affected on context. Gives abiity to modify
|
||||
context and product name of instance. This change is not autopromoted but
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ class TasksModel(QtGui.QStandardItemModel):
|
|||
tasks with same names then model is empty too.
|
||||
|
||||
Args:
|
||||
controller (AbstractPublisherFrontend): Controller which handles creation and
|
||||
publishing.
|
||||
controller (AbstractPublisherFrontend): Controller which handles
|
||||
creation and publishing.
|
||||
|
||||
"""
|
||||
def __init__(
|
||||
|
|
|
|||
|
|
@ -998,7 +998,11 @@ class PublisherWindow(QtWidgets.QDialog):
|
|||
new_item["label"] = new_item.pop("creator_label")
|
||||
new_item["identifier"] = new_item.pop("creator_identifier")
|
||||
new_failed_info.append(new_item)
|
||||
self.add_error_message_dialog(event["title"], new_failed_info, "Creator:")
|
||||
self.add_error_message_dialog(
|
||||
event["title"],
|
||||
new_failed_info,
|
||||
"Creator:"
|
||||
)
|
||||
|
||||
def _on_convertor_error(self, event):
|
||||
new_failed_info = []
|
||||
|
|
|
|||
|
|
@ -366,8 +366,8 @@ class ContainersModel:
|
|||
try:
|
||||
uuid.UUID(repre_id)
|
||||
except (ValueError, TypeError, AttributeError):
|
||||
# Fake not existing representation id so container is shown in UI
|
||||
# but as invalid
|
||||
# Fake not existing representation id so container
|
||||
# is shown in UI but as invalid
|
||||
item.representation_id = invalid_ids_mapping.setdefault(
|
||||
repre_id, uuid.uuid4().hex
|
||||
)
|
||||
|
|
|
|||
|
|
@ -556,9 +556,10 @@ class _IconsCache:
|
|||
log.info("Didn't find icon \"{}\"".format(icon_name))
|
||||
|
||||
elif used_variant != icon_name:
|
||||
log.debug("Icon \"{}\" was not found \"{}\" is used instead".format(
|
||||
icon_name, used_variant
|
||||
))
|
||||
log.debug(
|
||||
f"Icon \"{icon_name}\" was not found"
|
||||
f" \"{used_variant}\" is used instead"
|
||||
)
|
||||
|
||||
cls._qtawesome_cache[full_icon_name] = icon
|
||||
return icon
|
||||
|
|
|
|||
|
|
@ -68,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", "W"]
|
||||
select = ["E", "F", "W"]
|
||||
ignore = []
|
||||
|
||||
# Allow fix for all enabled rules (when `--fix`) is provided.
|
||||
|
|
|
|||
|
|
@ -358,7 +358,10 @@ class ExtractOIIOTranscodeOutputModel(BaseSettingsModel):
|
|||
custom_tags: list[str] = SettingsField(
|
||||
default_factory=list,
|
||||
title="Custom Tags",
|
||||
description="Additional custom tags that will be added to the created representation."
|
||||
description=(
|
||||
"Additional custom tags that will be added"
|
||||
" to the created representation."
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -892,9 +895,11 @@ class PublishPuginsModel(BaseSettingsModel):
|
|||
default_factory=CollectFramesFixDefModel,
|
||||
title="Collect Frames to Fix",
|
||||
)
|
||||
CollectUSDLayerContributions: CollectUSDLayerContributionsModel = SettingsField(
|
||||
default_factory=CollectUSDLayerContributionsModel,
|
||||
title="Collect USD Layer Contributions",
|
||||
CollectUSDLayerContributions: CollectUSDLayerContributionsModel = (
|
||||
SettingsField(
|
||||
default_factory=CollectUSDLayerContributionsModel,
|
||||
title="Collect USD Layer Contributions",
|
||||
)
|
||||
)
|
||||
ValidateEditorialAssetName: ValidateBaseModel = SettingsField(
|
||||
default_factory=ValidateBaseModel,
|
||||
|
|
@ -1214,7 +1219,9 @@ DEFAULT_PUBLISH_VALUES = {
|
|||
"TOP_RIGHT": "{anatomy[version]}",
|
||||
"BOTTOM_LEFT": "{username}",
|
||||
"BOTTOM_CENTERED": "{folder[name]}",
|
||||
"BOTTOM_RIGHT": "{frame_start}-{current_frame}-{frame_end}",
|
||||
"BOTTOM_RIGHT": (
|
||||
"{frame_start}-{current_frame}-{frame_end}"
|
||||
),
|
||||
"filter": {
|
||||
"families": [],
|
||||
"tags": []
|
||||
|
|
@ -1240,7 +1247,9 @@ DEFAULT_PUBLISH_VALUES = {
|
|||
"TOP_RIGHT": "{anatomy[version]}",
|
||||
"BOTTOM_LEFT": "{username}",
|
||||
"BOTTOM_CENTERED": "{folder[name]}",
|
||||
"BOTTOM_RIGHT": "{frame_start}-{current_frame}-{frame_end}",
|
||||
"BOTTOM_RIGHT": (
|
||||
"{frame_start}-{current_frame}-{frame_end}"
|
||||
),
|
||||
"filter": {
|
||||
"families": [],
|
||||
"tags": []
|
||||
|
|
|
|||
|
|
@ -83,8 +83,8 @@ class CreatorToolModel(BaseSettingsModel):
|
|||
filter_creator_profiles: list[FilterCreatorProfile] = SettingsField(
|
||||
default_factory=list,
|
||||
title="Filter creator profiles",
|
||||
description="Allowed list of creator labels that will be only shown if "
|
||||
"profile matches context."
|
||||
description="Allowed list of creator labels that will be only shown"
|
||||
" if profile matches context."
|
||||
)
|
||||
|
||||
@validator("product_types_smart_select")
|
||||
|
|
@ -426,7 +426,9 @@ DEFAULT_TOOLS_VALUES = {
|
|||
],
|
||||
"task_types": [],
|
||||
"tasks": [],
|
||||
"template": "{product[type]}{Task[name]}_{Renderlayer}_{Renderpass}"
|
||||
"template": (
|
||||
"{product[type]}{Task[name]}_{Renderlayer}_{Renderpass}"
|
||||
)
|
||||
},
|
||||
{
|
||||
"product_types": [
|
||||
|
|
|
|||
|
|
@ -130,19 +130,20 @@ def test_image_sequence_and_handles_out_of_range():
|
|||
|
||||
expected = [
|
||||
# 5 head black frames generated from gap (991-995)
|
||||
"/path/to/ffmpeg -t 0.2 -r 25.0 -f lavfi -i color=c=black:s=1280x720 -tune "
|
||||
"stillimage -start_number 991 C:/result/output.%03d.jpg",
|
||||
"/path/to/ffmpeg -t 0.2 -r 25.0 -f lavfi -i color=c=black:s=1280x720"
|
||||
" -tune stillimage -start_number 991 C:/result/output.%03d.jpg",
|
||||
|
||||
# 9 tail back frames generated from gap (1097-1105)
|
||||
"/path/to/ffmpeg -t 0.36 -r 25.0 -f lavfi -i color=c=black:s=1280x720 -tune "
|
||||
"stillimage -start_number 1097 C:/result/output.%03d.jpg",
|
||||
"/path/to/ffmpeg -t 0.36 -r 25.0 -f lavfi -i color=c=black:s=1280x720"
|
||||
" -tune stillimage -start_number 1097 C:/result/output.%03d.jpg",
|
||||
|
||||
# Report from source tiff (996-1096)
|
||||
# 996-1000 = additional 5 head frames
|
||||
# 1001-1095 = source range conformed to 25fps
|
||||
# 1096-1096 = additional 1 tail frames
|
||||
"/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i "
|
||||
f"C:\\tif_seq{os.sep}output.%04d.tif -start_number 996 C:/result/output.%03d.jpg"
|
||||
f"C:\\tif_seq{os.sep}output.%04d.tif -start_number 996"
|
||||
f" C:/result/output.%03d.jpg"
|
||||
]
|
||||
|
||||
assert calls == expected
|
||||
|
|
@ -179,13 +180,13 @@ def test_short_movie_head_gap_handles():
|
|||
|
||||
expected = [
|
||||
# 10 head black frames generated from gap (991-1000)
|
||||
"/path/to/ffmpeg -t 0.4 -r 25.0 -f lavfi -i color=c=black:s=1280x720 -tune "
|
||||
"stillimage -start_number 991 C:/result/output.%03d.jpg",
|
||||
"/path/to/ffmpeg -t 0.4 -r 25.0 -f lavfi -i color=c=black:s=1280x720"
|
||||
" -tune stillimage -start_number 991 C:/result/output.%03d.jpg",
|
||||
|
||||
# source range + 10 tail frames
|
||||
# duration = 50fr (source) + 10fr (tail handle) = 60 fr = 2.4s
|
||||
"/path/to/ffmpeg -ss 0.0 -t 2.4 -i C:\\data\\movie.mp4 -start_number 1001 "
|
||||
"C:/result/output.%03d.jpg"
|
||||
"/path/to/ffmpeg -ss 0.0 -t 2.4 -i C:\\data\\movie.mp4"
|
||||
" -start_number 1001 C:/result/output.%03d.jpg"
|
||||
]
|
||||
|
||||
assert calls == expected
|
||||
|
|
@ -208,7 +209,8 @@ def test_short_movie_tail_gap_handles():
|
|||
# 10 head frames + source range
|
||||
# duration = 10fr (head handle) + 66fr (source) = 76fr = 3.16s
|
||||
"/path/to/ffmpeg -ss 1.0416666666666667 -t 3.1666666666666665 -i "
|
||||
"C:\\data\\qt_no_tc_24fps.mov -start_number 991 C:/result/output.%03d.jpg"
|
||||
"C:\\data\\qt_no_tc_24fps.mov -start_number 991"
|
||||
" C:/result/output.%03d.jpg"
|
||||
]
|
||||
|
||||
assert calls == expected
|
||||
|
|
@ -234,10 +236,12 @@ def test_multiple_review_clips_no_gap():
|
|||
|
||||
expected = [
|
||||
# 10 head black frames generated from gap (991-1000)
|
||||
'/path/to/ffmpeg -t 0.4 -r 25.0 -f lavfi -i color=c=black:s=1280x720 -tune '
|
||||
'/path/to/ffmpeg -t 0.4 -r 25.0 -f lavfi'
|
||||
' -i color=c=black:s=1280x720 -tune '
|
||||
'stillimage -start_number 991 C:/result/output.%03d.jpg',
|
||||
|
||||
# Alternance 25fps tiff sequence and 24fps exr sequence for 100 frames each
|
||||
# Alternance 25fps tiff sequence and 24fps exr sequence
|
||||
# for 100 frames each
|
||||
'/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i '
|
||||
f'C:\\no_tc{os.sep}output.%04d.tif '
|
||||
'-start_number 1001 C:/result/output.%03d.jpg',
|
||||
|
|
@ -315,7 +319,8 @@ def test_multiple_review_clips_with_gap():
|
|||
|
||||
expected = [
|
||||
# Gap on review track (12 frames)
|
||||
'/path/to/ffmpeg -t 0.5 -r 24.0 -f lavfi -i color=c=black:s=1280x720 -tune '
|
||||
'/path/to/ffmpeg -t 0.5 -r 24.0 -f lavfi'
|
||||
' -i color=c=black:s=1280x720 -tune '
|
||||
'stillimage -start_number 991 C:/result/output.%03d.jpg',
|
||||
|
||||
'/path/to/ffmpeg -start_number 1000 -framerate 24.0 -i '
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue