mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
288 lines
8 KiB
Python
288 lines
8 KiB
Python
import os
|
|
import re
|
|
import clique
|
|
from .import_utils import discover_host_vendor_module
|
|
|
|
try:
|
|
import opentimelineio as otio
|
|
from opentimelineio import opentime as _ot
|
|
except ImportError:
|
|
if not os.environ.get("AVALON_APP"):
|
|
raise
|
|
otio = discover_host_vendor_module("opentimelineio")
|
|
_ot = discover_host_vendor_module("opentimelineio.opentime")
|
|
|
|
|
|
def otio_range_to_frame_range(otio_range):
|
|
start = _ot.to_frames(
|
|
otio_range.start_time, otio_range.start_time.rate)
|
|
end = start + _ot.to_frames(
|
|
otio_range.duration, otio_range.duration.rate) - 1
|
|
return start, end
|
|
|
|
|
|
def otio_range_with_handles(otio_range, instance):
|
|
handle_start = instance.data["handleStart"]
|
|
handle_end = instance.data["handleEnd"]
|
|
handles_duration = handle_start + handle_end
|
|
fps = float(otio_range.start_time.rate)
|
|
start = _ot.to_frames(otio_range.start_time, fps)
|
|
duration = _ot.to_frames(otio_range.duration, fps)
|
|
|
|
return _ot.TimeRange(
|
|
start_time=_ot.RationalTime((start - handle_start), fps),
|
|
duration=_ot.RationalTime((duration + handles_duration), fps)
|
|
)
|
|
|
|
|
|
def is_overlapping_otio_ranges(test_otio_range, main_otio_range, strict=False):
|
|
test_start, test_end = otio_range_to_frame_range(test_otio_range)
|
|
main_start, main_end = otio_range_to_frame_range(main_otio_range)
|
|
covering_exp = bool(
|
|
(test_start <= main_start) and (test_end >= main_end)
|
|
)
|
|
inside_exp = bool(
|
|
(test_start >= main_start) and (test_end <= main_end)
|
|
)
|
|
overlaying_right_exp = bool(
|
|
(test_start <= main_end) and (test_end >= main_end)
|
|
)
|
|
overlaying_left_exp = bool(
|
|
(test_end >= main_start) and (test_start <= main_start)
|
|
)
|
|
|
|
if not strict:
|
|
return any((
|
|
covering_exp,
|
|
inside_exp,
|
|
overlaying_right_exp,
|
|
overlaying_left_exp
|
|
))
|
|
else:
|
|
return covering_exp
|
|
|
|
|
|
def convert_to_padded_path(path, padding):
|
|
"""
|
|
Return correct padding in sequence string
|
|
|
|
Args:
|
|
path (str): path url or simple file name
|
|
padding (int): number of padding
|
|
|
|
Returns:
|
|
type: string with reformated path
|
|
|
|
Example:
|
|
convert_to_padded_path("plate.%d.exr") > plate.%04d.exr
|
|
|
|
"""
|
|
if "%d" in path:
|
|
path = re.sub("%d", "%0{padding}d".format(padding=padding), path)
|
|
return path
|
|
|
|
|
|
def trim_media_range(media_range, source_range):
|
|
"""
|
|
Trim input media range with clip source range.
|
|
|
|
Args:
|
|
media_range (otio._ot._ot.TimeRange): available range of media
|
|
source_range (otio._ot._ot.TimeRange): clip required range
|
|
|
|
Returns:
|
|
otio._ot._ot.TimeRange: trimmed media range
|
|
|
|
"""
|
|
rw_media_start = _ot.RationalTime(
|
|
media_range.start_time.value + source_range.start_time.value,
|
|
media_range.start_time.rate
|
|
)
|
|
rw_media_duration = _ot.RationalTime(
|
|
source_range.duration.value,
|
|
media_range.duration.rate
|
|
)
|
|
return _ot.TimeRange(
|
|
rw_media_start, rw_media_duration)
|
|
|
|
|
|
def range_from_frames(start, duration, fps):
|
|
"""
|
|
Returns otio time range.
|
|
|
|
Args:
|
|
start (int): frame start
|
|
duration (int): frame duration
|
|
fps (float): frame range
|
|
|
|
Returns:
|
|
otio._ot._ot.TimeRange: crated range
|
|
|
|
"""
|
|
return _ot.TimeRange(
|
|
_ot.RationalTime(start, fps),
|
|
_ot.RationalTime(duration, fps)
|
|
)
|
|
|
|
|
|
def frames_to_secons(frames, framerate):
|
|
"""
|
|
Returning secons.
|
|
|
|
Args:
|
|
frames (int): frame
|
|
framerate (flaot): frame rate
|
|
|
|
Returns:
|
|
float: second value
|
|
|
|
"""
|
|
rt = _ot.from_frames(frames, framerate)
|
|
return _ot.to_seconds(rt)
|
|
|
|
|
|
def frames_to_timecode(frames, framerate):
|
|
rt = _ot.from_frames(frames, framerate)
|
|
return _ot.to_timecode(rt)
|
|
|
|
|
|
def make_sequence_collection(path, otio_range, metadata):
|
|
"""
|
|
Make collection from path otio range and otio metadata.
|
|
|
|
Args:
|
|
path (str): path to image sequence with `%d`
|
|
otio_range (otio._ot._ot.TimeRange): range to be used
|
|
metadata (dict): data where padding value can be found
|
|
|
|
Returns:
|
|
list: dir_path (str): path to sequence, collection object
|
|
|
|
"""
|
|
if "%" not in path:
|
|
return None
|
|
file_name = os.path.basename(path)
|
|
dir_path = os.path.dirname(path)
|
|
head = file_name.split("%")[0]
|
|
tail = os.path.splitext(file_name)[-1]
|
|
first, last = otio_range_to_frame_range(otio_range)
|
|
collection = clique.Collection(
|
|
head=head, tail=tail, padding=metadata["padding"])
|
|
collection.indexes.update([i for i in range(first, (last + 1))])
|
|
return dir_path, collection
|
|
|
|
|
|
def _sequence_resize(source, length):
|
|
step = float(len(source) - 1) / (length - 1)
|
|
for i in range(length):
|
|
low, ratio = divmod(i * step, 1)
|
|
high = low + 1 if ratio > 0 else low
|
|
yield (1 - ratio) * source[int(low)] + ratio * source[int(high)]
|
|
|
|
|
|
def get_media_range_with_retimes(otio_clip, handle_start, handle_end):
|
|
source_range = otio_clip.source_range
|
|
available_range = otio_clip.available_range()
|
|
media_in = available_range.start_time.value
|
|
media_out = available_range.end_time_inclusive().value
|
|
|
|
# modifiers
|
|
time_scalar = 1.
|
|
offset_in = 0
|
|
offset_out = 0
|
|
time_warp_nodes = []
|
|
|
|
# Check for speed effects and adjust playback speed accordingly
|
|
for effect in otio_clip.effects:
|
|
if isinstance(effect, otio.schema.LinearTimeWarp):
|
|
time_scalar = effect.time_scalar
|
|
|
|
elif isinstance(effect, otio.schema.FreezeFrame):
|
|
# For freeze frame, playback speed must be set after range
|
|
time_scalar = 0.
|
|
|
|
elif isinstance(effect, otio.schema.TimeEffect):
|
|
# For freeze frame, playback speed must be set after range
|
|
name = effect.name
|
|
effect_name = effect.effect_name
|
|
if "TimeWarp" not in effect_name:
|
|
continue
|
|
metadata = effect.metadata
|
|
lookup = metadata.get("lookup")
|
|
if not lookup:
|
|
continue
|
|
|
|
# time warp node
|
|
tw_node = {
|
|
"Class": "TimeWarp",
|
|
"name": name
|
|
}
|
|
tw_node.update(metadata)
|
|
|
|
# get first and last frame offsets
|
|
offset_in += lookup[0]
|
|
offset_out += lookup[-1]
|
|
|
|
# add to timewarp nodes
|
|
time_warp_nodes.append(tw_node)
|
|
|
|
# multiply by time scalar
|
|
offset_in *= time_scalar
|
|
offset_out *= time_scalar
|
|
|
|
# filip offset if reversed speed
|
|
if time_scalar < 0:
|
|
_offset_in = offset_out
|
|
_offset_out = offset_in
|
|
offset_in = _offset_in
|
|
offset_out = _offset_out
|
|
|
|
# scale handles
|
|
handle_start *= abs(time_scalar)
|
|
handle_end *= abs(time_scalar)
|
|
|
|
# filip handles if reversed speed
|
|
if time_scalar < 0:
|
|
_handle_start = handle_end
|
|
_handle_end = handle_start
|
|
handle_start = _handle_start
|
|
handle_end = _handle_end
|
|
|
|
source_in = source_range.start_time.value
|
|
|
|
media_in_trimmed = (
|
|
media_in + source_in + offset_in)
|
|
media_out_trimmed = (
|
|
media_in + source_in + (
|
|
((source_range.duration.value - 1) * abs(
|
|
time_scalar)) + offset_out))
|
|
|
|
# calculate available hanles
|
|
if (media_in_trimmed - media_in) < handle_start:
|
|
handle_start = (media_in_trimmed - media_in)
|
|
if (media_out - media_out_trimmed) < handle_end:
|
|
handle_end = (media_out - media_out_trimmed)
|
|
|
|
# create version data
|
|
version_data = {
|
|
"versionData": {
|
|
"retime": True,
|
|
"speed": time_scalar,
|
|
"timewarps": time_warp_nodes,
|
|
"handleStart": handle_start,
|
|
"handleEnd": handle_end
|
|
}
|
|
}
|
|
|
|
returning_dict = {
|
|
"mediaIn": media_in_trimmed,
|
|
"mediaOut": media_out_trimmed,
|
|
"handleStart": handle_start,
|
|
"handleEnd": handle_end
|
|
}
|
|
|
|
# add version data only if retime
|
|
if time_warp_nodes or time_scalar != 1.:
|
|
returning_dict.update(version_data)
|
|
|
|
return returning_dict
|