Merge branch 'bugfix/workfiles_tool_disallow_save_as_when_not_in_task' into enhancement/add_borisfx_silhouette_support

This commit is contained in:
Roy Nieterau 2025-01-14 16:24:54 +01:00
commit f5f48230f5
17 changed files with 447 additions and 123 deletions

View file

@ -15,7 +15,7 @@ from ayon_core.pipeline.plugin_discover import (
deregister_plugin,
deregister_plugin_path
)
from ayon_core.pipeline import get_staging_dir_info
from ayon_core.pipeline.staging_dir import get_staging_dir_info, StagingDir
from .constants import DEFAULT_VARIANT_VALUE
from .product_name import get_product_name
@ -833,7 +833,7 @@ class Creator(BaseCreator):
"""
return self.pre_create_attr_defs
def get_staging_dir(self, instance):
def get_staging_dir(self, instance) -> Optional[StagingDir]:
"""Return the staging dir and persistence from instance.
Args:
@ -915,7 +915,7 @@ class Creator(BaseCreator):
instance.transient_data.update({
"stagingDir": staging_dir_path,
"stagingDir_persistent": staging_dir_info.persistent,
"stagingDir_persistent": staging_dir_info.is_persistent,
})
self.log.info(f"Applied staging dir to instance: {staging_dir_path}")

View file

