mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-25 05:14:40 +01:00
🎨 enhance range validations
This commit is contained in:
parent
265b1816e8
commit
5e0509ca48
2 changed files with 205 additions and 41 deletions
|
|
@ -1,6 +1,8 @@
|
|||
"""Content traits for the pipeline."""
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
|
||||
# TC003 is there because Path in TYPECHECKING will fail in tests
|
||||
from pathlib import Path # noqa: TC003
|
||||
from typing import ClassVar, Optional
|
||||
|
|
@ -8,7 +10,7 @@ from typing import ClassVar, Optional
|
|||
from pydantic import Field
|
||||
|
||||
from .representation import Representation
|
||||
from .time import FrameRanged, Sequence
|
||||
from .time import FrameRanged, Handles, Sequence
|
||||
from .trait import (
|
||||
MissingTraitError,
|
||||
TraitBase,
|
||||
|
|
@ -125,20 +127,55 @@ class FileLocations(TraitBase):
|
|||
# If there are no file paths, we can't validate
|
||||
msg = "No file locations defined (empty list)"
|
||||
raise TraitValidationError(self.name, msg)
|
||||
if representation.contains_trait(FrameRanged):
|
||||
self._validate_frame_range(representation)
|
||||
|
||||
def _validate_frame_range(self, representation: Representation) -> None:
|
||||
"""Validate the frame range against the file paths.
|
||||
|
||||
If the representation contains a FrameRanged trait, this method will
|
||||
validate the frame range against the file paths. If the frame range
|
||||
does not match the file paths, the trait is invalid. It takes into
|
||||
account the Handles and Sequence traits.
|
||||
|
||||
Args:
|
||||
representation (Representation): Representation to validate.
|
||||
|
||||
Raises:
|
||||
TraitValidationError: If the trait is invalid within the
|
||||
representation.
|
||||
|
||||
"""
|
||||
tmp_frame_ranged: FrameRanged = get_sequence_from_files(
|
||||
[f.file_path for f in self.file_paths])
|
||||
|
||||
frames_from_spec = None
|
||||
try:
|
||||
with contextlib.suppress(MissingTraitError):
|
||||
sequence: Sequence = representation.get_trait(Sequence)
|
||||
if sequence.frame_spec:
|
||||
frames_from_spec: list[int] = sequence.get_frame_list(
|
||||
self, sequence.frame_regex)
|
||||
|
||||
except MissingTraitError:
|
||||
# If there is no sequence trait, we can't validate it
|
||||
pass
|
||||
frame_start_with_handles, frame_end_with_handles = \
|
||||
self._get_frame_info_with_handles(representation, frames_from_spec)
|
||||
|
||||
if frame_start_with_handles \
|
||||
and tmp_frame_ranged.frame_start != frame_start_with_handles:
|
||||
# If the detected frame range does not match the combined
|
||||
# FrameRanged and Handles trait, the
|
||||
# trait is invalid.
|
||||
msg = (
|
||||
f"Frame range defined by {self.name} "
|
||||
f"({tmp_frame_ranged.frame_start}-"
|
||||
f"{tmp_frame_ranged.frame_end}) "
|
||||
"in files does not match "
|
||||
"frame range "
|
||||
f"({frame_start_with_handles}-"
|
||||
f"{frame_end_with_handles}) defined in FrameRanged trait."
|
||||
)
|
||||
|
||||
raise TraitValidationError(self.name, msg)
|
||||
|
||||
if frames_from_spec:
|
||||
if len(frames_from_spec) != len(self.file_paths) :
|
||||
# If the number of file paths does not match the frame range,
|
||||
|
|
@ -155,40 +192,94 @@ class FileLocations(TraitBase):
|
|||
# the rest is validated by Sequence validators.
|
||||
return
|
||||
|
||||
length_with_handles: int = (
|
||||
frame_end_with_handles - frame_start_with_handles + 1
|
||||
)
|
||||
|
||||
if len(self.file_paths) - 1 != \
|
||||
tmp_frame_ranged.frame_end - tmp_frame_ranged.frame_start:
|
||||
if len(self.file_paths) != length_with_handles:
|
||||
# If the number of file paths does not match the frame range,
|
||||
# the trait is invalid
|
||||
msg = (
|
||||
f"Number of file locations ({len(self.file_paths) - 1}) "
|
||||
f"Number of file locations ({len(self.file_paths)}) "
|
||||
"does not match frame range "
|
||||
f"({tmp_frame_ranged.frame_end - tmp_frame_ranged.frame_start})" # noqa: E501
|
||||
f"({length_with_handles})"
|
||||
)
|
||||
raise TraitValidationError(self.name, msg)
|
||||
|
||||
try:
|
||||
frame_ranged: FrameRanged = representation.get_trait(FrameRanged)
|
||||
frame_ranged: FrameRanged = representation.get_trait(FrameRanged)
|
||||
|
||||
if frame_ranged.frame_start != tmp_frame_ranged.frame_start or \
|
||||
frame_ranged.frame_end != tmp_frame_ranged.frame_end:
|
||||
# If the frame range does not match the sequence trait, the
|
||||
# trait is invalid. Note that we don't check the frame rate
|
||||
# because it is not stored in the file paths and is not
|
||||
# determined by `get_sequence_from_files`.
|
||||
msg = (
|
||||
"Frame range "
|
||||
f"({frame_ranged.frame_start}-{frame_ranged.frame_end}) "
|
||||
"in sequence trait does not match "
|
||||
"frame range "
|
||||
f"({tmp_frame_ranged.frame_start}-{tmp_frame_ranged.frame_end}) " # noqa: E501
|
||||
"defined in files."
|
||||
)
|
||||
raise TraitValidationError(self.name, msg)
|
||||
if frame_start_with_handles != tmp_frame_ranged.frame_start or \
|
||||
frame_end_with_handles != tmp_frame_ranged.frame_end:
|
||||
# If the frame range does not match the FrameRanged trait, the
|
||||
# trait is invalid. Note that we don't check the frame rate
|
||||
# because it is not stored in the file paths and is not
|
||||
# determined by `get_sequence_from_files`.
|
||||
msg = (
|
||||
"Frame range "
|
||||
f"({frame_ranged.frame_start}-{frame_ranged.frame_end}) "
|
||||
"in sequence trait does not match "
|
||||
"frame range "
|
||||
f"({tmp_frame_ranged.frame_start}-"
|
||||
f"{tmp_frame_ranged.frame_end}) "
|
||||
)
|
||||
raise TraitValidationError(self.name, msg)
|
||||
|
||||
except MissingTraitError:
|
||||
# If there is no frame_ranged trait, we can't validate it
|
||||
pass
|
||||
def _get_frame_info_with_handles(
|
||||
self,
|
||||
representation: Representation,
|
||||
frames_from_spec: list[int]) -> tuple[int, int]:
|
||||
"""Get the frame range with handles from the representation.
|
||||
|
||||
This will return frame start and frame end with handles calculated
|
||||
in if there actually is the Handles trait in the representation.
|
||||
|
||||
Args:
|
||||
representation (Representation): Representation to get the frame
|
||||
range from.
|
||||
frames_from_spec (list[int]): List of frames from the frame spec.
|
||||
This list is modified in place to take into
|
||||
account the handles.
|
||||
|
||||
Mutates:
|
||||
frames_from_spec: List of frames from the frame spec.
|
||||
|
||||
Returns:
|
||||
tuple[int, int]: Start and end frame with handles.
|
||||
|
||||
"""
|
||||
frame_start = frame_end = 0
|
||||
frame_start_handle = frame_end_handle = 0
|
||||
# If there is no sequence trait, we can't validate it
|
||||
if frames_from_spec and representation.contains_trait(FrameRanged):
|
||||
# if there is no FrameRanged trait (but really there should be)
|
||||
# we can use the frame range from the frame spec
|
||||
frame_start = min(frames_from_spec)
|
||||
frame_end = max(frames_from_spec)
|
||||
|
||||
# Handle the frame range
|
||||
with contextlib.suppress(MissingTraitError):
|
||||
frame_start = representation.get_trait(FrameRanged).frame_start
|
||||
frame_end = representation.get_trait(FrameRanged).frame_end
|
||||
|
||||
# Handle the handles :P
|
||||
with contextlib.suppress(MissingTraitError):
|
||||
handles: Handles = representation.get_trait(Handles)
|
||||
if not handles.inclusive:
|
||||
# if handless are exclusive, we need to adjust the frame range
|
||||
frame_start_handle = handles.frame_start_handle
|
||||
frame_end_handle = handles.frame_end_handle
|
||||
if frames_from_spec:
|
||||
frames_from_spec.extend(
|
||||
range(frame_start - frame_start_handle, frame_start)
|
||||
)
|
||||
frames_from_spec.extend(
|
||||
range(frame_end + 1, frame_end_handle + frame_end + 1)
|
||||
)
|
||||
|
||||
frame_start_with_handles = frame_start - frame_start_handle
|
||||
frame_end_with_handles = frame_end + frame_end_handle
|
||||
|
||||
return frame_start_with_handles, frame_end_with_handles
|
||||
|
||||
|
||||
class RootlessLocation(TraitBase):
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
"""Temporal (time related) traits."""
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
from enum import Enum, auto
|
||||
from typing import TYPE_CHECKING, ClassVar, Optional
|
||||
|
||||
|
|
@ -137,20 +138,62 @@ class Sequence(TraitBase):
|
|||
|
||||
# if there is FileLocations trait, run validation
|
||||
# on it as well
|
||||
try:
|
||||
from .content import FileLocations
|
||||
file_locs: FileLocations = representation.get_trait(
|
||||
FileLocations)
|
||||
file_locs.validate(representation)
|
||||
# validate if file locations on representation
|
||||
# matches the frame list (if any)
|
||||
self.validate_frame_list(file_locs)
|
||||
self.validate_frame_padding(file_locs)
|
||||
except MissingTraitError:
|
||||
pass
|
||||
|
||||
with contextlib.suppress(MissingTraitError):
|
||||
self._validate_file_locations(representation)
|
||||
|
||||
def _validate_file_locations(self, representation: Representation) -> None:
|
||||
"""Validate file locations trait.
|
||||
|
||||
If along with the Sequence trait, there is a FileLocations trait,
|
||||
then we need to validate if the file locations match the frame
|
||||
list specification.
|
||||
|
||||
Args:
|
||||
representation (Representation): Representation instance.
|
||||
|
||||
Raises:
|
||||
TraitValidationError: If file locations do not match the
|
||||
frame list specification
|
||||
|
||||
"""
|
||||
from .content import FileLocations
|
||||
file_locs: FileLocations = representation.get_trait(
|
||||
FileLocations)
|
||||
# validate if file locations on representation
|
||||
# matches the frame list (if any)
|
||||
# we need to extend the expected frames with Handles
|
||||
frame_start = None
|
||||
frame_end = None
|
||||
handles_frame_start = None
|
||||
handles_frame_end = None
|
||||
with contextlib.suppress(MissingTraitError):
|
||||
handles: Handles = representation.get_trait(Handles)
|
||||
# if handles are inclusive, they should be already
|
||||
# accounted in the FrameRaged frame spec
|
||||
if not handles.inclusive:
|
||||
handles_frame_start = handles.frame_start_handle
|
||||
handles_frame_end = handles.frame_end_handle
|
||||
with contextlib.suppress(MissingTraitError):
|
||||
frame_ranged: FrameRanged = representation.get_trait(
|
||||
FrameRanged)
|
||||
frame_start = frame_ranged.frame_start
|
||||
frame_end = frame_ranged.frame_end
|
||||
self.validate_frame_list(
|
||||
file_locs,
|
||||
frame_start,
|
||||
frame_end,
|
||||
handles_frame_start,
|
||||
handles_frame_end)
|
||||
self.validate_frame_padding(file_locs)
|
||||
|
||||
def validate_frame_list(
|
||||
self, file_locations: FileLocations) -> None:
|
||||
self,
|
||||
file_locations: FileLocations,
|
||||
frame_start: Optional[int] = None,
|
||||
frame_end: Optional[int] = None,
|
||||
handles_frame_start: Optional[int] = None,
|
||||
handles_frame_end: Optional[int] = None) -> None:
|
||||
"""Validate frame list.
|
||||
|
||||
This will take FileLocations trait and validate if the
|
||||
|
|
@ -164,6 +207,10 @@ class Sequence(TraitBase):
|
|||
|
||||
Args:
|
||||
file_locations (FileLocations): File locations trait.
|
||||
frame_start (Optional[int]): Frame start.
|
||||
frame_end (Optional[int]): Frame end.
|
||||
handles_frame_start (Optional[int]): Frame start handle.
|
||||
handles_frame_end (Optional[int]): Frame end handle.
|
||||
|
||||
Raises:
|
||||
TraitValidationError: If frame list does not match
|
||||
|
|
@ -177,6 +224,32 @@ class Sequence(TraitBase):
|
|||
file_locations, self.frame_regex)
|
||||
|
||||
expected_frames = self.list_spec_to_frames(self.frame_spec)
|
||||
if frame_start is None or frame_end is None:
|
||||
if min(expected_frames) != frame_start:
|
||||
msg = (
|
||||
"Frame start does not match the expected frame start. "
|
||||
f"Expected: {frame_start}, Found: {min(expected_frames)}"
|
||||
)
|
||||
raise TraitValidationError(self.name, msg)
|
||||
|
||||
if max(expected_frames) != frame_end:
|
||||
msg = (
|
||||
"Frame end does not match the expected frame end. "
|
||||
f"Expected: {frame_end}, Found: {max(expected_frames)}"
|
||||
)
|
||||
raise TraitValidationError(self.name, msg)
|
||||
|
||||
# we need to extend the expected frames with Handles
|
||||
if handles_frame_start is not None:
|
||||
expected_frames.extend(
|
||||
range(
|
||||
min(frames) - handles_frame_start, min(frames) + 1))
|
||||
|
||||
if handles_frame_end is not None:
|
||||
expected_frames.extend(
|
||||
range(
|
||||
max(frames), max(frames) + handles_frame_end + 1))
|
||||
|
||||
if set(frames) != set(expected_frames):
|
||||
msg = (
|
||||
"Frame list does not match the expected frames. "
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue