Refactor Resolve media loading logic for single files and sequences.

- Refactored file path handling for importing media.
- Updated method to determine if the file is a sequence.
- Improved handling of frame numbers in file paths.
This commit is contained in:
Jakub Jezek 2024-06-12 11:17:29 +02:00
parent 21c86742d4
commit 7b1c4a8a08
No known key found for this signature in database
GPG key ID: 06DBD609ADF27FD9

View file

@ -1,17 +1,19 @@
import json
import copy
import contextlib
from pathlib import Path
from collections import defaultdict
from typing import Union, List, Optional, TypedDict
from typing import Union, List, Optional, TypedDict, Tuple
from ayon_api import version_is_latest
from ayon_core.lib import StringTemplate
from ayon_core.pipeline.colorspace import get_remapped_colorspace_to_native
from ayon_core.pipeline import (
Anatomy,
LoaderPlugin,
get_representation_path,
registered_host
)
from ayon_core.pipeline.load import get_representation_path_with_anatomy
from ayon_core.lib.transcoding import (
VIDEO_EXTENSIONS,
IMAGE_EXTENSIONS
@ -21,6 +23,9 @@ from ayon_resolve.api import lib
from ayon_resolve.api.pipeline import AVALON_CONTAINER_ID
FRAME_SPLITTER = "__frame_splitter__"
class MetadataEntry(TypedDict):
"""Metadata entry is dict with {"name": "key", "value: "value"}"""
name: str
@ -188,6 +193,7 @@ class LoadMedia(LoaderPlugin):
self.timeline = lib.get_current_timeline()
representation = context["representation"]
self._project_name = context["project"]["name"]
project = lib.get_current_project()
media_pool = project.GetMediaPool()
@ -221,10 +227,17 @@ class LoadMedia(LoaderPlugin):
media_pool.SetCurrentFolder(folder)
# Import media
path = self._get_filepath(context)
items = media_pool.ImportMedia([path])
# Resolve API: ImportMedia function requires a list of dictionaries
# with keys "FilePath", "StartIndex" and "EndIndex" for sequences
# but only string with absolute path for single files.
is_sequence, file_info = self._get_file_info(context)
if is_sequence:
items = media_pool.ImportMedia([file_info])
else:
items = media_pool.ImportMedia([file_info["FilePath"]])
assert len(items) == 1, "Must import only one media item"
item = items[0]
self._set_metadata(item, context)
@ -384,40 +397,74 @@ class LoadMedia(LoaderPlugin):
value_formatted = StringTemplate(value).format_strict(context)
media_pool_item.SetClipProperty(clip_property, value_formatted)
def _get_filepath(self, context: dict) -> Union[str, dict]:
def _get_file_info(self, context: dict) -> Tuple[bool, Union[str, dict]]:
"""Return file info for Resolve ImportMedia.
Args:
context (dict): The context dictionary.
Returns:
Tuple[bool, Union[str, dict]]: A tuple of whether the file is a
sequence and the file info dictionary.
"""
representation = context["representation"]
is_sequence = bool(representation["context"].get("frame"))
if not is_sequence:
return get_representation_path(representation)
anatomy = Anatomy(self._project_name)
version = context["version"]
# Get path to representation with correct frame number
repre_path = get_representation_path_with_anatomy(
representation, anatomy)
# Get the start and end frame of the image sequence, incl. handles
frame_start = version["data"].get("frameStart", 0)
frame_end = version["data"].get("frameEnd", 0)
handle_start = version["data"].get("handleStart", 0)
handle_end = version["data"].get("handleEnd", 0)
frame_start_handle = frame_start - handle_start
frame_end_handle = frame_end + handle_end
padding = len(representation["context"].get("frame"))
first_frame = representation["context"].get("frame")
# We format the frame number to the required token. To do so
# we in-place change the representation context data to format the path
# with that replaced data
representation = copy.deepcopy(representation)
representation["context"]["frame"] = f"%0{padding}d"
path = get_representation_path(representation)
is_sequence = False
# is not sequence
if first_frame is None:
return (
is_sequence, {"FilePath": repre_path}
)
# This is sequence
is_sequence = True
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)
file_name = f"{file_prefix}%0{frame_padding}d{file_suffix}"
abs_filepath = Path(repre_dir, file_name)
start_index = int(first_frame)
end_index = int(int(first_frame) + len(repre_files) - 1)
# See Resolve API, to import for example clip "file_[001-100].dpx":
# ImportMedia([{"FilePath":"file_%03d.dpx",
# "StartIndex":1,
# "EndIndex":100}])
return {
"FilePath": path,
"StartIndex": frame_start_handle,
"EndIndex": frame_end_handle
}
return (
is_sequence,
{
"FilePath": abs_filepath.as_posix(),
"StartIndex": start_index,
"EndIndex": end_index,
}
)
def _get_colorspace(self, representation: dict) -> Optional[str]:
"""Return Resolve native colorspace from OCIO colorspace data.