@ -196,11 +196,11 @@ def is_clip_from_media_sequence(otio_clip):
return is_input_sequence or is_input_sequence_legacy
def remap_range_on_file_sequence(otio_clip, in_out_range):
def remap_range_on_file_sequence(otio_clip, otio_range):
"""
Args:
otio_clip (otio.schema.Clip): The OTIO clip to check.
in_out_range (tuple[float, float]): The in-out range to remap.
otio_range (otio.schema.TimeRange): The trim range to apply.
Returns:
tuple(int, int): The remapped range as discrete frame number.
@ -211,17 +211,24 @@ def remap_range_on_file_sequence(otio_clip, in_out_range):
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
# 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_range_rate = round(otio_range.start_time.rate, 2)
if rounded_av_rate != rounded_range_rate:
raise ValueError("Inconsistent range between clip and provided clip")
source_range = otio_clip.source_range
media_in = available_range.start_time
available_range_start_frame = (
available_range.start_time.to_frames()
)
@ -231,19 +238,26 @@ def remap_range_on_file_sequence(otio_clip, in_out_range):
# source range for image sequence. Following code maintain
# backward-compatibility by adjusting media_in
# while we are updating those.
conformed_src_in = source_range.start_time.rescaled_to(
available_range_rate
)
if (
is_clip_from_media_sequence(otio_clip)
and available_range_start_frame == media_ref.start_frame
and source_range.start_time.to_frames() < media_ref.start_frame
and conformed_src_in.to_frames() < media_ref.start_frame
):
media_in = 0
media_in = otio.opentime.RationalTime(
0, rate=available_range_rate
)
src_offset_in = otio_range.start_time - media_in
frame_in = otio.opentime.RationalTime.from_frames(
media_in_trimmed - media_in + media_ref.start_frame,
media_ref.start_frame + src_offset_in.to_frames(),
rate=available_range_rate,
).to_frames()
frame_out = otio.opentime.RationalTime.from_frames(
media_out_trimmed - media_in + media_ref.start_frame,
frame_in + otio_range.duration.to_frames() - 1,
rate=available_range_rate,
).to_frames()
@ -261,21 +275,6 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end):
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
#
@ -320,6 +319,22 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end):
else:
conformed_source_range = source_range
# 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 conformed_source_range.start_time.to_frames() <
media_ref.start_frame
):
available_range = _ot.TimeRange(
_ot.RationalTime(0, rate=available_range_rate),
available_range.duration,
)
# modifiers
time_scalar = 1.
offset_in = 0
@ -374,31 +389,47 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end):
offset_in, offset_out = offset_out, offset_in
handle_start, handle_end = handle_end, handle_start
# 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 = available_range.start_time.value
media_out = available_range.end_time_inclusive().value
# If media source is an image sequence, returned
# mediaIn/mediaOut have to correspond
# to frame numbers from source sequence.
if is_input_sequence:
src_in = conformed_source_range.start_time
src_duration = conformed_source_range.duration
offset_in = otio.opentime.RationalTime(offset_in, rate=src_in.rate)
offset_duration = otio.opentime.RationalTime(
offset_out,
rate=src_duration.rate
)
trim_range = otio.opentime.TimeRange(
start_time=src_in + offset_in,
duration=src_duration + offset_duration
)
# preserve discrete frame numbers
media_in_trimmed, media_out_trimmed = remap_range_on_file_sequence(
otio_clip,
(media_in_trimmed, media_out_trimmed)
trim_range,
)
media_in = media_ref.start_frame
media_out = media_in + available_range.duration.to_frames() - 1
else:
# 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 = available_range.start_time.value
media_out = available_range.end_time_inclusive().value
# adjust available handles if needed
if (media_in_trimmed - media_in) < handle_start:
handle_start = max(0, media_in_trimmed - media_in)

View file

@ -716,8 +716,8 @@ def get_instance_staging_dir(instance):
os.makedirs(staging_dir_path, exist_ok=True)
instance.data.update({
"stagingDir": staging_dir_path,
"stagingDir_persistent": staging_dir_info.persistent,
"stagingDir_custom": staging_dir_info.custom
"stagingDir_persistent": staging_dir_info.is_persistent,
"stagingDir_is_custom": staging_dir_info.is_custom
})
return staging_dir_path

View file

@ -1,3 +1,6 @@
import logging
import warnings
from typing import Optional, Dict, Any
from dataclasses import dataclass
from ayon_core.lib import Logger, filter_profiles
@ -11,21 +14,41 @@ from .tempdir import get_temp_dir
@dataclass
class StagingDir:
directory: str
persistent: bool
custom: bool # Whether the staging dir is a custom staging dir
is_persistent: bool
# Whether the staging dir is a custom staging dir
is_custom: bool
def __setattr__(self, key, value):
if key == "persistent":
warnings.warn(
"'StagingDir.persistent' is deprecated."
" Use 'StagingDir.is_persistent' instead.",
DeprecationWarning
)
key = "is_persistent"
super().__setattr__(key, value)
@property
def persistent(self):
warnings.warn(
"'StagingDir.persistent' is deprecated."
" Use 'StagingDir.is_persistent' instead.",
DeprecationWarning
)
return self.is_persistent
def get_staging_dir_config(
project_name,
task_type,
task_name,
product_type,
product_name,
host_name,
project_settings=None,
anatomy=None,
log=None,
):
project_name: str,
task_type: Optional[str],
task_name: Optional[str],
product_type: str,
product_name: str,
host_name: str,
project_settings: Optional[Dict[str, Any]] = None,
anatomy: Optional[Anatomy] = None,
log: Optional[logging.Logger] = None,
) -> Optional[Dict[str, Any]]:
"""Get matching staging dir profile.
Args:
@ -76,7 +99,6 @@ def get_staging_dir_config(
# get template from template name
template_name = profile["template_name"]
_validate_template_name(project_name, template_name, anatomy)
template = anatomy.get_template_item("staging", template_name)
@ -93,35 +115,22 @@ def get_staging_dir_config(
return {"template": template, "persistence": data_persistence}
def _validate_template_name(project_name, template_name, anatomy):
"""Check that staging dir section with appropriate template exist.
Raises:
ValueError - if misconfigured template
"""
if template_name not in anatomy.templates["staging"]:
raise ValueError(
f'Anatomy of project "{project_name}" does not have set'
f' "{template_name}" template key at Staging Dir category!'
)
def get_staging_dir_info(
project_entity,
folder_entity,
task_entity,
product_type,
product_name,
host_name,
anatomy=None,
project_settings=None,
template_data=None,
always_return_path=True,
force_tmp_dir=False,
logger=None,
prefix=None,
suffix=None,
):
project_entity: Dict[str, Any],
folder_entity: Optional[Dict[str, Any]],
task_entity: Optional[Dict[str, Any]],
product_type: str,
product_name: str,
host_name: str,
anatomy: Optional[Anatomy] = None,
project_settings: Optional[Dict[str, Any]] = None,
template_data: Optional[Dict[str, Any]] = None,
always_return_path: bool = True,
force_tmp_dir: bool = False,
logger: Optional[logging.Logger] = None,
prefix: Optional[str] = None,
suffix: Optional[str] = None,
) -> Optional[StagingDir]:
"""Get staging dir info data.
If `force_temp` is set, staging dir will be created as tempdir.
@ -161,11 +170,15 @@ def get_staging_dir_info(
)
if force_tmp_dir:
return get_temp_dir(
project_name=project_entity["name"],
anatomy=anatomy,
prefix=prefix,
suffix=suffix,
return StagingDir(
get_temp_dir(
project_name=project_entity["name"],
anatomy=anatomy,
prefix=prefix,
suffix=suffix,
),
is_persistent=False,
is_custom=False
)
# making few queries to database
@ -205,8 +218,8 @@ def get_staging_dir_info(
dir_template = staging_dir_config["template"]["directory"]
return StagingDir(
dir_template.format_strict(ctx_data),
persistent=staging_dir_config["persistence"],
custom=True
is_persistent=staging_dir_config["persistence"],
is_custom=True
)
# no config found but force an output
@ -218,8 +231,8 @@ def get_staging_dir_info(
prefix=prefix,
suffix=suffix,
),
persistent=False,
custom=False
is_persistent=False,
is_custom=False
)
return None

View file

@ -24,6 +24,7 @@ class CollectCoreJobEnvVars(pyblish.api.ContextPlugin):
# NOTE we should use 'context.data["user"]' but that has higher
# order.
("AYON_USERNAME", get_ayon_username()),
("AYON_HOST_NAME", context.data["hostName"]),
):
if value:
self.log.debug(f"Setting job env: {key}: {value}")

View file

@ -178,7 +178,10 @@ class CollectOtioSubsetResources(
repre = self._create_representation(
frame_start, frame_end, collection=collection)
if "review" in instance.data["families"]:
if (
not instance.data.get("otioReviewClips")
and "review" in instance.data["families"]
):
review_repre = self._create_representation(
frame_start, frame_end, collection=collection,
delete=True, review=True)
@ -197,7 +200,10 @@ class CollectOtioSubsetResources(
repre = self._create_representation(
frame_start, frame_end, file=filename, trim=_trim)
if "review" in instance.data["families"]:
if (
not instance.data.get("otioReviewClips")
and "review" in instance.data["families"]
):
review_repre = self._create_representation(
frame_start, frame_end,
file=filename, delete=True, review=True)

View file

@ -209,13 +209,9 @@ class ExtractOTIOReview(
# File sequence way
if is_sequence:
# 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,
processing_range,
)
input_fps = processing_range.start_time.rate

View file

@ -197,7 +197,7 @@ class ConvertorItemsGroupWidget(BaseGroupWidget):
else:
widget = ConvertorItemCardWidget(item, self)
widget.selected.connect(self._on_widget_selection)
widget.double_clicked(self.double_clicked)
widget.double_clicked.connect(self.double_clicked)
self._widgets_by_id[item.id] = widget
self._content_layout.insertWidget(widget_idx, widget)
widget_idx += 1

View file

@ -136,6 +136,8 @@ class FilesWidget(QtWidgets.QWidget):
# Initial setup
workarea_btn_open.setEnabled(False)
workarea_btn_browse.setEnabled(False)
workarea_btn_save.setEnabled(False)
published_btn_copy_n_open.setEnabled(False)
published_btn_change_context.setEnabled(False)
published_btn_cancel.setVisible(False)
@ -278,8 +280,9 @@ class FilesWidget(QtWidgets.QWidget):
self._published_btn_change_context.setEnabled(enabled)
def _update_workarea_btns_state(self):
enabled = self._is_save_enabled
enabled = self._is_save_enabled and self._valid_selected_context
self._workarea_btn_save.setEnabled(enabled)
self._workarea_btn_browse.setEnabled(self._valid_selected_context)
def _on_published_repre_changed(self, event):
self._valid_representation_id = event["representation_id"] is not None
@ -294,6 +297,7 @@ class FilesWidget(QtWidgets.QWidget):
and self._selected_task_id is not None
)
self._update_published_btns_state()
self._update_workarea_btns_state()
def _on_published_save_clicked(self):
result = self._exec_save_as_dialog()

View file

@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
"""Package declaring AYON addon 'core' version."""
__version__ = "1.0.12+dev"
__version__ = "1.0.13+dev"

View file

@ -1,6 +1,6 @@
name = "core"
title = "Core"
version = "1.0.12+dev"
version = "1.0.13+dev"
client_dir = "ayon_core"

View file

@ -5,7 +5,7 @@
[tool.poetry]
name = "ayon-core"
version = "1.0.12+dev"
version = "1.0.13+dev"
description = ""
authors = ["Ynput Team <team@ynput.io>"]
readme = "README.md"

View file

@ -1008,8 +1008,8 @@ DEFAULT_PUBLISH_VALUES = {
{"name": "model", "order": 100},
{"name": "assembly", "order": 150},
{"name": "groom", "order": 175},
{"name": "look", "order": 300},
{"name": "rig", "order": 100},
{"name": "look", "order": 200},
{"name": "rig", "order": 300},
# Shot layers
{"name": "layout", "order": 200},
{"name": "animation", "order": 300},

View file

@ -0,0 +1,51 @@
{
"OTIO_SCHEMA": "Clip.2",
"metadata": {},
"name": "",
"source_range": {
"OTIO_SCHEMA": "TimeRange.1",
"duration": {
"OTIO_SCHEMA": "RationalTime.1",
"rate": 23.976,
"value": 108.0
},
"start_time": {
"OTIO_SCHEMA": "RationalTime.1",
"rate": 23.976,
"value": 883159.0
}
},
"effects": [],
"markers": [],
"enabled": true,
"media_references": {
"DEFAULT_MEDIA": {
"OTIO_SCHEMA": "ImageSequenceReference.1",
"metadata": {},
"name": "",
"available_range": {
"OTIO_SCHEMA": "TimeRange.1",
"duration": {
"OTIO_SCHEMA": "RationalTime.1",
"rate": 24.0,
"value": 755.0
},
"start_time": {
"OTIO_SCHEMA": "RationalTime.1",
"rate": 24.0,
"value": 883750.0
}
},
"available_image_bounds": null,
"target_url_base": "/mnt/jobs/yahoo_theDog_1132/IN/FOOTAGE/SCANS_LINEAR/Panasonic Rec 709 to ACESCG/Panasonic P2 /A001_S001_S001_T004/",
"name_prefix": "A001_S001_S001_T004.",
"name_suffix": ".exr",
"start_frame": 883750,
"frame_step": 1,
"rate": 1.0,
"frame_zero_padding": 0,
"missing_frame_policy": "error"
}
},
"active_media_reference_key": "DEFAULT_MEDIA"
}

View file

@ -0,0 +1,174 @@
{
"OTIO_SCHEMA": "Clip.2",
"metadata": {},
"name": "Main088sh110",
"source_range": {
"OTIO_SCHEMA": "TimeRange.1",
"duration": {
"OTIO_SCHEMA": "RationalTime.1",
"rate": 23.976024627685547,
"value": 82.0
},
"start_time": {
"OTIO_SCHEMA": "RationalTime.1",
"rate": 23.976024627685547,
"value": 1937905.9905694576
}
},
"effects": [],
"markers": [
{
"OTIO_SCHEMA": "Marker.2",
"metadata": {
"applieswhole": "1",
"hiero_source_type": "TrackItem",
"json_metadata": "{\"hiero_sub_products\": {\"io.ayon.creators.hiero.shot\": {\"id\": \"pyblish.avalon.instance\", \"productType\": \"shot\", \"productName\": \"shotMain\", \"active\": true, \"creator_identifier\": \"io.ayon.creators.hiero.shot\", \"variant\": \"main\", \"folderPath\": \"/shots/088/Main088sh110\", \"task\": null, \"clip_index\": \"70C9FA86-76A5-A045-A004-3158FB3F27C5\", \"hierarchy\": \"shots/088\", \"folder\": \"shots\", \"episode\": \"404\", \"sequence\": \"088\", \"track\": \"Main\", \"shot\": \"sh110\", \"reviewableSource\": \"Reference\", \"sourceResolution\": false, \"workfileFrameStart\": 1009, \"handleStart\": 8, \"handleEnd\": 8, \"parents\": [{\"entity_type\": \"folder\", \"folder_type\": \"folder\", \"entity_name\": \"shots\"}, {\"entity_type\": \"sequence\", \"folder_type\": \"sequence\", \"entity_name\": \"088\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"404\", \"sequence\": \"088\", \"track\": \"Main\"}, \"heroTrack\": true, \"uuid\": \"8b0d1db8-7094-48ba-b2cd-df0d43cfffda\", \"reviewTrack\": \"Reference\", \"review\": true, \"folderName\": \"Main088sh110\", \"label\": \"/shots/088/Main088sh110 shotMain\", \"newHierarchyIntegration\": true, \"instance_id\": \"f6b7f12c-f3a8-44fd-b4e4-acc63ed80bb1\", \"creator_attributes\": {\"workfileFrameStart\": 1009, \"handleStart\": 8, \"handleEnd\": 8, \"frameStart\": 1009, \"frameEnd\": 1091, \"clipIn\": 80, \"clipOut\": 161, \"clipDuration\": 82, \"sourceIn\": 8.0, \"sourceOut\": 89.0, \"fps\": \"from_selection\"}, \"publish_attributes\": {}}, \"io.ayon.creators.hiero.plate\": {\"id\": \"pyblish.avalon.instance\", \"productType\": \"plate\", \"productName\": \"plateMain\", \"active\": true, \"creator_identifier\": \"io.ayon.creators.hiero.plate\", \"variant\": \"Main\", \"folderPath\": \"/shots/088/Main088sh110\", \"task\": null, \"clip_index\": \"70C9FA86-76A5-A045-A004-3158FB3F27C5\", \"hierarchy\": \"shots/088\", \"folder\": \"shots\", \"episode\": \"404\", \"sequence\": \"088\", \"track\": \"Main\", \"shot\": \"sh110\", \"reviewableSource\": \"Reference\", \"sourceResolution\": false, \"workfileFrameStart\": 1009, \"handleStart\": 8, \"handleEnd\": 8, \"parents\": [{\"entity_type\": \"folder\", \"folder_type\": \"folder\", \"entity_name\": \"shots\"}, {\"entity_type\": \"sequence\", \"folder_type\": \"sequence\", \"entity_name\": \"088\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"404\", \"sequence\": \"088\", \"track\": \"Main\"}, \"heroTrack\": true, \"uuid\": \"8b0d1db8-7094-48ba-b2cd-df0d43cfffda\", \"reviewTrack\": \"Reference\", \"review\": true, \"folderName\": \"Main088sh110\", \"parent_instance_id\": \"f6b7f12c-f3a8-44fd-b4e4-acc63ed80bb1\", \"label\": \"/shots/088/Main088sh110 plateMain\", \"newHierarchyIntegration\": true, \"instance_id\": \"64b54c11-7ab1-45ef-b156-9ed5d5552b9b\", \"creator_attributes\": {\"parentInstance\": \"/shots/088/Main088sh110 shotMain\", \"review\": true, \"reviewableSource\": \"Reference\"}, \"publish_attributes\": {}}}, \"clip_index\": \"70C9FA86-76A5-A045-A004-3158FB3F27C5\"}",
"label": "AYONdata_6b797112",
"note": "AYON data container"
},
"name": "AYONdata_6b797112",
"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
}
},
"comment": ""
}
],
"enabled": true,
"media_references": {
"DEFAULT_MEDIA": {
"OTIO_SCHEMA": "ImageSequenceReference.1",
"metadata": {
"ayon.source.colorspace": "Input - Sony - Linear - Venice S-Gamut3.Cine",
"ayon.source.height": 2160,
"ayon.source.pixelAspect": 1.0,
"ayon.source.width": 4096,
"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": "Input - Sony - Linear - Venice S-Gamut3.Cine",
"foundry.source.duration": "98",
"foundry.source.filename": "409_083_0015.%04d.exr 1001-1098",
"foundry.source.filesize": "",
"foundry.source.fragments": "98",
"foundry.source.framerate": "23.98",
"foundry.source.fullpath": "",
"foundry.source.height": "2160",
"foundry.source.layers": "colour",
"foundry.source.path": "X:/prj/AYON_CIRCUIT_TEST/data/OBX_20240729_P159_DOG_409/EXR/409_083_0015/409_083_0015.%04d.exr 1001-1098",
"foundry.source.pixelAspect": "1",
"foundry.source.pixelAspectRatio": "",
"foundry.source.pixelformat": "RGBA (Float16) Open Color IO space: 368",
"foundry.source.reelID": "",
"foundry.source.resolution": "",
"foundry.source.samplerate": "Invalid",
"foundry.source.shortfilename": "409_083_0015.%04d.exr 1001-1098",
"foundry.source.shot": "",
"foundry.source.shotDate": "",
"foundry.source.startTC": "",
"foundry.source.starttime": "1001",
"foundry.source.timecode": "1937896",
"foundry.source.umid": "4b3e13b3-e465-4df4-cb1f-257091b63815",
"foundry.source.umidOriginator": "foundry.source.umid",
"foundry.source.width": "4096",
"foundry.timeline.colorSpace": "Input - Sony - Linear - Venice S-Gamut3.Cine",
"foundry.timeline.duration": "98",
"foundry.timeline.framerate": "23.98",
"foundry.timeline.outputformat": "",
"foundry.timeline.poster": "0",
"foundry.timeline.posterLayer": "colour",
"foundry.timeline.readParams": "AAAAAQAAAAAAAAAFAAAACmNvbG9yc3BhY2UAAAAFaW50MzIAAABqAAAAC2VkZ2VfcGl4ZWxzAAAABWludDMyAAAAAAAAABFpZ25vcmVfcGFydF9uYW1lcwAAAARib29sAAAAAAhub3ByZWZpeAAAAARib29sAAAAAB5vZmZzZXRfbmVnYXRpdmVfZGlzcGxheV93aW5kb3cAAAAEYm9vbAE=",
"foundry.timeline.samplerate": "Invalid",
"isSequence": true,
"media.exr.camera_camera_type": "AXS-R7",
"media.exr.camera_fps": "23.976",
"media.exr.camera_id": "MPC-3610 0010762 Version6.30",
"media.exr.camera_iso": "2500",
"media.exr.camera_lens_type": "Unknown",
"media.exr.camera_monitor_space": "OBX4_LUT_1_Night.cube",
"media.exr.camera_nd_filter": "1",
"media.exr.camera_roll_angle": "0.3",
"media.exr.camera_shutter_angle": "180.0",
"media.exr.camera_shutter_speed": "0.0208333",
"media.exr.camera_shutter_type": "Speed and Angle",
"media.exr.camera_sl_num": "00011434",
"media.exr.camera_tilt_angle": "-7.4",
"media.exr.camera_type": "Sony",
"media.exr.camera_white_kelvin": "3200",
"media.exr.channels": "B:{1 0 1 1},G:{1 0 1 1},R:{1 0 1 1}",
"media.exr.clip_details_codec": "F55_X-OCN_ST_4096_2160",
"media.exr.clip_details_pixel_aspect_ratio": "1",
"media.exr.clip_details_shot_frame_rate": "23.98p",
"media.exr.compression": "0",
"media.exr.compressionName": "none",
"media.exr.dataWindow": "0,0,4095,2159",
"media.exr.displayWindow": "0,0,4095,2159",
"media.exr.lineOrder": "0",
"media.exr.owner": "C272C010_240530HO",
"media.exr.pixelAspectRatio": "1",
"media.exr.screenWindowCenter": "0,0",
"media.exr.screenWindowWidth": "1",
"media.exr.tech_details_aspect_ratio": "1.8963",
"media.exr.tech_details_cdl_sat": "1",
"media.exr.tech_details_cdl_sop": "(1 1 1)(0 0 0)(1 1 1)",
"media.exr.tech_details_gamma_space": "R709 Video",
"media.exr.tech_details_par": "1",
"media.exr.type": "scanlineimage",
"media.input.bitsperchannel": "16-bit half float",
"media.input.ctime": "2024-07-30 18:51:38",
"media.input.filename": "X:/prj/AYON_CIRCUIT_TEST/data/OBX_20240729_P159_DOG_409/EXR/409_083_0015/409_083_0015.1001.exr",
"media.input.filereader": "exr",
"media.input.filesize": "53120020",
"media.input.frame": "1",
"media.input.frame_rate": "23.976",
"media.input.height": "2160",
"media.input.mtime": "2024-07-30 18:51:38",
"media.input.timecode": "22:25:45:16",
"media.input.width": "4096",
"padding": 4
},
"name": "",
"available_range": {
"OTIO_SCHEMA": "TimeRange.1",
"duration": {
"OTIO_SCHEMA": "RationalTime.1",
"rate": 23.976,
"value": 98.0
},
"start_time": {
"OTIO_SCHEMA": "RationalTime.1",
"rate": 23.976,
"value": 1937896.0
}
},
"available_image_bounds": null,
"target_url_base": "X:/prj/AYON_CIRCUIT_TEST/data/OBX_20240729_P159_DOG_409/EXR/409_083_0015\\",
"name_prefix": "409_083_0015.",
"name_suffix": ".exr",
"start_frame": 1001,
"frame_step": 1,
"rate": 23.976,
"frame_zero_padding": 4,
"missing_frame_policy": "error"
}
},
"active_media_reference_key": "DEFAULT_MEDIA"
}

View file

@ -252,48 +252,48 @@ def test_multiple_review_clips_no_gap():
'/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i '
f'C:\\no_tc{os.sep}output.%04d.tif '
'-start_number 1199 C:/result/output.%04d.jpg',
'-start_number 1198 C:/result/output.%04d.jpg',
'/path/to/ffmpeg -start_number 1000 -framerate 24.0 -i '
f'C:\\with_tc{os.sep}output.%04d.exr '
'-start_number 1300 C:/result/output.%04d.jpg',
'-start_number 1299 C:/result/output.%04d.jpg',
# Repeated 25fps tiff sequence multiple times till the end
'/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i '
f'C:\\no_tc{os.sep}output.%04d.tif '
'-start_number 1397 C:/result/output.%04d.jpg',
'-start_number 1395 C:/result/output.%04d.jpg',
'/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i '
f'C:\\no_tc{os.sep}output.%04d.tif '
'-start_number 1498 C:/result/output.%04d.jpg',
'-start_number 1496 C:/result/output.%04d.jpg',
'/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i '
f'C:\\no_tc{os.sep}output.%04d.tif '
'-start_number 1599 C:/result/output.%04d.jpg',
'-start_number 1597 C:/result/output.%04d.jpg',
'/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i '
f'C:\\no_tc{os.sep}output.%04d.tif '
'-start_number 1700 C:/result/output.%04d.jpg',
'-start_number 1698 C:/result/output.%04d.jpg',
'/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i '
f'C:\\no_tc{os.sep}output.%04d.tif '
'-start_number 1801 C:/result/output.%04d.jpg',
'-start_number 1799 C:/result/output.%04d.jpg',
'/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i '
f'C:\\no_tc{os.sep}output.%04d.tif '
'-start_number 1902 C:/result/output.%04d.jpg',
'-start_number 1900 C:/result/output.%04d.jpg',
'/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i '
f'C:\\no_tc{os.sep}output.%04d.tif '
'-start_number 2003 C:/result/output.%04d.jpg',
'-start_number 2001 C:/result/output.%04d.jpg',
'/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i '
f'C:\\no_tc{os.sep}output.%04d.tif '
'-start_number 2104 C:/result/output.%04d.jpg',
'-start_number 2102 C:/result/output.%04d.jpg',
'/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i '
f'C:\\no_tc{os.sep}output.%04d.tif '
'-start_number 2205 C:/result/output.%04d.jpg'
'-start_number 2203 C:/result/output.%04d.jpg'
]
assert calls == expected

View file

@ -64,6 +64,28 @@ def test_movie_embedded_tc_handle():
)
def test_movie_23fps_qt_embedded_tc():
"""
Movie clip (embedded timecode 1h)
available_range = 1937896-1937994 23.976fps
source_range = 1937905-1937987 23.97602462768554fps
"""
expected_data = {
'mediaIn': 1009,
'mediaOut': 1090,
'handleStart': 8,
'handleEnd': 8,
'speed': 1.0
}
_check_expected_retimed_values(
"qt_23.976_embedded_long_tc.json",
expected_data,
handle_start=8,
handle_end=8,
)
def test_movie_retime_effect():
"""
Movie clip (embedded timecode 1h)
@ -187,3 +209,29 @@ def test_img_sequence_conform_to_23_976fps():
handle_start=0,
handle_end=8,
)
def test_img_sequence_conform_from_24_to_23_976fps():
"""
Img sequence clip
available files = 883750-884504 24fps
source_range = 883159-883267 23.976fps
This test ensures such entries do not trigger
the legacy Hiero export compatibility.
"""
expected_data = {
'mediaIn': 884043,
'mediaOut': 884150,
'handleStart': 0,
'handleEnd': 0,
'speed': 1.0
}
_check_expected_retimed_values(
"img_seq_24_to_23.976_no_legacy.json",
expected_data,
handle_start=0,
handle_end=0,
)