mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
- `frameRate` to `fps` - `startFrame` to `frameStart` - `endFrame` to `frameEnd` - `fstart` to `frameStart` - `fend` to `frameEnd` - `handle_start` to `handleStart` - `handle_end` to `handleEnd` - `resolution_width` to `resolutionWidth` - `resolution_height` to `resolutionHeight` - `pixel_aspect` to `pixelAspect`
259 lines
8.7 KiB
Python
259 lines
8.7 KiB
Python
import os
|
|
import contextlib
|
|
|
|
from avalon import api
|
|
import avalon.io as io
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def preserve_inputs(tool, inputs):
|
|
"""Preserve the tool's inputs after context"""
|
|
|
|
comp = tool.Comp()
|
|
|
|
values = {}
|
|
for name in inputs:
|
|
tool_input = getattr(tool, name)
|
|
value = tool_input[comp.TIME_UNDEFINED]
|
|
values[name] = value
|
|
|
|
try:
|
|
yield
|
|
finally:
|
|
for name, value in values.items():
|
|
tool_input = getattr(tool, name)
|
|
tool_input[comp.TIME_UNDEFINED] = value
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def preserve_trim(loader, log=None):
|
|
"""Preserve the relative trim of the Loader tool.
|
|
|
|
This tries to preserve the loader's trim (trim in and trim out) after
|
|
the context by reapplying the "amount" it trims on the clip's length at
|
|
start and end.
|
|
|
|
"""
|
|
|
|
# Get original trim as amount of "trimming" from length
|
|
time = loader.Comp().TIME_UNDEFINED
|
|
length = loader.GetAttrs()["TOOLIT_Clip_Length"][1] - 1
|
|
trim_from_start = loader["ClipTimeStart"][time]
|
|
trim_from_end = length - loader["ClipTimeEnd"][time]
|
|
|
|
try:
|
|
yield
|
|
finally:
|
|
|
|
length = loader.GetAttrs()["TOOLIT_Clip_Length"][1] - 1
|
|
if trim_from_start > length:
|
|
trim_from_start = length
|
|
if log:
|
|
log.warning("Reducing trim in to %d "
|
|
"(because of less frames)" % trim_from_start)
|
|
|
|
remainder = length - trim_from_start
|
|
if trim_from_end > remainder:
|
|
trim_from_end = remainder
|
|
if log:
|
|
log.warning("Reducing trim in to %d "
|
|
"(because of less frames)" % trim_from_end)
|
|
|
|
loader["ClipTimeStart"][time] = trim_from_start
|
|
loader["ClipTimeEnd"][time] = length - trim_from_end
|
|
|
|
|
|
def loader_shift(loader, frame, relative=True):
|
|
"""Shift global in time by i preserving duration
|
|
|
|
This moves the loader by i frames preserving global duration. When relative
|
|
is False it will shift the global in to the start frame.
|
|
|
|
Args:
|
|
loader (tool): The fusion loader tool.
|
|
frame (int): The amount of frames to move.
|
|
relative (bool): When True the shift is relative, else the shift will
|
|
change the global in to frame.
|
|
|
|
Returns:
|
|
int: The resulting relative frame change (how much it moved)
|
|
|
|
"""
|
|
comp = loader.Comp()
|
|
time = comp.TIME_UNDEFINED
|
|
|
|
old_in = loader["GlobalIn"][time]
|
|
old_out = loader["GlobalOut"][time]
|
|
|
|
if relative:
|
|
shift = frame
|
|
else:
|
|
shift = frame - old_in
|
|
|
|
# Shifting global in will try to automatically compensate for the change
|
|
# in the "ClipTimeStart" and "HoldFirstFrame" inputs, so we preserve those
|
|
# input values to "just shift" the clip
|
|
with preserve_inputs(loader, inputs=["ClipTimeStart",
|
|
"ClipTimeEnd",
|
|
"HoldFirstFrame",
|
|
"HoldLastFrame"]):
|
|
|
|
# GlobalIn cannot be set past GlobalOut or vice versa
|
|
# so we must apply them in the order of the shift.
|
|
if shift > 0:
|
|
loader["GlobalOut"][time] = old_out + shift
|
|
loader["GlobalIn"][time] = old_in + shift
|
|
else:
|
|
loader["GlobalIn"][time] = old_in + shift
|
|
loader["GlobalOut"][time] = old_out + shift
|
|
|
|
return int(shift)
|
|
|
|
|
|
class FusionLoadSequence(api.Loader):
|
|
"""Load image sequence into Fusion"""
|
|
|
|
families = ["imagesequence"]
|
|
representations = ["*"]
|
|
|
|
label = "Load sequence"
|
|
order = -10
|
|
icon = "code-fork"
|
|
color = "orange"
|
|
|
|
def load(self, context, name, namespace, data):
|
|
|
|
from avalon.fusion import (
|
|
imprint_container,
|
|
get_current_comp,
|
|
comp_lock_and_undo_chunk
|
|
)
|
|
|
|
# Fallback to asset name when namespace is None
|
|
if namespace is None:
|
|
namespace = context['asset']['name']
|
|
|
|
# Use the first file for now
|
|
path = self._get_first_image(self.fname)
|
|
|
|
# Create the Loader with the filename path set
|
|
comp = get_current_comp()
|
|
with comp_lock_and_undo_chunk(comp, "Create Loader"):
|
|
|
|
args = (-32768, -32768)
|
|
tool = comp.AddTool("Loader", *args)
|
|
tool["Clip"] = path
|
|
|
|
# Set global in point to start frame (if in version.data)
|
|
start = context["version"]["data"].get("frameStart", None)
|
|
if start is not None:
|
|
loader_shift(tool, start, relative=False)
|
|
|
|
imprint_container(tool,
|
|
name=name,
|
|
namespace=namespace,
|
|
context=context,
|
|
loader=self.__class__.__name__)
|
|
|
|
def switch(self, container, representation):
|
|
self.update(container, representation)
|
|
|
|
def update(self, container, representation):
|
|
"""Update the Loader's path
|
|
|
|
Fusion automatically tries to reset some variables when changing
|
|
the loader's path to a new file. These automatic changes are to its
|
|
inputs:
|
|
- ClipTimeStart: Fusion reset to 0 if duration changes
|
|
- We keep the trim in as close as possible to the previous value.
|
|
When there are less frames then the amount of trim we reduce
|
|
it accordingly.
|
|
|
|
- ClipTimeEnd: Fusion reset to 0 if duration changes
|
|
- We keep the trim out as close as possible to the previous value
|
|
within new amount of frames after trim in (ClipTimeStart) has
|
|
been set.
|
|
|
|
- GlobalIn: Fusion reset to comp's global in if duration changes
|
|
- We change it to the "frameStart"
|
|
|
|
- GlobalEnd: Fusion resets to globalIn + length if duration changes
|
|
- We do the same like Fusion - allow fusion to take control.
|
|
|
|
- HoldFirstFrame: Fusion resets this to 0
|
|
- We preverse the value.
|
|
|
|
- HoldLastFrame: Fusion resets this to 0
|
|
- We preverse the value.
|
|
|
|
- Reverse: Fusion resets to disabled if "Loop" is not enabled.
|
|
- We preserve the value.
|
|
|
|
- Depth: Fusion resets to "Format"
|
|
- We preverse the value.
|
|
|
|
- KeyCode: Fusion resets to ""
|
|
- We preverse the value.
|
|
|
|
- TimeCodeOffset: Fusion resets to 0
|
|
- We preverse the value.
|
|
|
|
"""
|
|
|
|
from avalon.fusion import comp_lock_and_undo_chunk
|
|
|
|
tool = container["_tool"]
|
|
assert tool.ID == "Loader", "Must be Loader"
|
|
comp = tool.Comp()
|
|
|
|
root = api.get_representation_path(representation)
|
|
path = self._get_first_image(root)
|
|
|
|
# Get start frame from version data
|
|
version = io.find_one({"type": "version",
|
|
"_id": representation["parent"]})
|
|
start = version["data"].get("frameStart")
|
|
if start is None:
|
|
self.log.warning("Missing start frame for updated version"
|
|
"assuming starts at frame 0 for: "
|
|
"{} ({})".format(tool.Name, representation))
|
|
start = 0
|
|
|
|
with comp_lock_and_undo_chunk(comp, "Update Loader"):
|
|
|
|
# Update the loader's path whilst preserving some values
|
|
with preserve_trim(tool, log=self.log):
|
|
with preserve_inputs(tool,
|
|
inputs=("HoldFirstFrame",
|
|
"HoldLastFrame",
|
|
"Reverse",
|
|
"Depth",
|
|
"KeyCode",
|
|
"TimeCodeOffset")):
|
|
tool["Clip"] = path
|
|
|
|
# Set the global in to the start frame of the sequence
|
|
global_in_changed = loader_shift(tool, start, relative=False)
|
|
if global_in_changed:
|
|
# Log this change to the user
|
|
self.log.debug("Changed '%s' global in: %d" % (tool.Name,
|
|
start))
|
|
|
|
# Update the imprinted representation
|
|
tool.SetData("avalon.representation", str(representation["_id"]))
|
|
|
|
def remove(self, container):
|
|
|
|
from avalon.fusion import comp_lock_and_undo_chunk
|
|
|
|
tool = container["_tool"]
|
|
assert tool.ID == "Loader", "Must be Loader"
|
|
comp = tool.Comp()
|
|
|
|
with comp_lock_and_undo_chunk(comp, "Remove Loader"):
|
|
tool.Delete()
|
|
|
|
def _get_first_image(self, root):
|
|
"""Get first file in representation root"""
|
|
files = sorted(os.listdir(root))
|
|
return os.path.join(root, files[0])
|