Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AY-6975 Consolidate retimes computation. #1087

Open
wants to merge 17 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 70 additions & 23 deletions client/ayon_core/pipeline/editorial.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
import re
import clique
import math

import opentimelineio as otio
from opentimelineio import opentime as _ot
Expand Down Expand Up @@ -256,8 +257,14 @@ def remap_range_on_file_sequence(otio_clip, otio_range):
rate=available_range_rate,
).to_frames()

# e.g.:
# duration = 10 frames at 24fps
# if frame_in = 1001 then
# frame_out = 1010
offset_duration = max(0, otio_range.duration.to_frames() - 1)

frame_out = otio.opentime.RationalTime.from_frames(
frame_in + otio_range.duration.to_frames() - 1,
frame_in + offset_duration,
rate=available_range_rate,
).to_frames()

Expand Down Expand Up @@ -369,10 +376,6 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end):
tw_node.update(metadata)
tw_node["lookup"] = list(lookup)

# get first and last frame offsets
offset_in += lookup[0]
offset_out += lookup[-1]

# add to timewarp nodes
time_warp_nodes.append(tw_node)

Expand All @@ -395,17 +398,19 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end):
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
src_duration = math.ceil(
otio_clip.source_range.duration.value
* abs(time_scalar)
)
retimed_duration = otio.opentime.RationalTime(
src_duration,
otio_clip.source_range.duration.rate
)
retimed_duration = retimed_duration.rescaled_to(src_in.rate)

trim_range = otio.opentime.TimeRange(
start_time=src_in + offset_in,
duration=src_duration + offset_duration
start_time=src_in,
duration=retimed_duration,
)

# preserve discrete frame numbers
Expand All @@ -419,17 +424,59 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end):
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

offset_duration = (
conformed_source_range.duration.value
* abs(time_scalar)
)

# Offset duration by 1 for media out frame
# - only if duration is not single frame (start frame != end frame)
if offset_duration > 0:
offset_duration -= 1
media_out_trimmed = media_in_trimmed + offset_duration

media_in = available_range.start_time.value
media_out = available_range.end_time_inclusive().value

if time_warp_nodes:
# Naive approach: Resolve consecutive timewarp(s) on range,
# then check if plate range has to be extended beyond source range.
in_frame = media_in_trimmed
frame_range = [in_frame]
for _ in range(otio_clip.source_range.duration.to_frames() - 1):
in_frame += time_scalar
frame_range.append(in_frame)

for tw_idx, tw in enumerate(time_warp_nodes):
for idx, frame_number in enumerate(frame_range):
# First timewarp, apply on media range
if tw_idx == 0:
frame_range[idx] = round(
frame_number +
(tw["lookup"][idx] * time_scalar)
)
# Consecutive timewarp, apply on the previous result
else:
new_idx = round(idx + tw["lookup"][idx])

if 0 <= new_idx < len(frame_range):
frame_range[idx] = frame_range[new_idx]
continue

# TODO: implementing this would need to actually have
# retiming engine resolve process within AYON,
# resolving wraps as curves, then projecting
# those into the previous media_range.
raise NotImplementedError(
"Unsupported consecutive timewarps "
"(out of computed range)"
)

# adjust range if needed
media_in_trimmed = max(min(frame_range), media_in)
media_out_trimmed = min(max(frame_range), media_out)

# adjust available handles if needed
if (media_in_trimmed - media_in) < handle_start:
handle_start = max(0, media_in_trimmed - media_in)
Expand All @@ -448,16 +495,16 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end):
"retime": True,
"speed": time_scalar,
"timewarps": time_warp_nodes,
"handleStart": int(handle_start),
"handleEnd": int(handle_end)
"handleStart": math.ceil(handle_start),
"handleEnd": math.ceil(handle_end)
}
}

returning_dict = {
"mediaIn": media_in_trimmed,
"mediaOut": media_out_trimmed,
"handleStart": int(handle_start),
"handleEnd": int(handle_end),
"handleStart": math.ceil(handle_start),
"handleEnd": math.ceil(handle_end),
"speed": time_scalar
}

Expand Down
33 changes: 25 additions & 8 deletions client/ayon_core/plugins/publish/collect_otio_subset_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
instance -> otioReviewClips
"""
import os
import math

import clique
import pyblish.api
Expand Down Expand Up @@ -69,9 +70,17 @@ def process(self, instance):
self.log.debug(
">> retimed_attributes: {}".format(retimed_attributes))

# break down into variables
media_in = int(retimed_attributes["mediaIn"])
media_out = int(retimed_attributes["mediaOut"])
# break down into variables as rounded frame numbers
#
# 0 1 2 3 4
# |-------------|---------------|--------------|-------------|
# |_______________media range_______________|
# 0.6 3.2
#
# As rounded frames, media_in = 0 and media_out = 4
media_in = math.floor(retimed_attributes["mediaIn"])
media_out = math.ceil(retimed_attributes["mediaOut"])

handle_start = int(retimed_attributes["handleStart"])
handle_end = int(retimed_attributes["handleEnd"])

Expand Down Expand Up @@ -174,17 +183,25 @@ def process(self, instance):
path, trimmed_media_range_h, metadata)
self.staging_dir, collection = collection_data

self.log.debug(collection)
repre = self._create_representation(
frame_start, frame_end, collection=collection)
if len(collection.indexes) > 1:
self.log.debug(collection)
repre = self._create_representation(
frame_start, frame_end, collection=collection)
else:
filename = tuple(collection)[0]
self.log.debug(filename)

# TODO: discuss this, it erases frame number.
repre = self._create_representation(
frame_start, frame_end, file=filename)

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)
frame_start, frame_end, collection=collection,
delete=True, review=True)

else:
_trim = False
Expand Down
3 changes: 1 addition & 2 deletions client/ayon_core/plugins/publish/extract_otio_review.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,6 @@ def process(self, instance):
self.actual_fps = available_range.duration.rate
start = src_range.start_time.rescaled_to(self.actual_fps)
duration = src_range.duration.rescaled_to(self.actual_fps)
src_frame_start = src_range.start_time.to_frames()

# Temporary.
# Some AYON custom OTIO exporter were implemented with
Expand All @@ -157,7 +156,7 @@ def process(self, instance):
if (
is_clip_from_media_sequence(r_otio_cl)
and available_range_start_frame == media_ref.start_frame
and src_frame_start < media_ref.start_frame
and start.to_frames() < media_ref.start_frame
):
available_range = otio.opentime.TimeRange(
otio.opentime.RationalTime(0, rate=self.actual_fps),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
{
"OTIO_SCHEMA": "Clip.2",
"metadata": {},
"name": "sh010",
"source_range": {
"OTIO_SCHEMA": "TimeRange.1",
"duration": {
"OTIO_SCHEMA": "RationalTime.1",
"rate": 23.976024627685547,
"value": 11.0
},
"start_time": {
"OTIO_SCHEMA": "RationalTime.1",
"rate": 23.976024627685547,
"value": 909986.0387191772
}
},
"effects": [
{
"OTIO_SCHEMA": "LinearTimeWarp.1",
"metadata": {},
"name": "Speed",
"effect_name": "LinearTimeWarp",
"time_scalar": 2.0
}
],
"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/hiero_retime_2x/sh010\", \"task\": null, \"clip_index\": \"37BA620A-6580-A543-ADF3-5A7133F41BB6\", \"hierarchy\": \"shots/hiero_retime_2x\", \"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"hiero_retime_2x\", \"track\": \"Video_1\", \"shot\": \"sh010\", \"reviewableSource\": \"Video 1\", \"sourceResolution\": false, \"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"parents\": [{\"entity_type\": \"folder\", \"folder_type\": \"folder\", \"entity_name\": \"shots\"}, {\"entity_type\": \"sequence\", \"folder_type\": \"sequence\", \"entity_name\": \"hiero_retime_2x\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"hiero_retime_2x\", \"track\": \"Video_1\"}, \"heroTrack\": true, \"uuid\": \"c60086c3-9ec3-448a-9bc5-6aa9f6af0fd5\", \"reviewTrack\": \"Video 1\", \"review\": true, \"folderName\": \"sh010\", \"label\": \"/shots/hiero_retime_2x/sh010 shotMain\", \"newHierarchyIntegration\": true, \"instance_id\": \"8cdde735-d5a7-4f95-9cff-ded20ff21135\", \"creator_attributes\": {\"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"frameStart\": 1001, \"frameEnd\": 1012, \"clipIn\": 0, \"clipOut\": 10, \"clipDuration\": 11, \"sourceIn\": 176.0, \"sourceOut\": 196.0, \"fps\": \"from_selection\"}, \"publish_attributes\": {\"CollectSlackFamilies\": {\"additional_message\": \"\"}}}, \"io.ayon.creators.hiero.plate\": {\"id\": \"pyblish.avalon.instance\", \"productType\": \"plate\", \"productName\": \"plateVideo_1\", \"active\": true, \"creator_identifier\": \"io.ayon.creators.hiero.plate\", \"variant\": \"Video_1\", \"folderPath\": \"/shots/hiero_retime_2x/sh010\", \"task\": null, \"clip_index\": \"37BA620A-6580-A543-ADF3-5A7133F41BB6\", \"hierarchy\": \"shots/hiero_retime_2x\", \"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"hiero_retime_2x\", \"track\": \"Video_1\", \"shot\": \"sh010\", \"reviewableSource\": \"Video 1\", \"sourceResolution\": false, \"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"parents\": [{\"entity_type\": \"folder\", \"folder_type\": \"folder\", \"entity_name\": \"shots\"}, {\"entity_type\": \"sequence\", \"folder_type\": \"sequence\", \"entity_name\": \"hiero_retime_2x\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"hiero_retime_2x\", \"track\": \"Video_1\"}, \"heroTrack\": true, \"uuid\": \"c60086c3-9ec3-448a-9bc5-6aa9f6af0fd5\", \"reviewTrack\": \"Video 1\", \"review\": true, \"folderName\": \"sh010\", \"parent_instance_id\": \"8cdde735-d5a7-4f95-9cff-ded20ff21135\", \"label\": \"/shots/hiero_retime_2x/sh010 plateVideo_1\", \"newHierarchyIntegration\": true, \"instance_id\": \"064a92fc-5704-4316-8cc9-780e430ae2e5\", \"creator_attributes\": {\"parentInstance\": \"/shots/hiero_retime_2x/sh010 shotMain\", \"review\": true, \"reviewableSource\": \"Video 1\"}, \"publish_attributes\": {\"CollectSlackFamilies\": {\"additional_message\": \"\"}}}}, \"clip_index\": \"37BA620A-6580-A543-ADF3-5A7133F41BB6\"}",
"label": "AYONdata_3c3f54af",
"note": "AYON data container"
},
"name": "AYONdata_3c3f54af",
"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
}
}
}
],
"enabled": true,
"media_references": {
"DEFAULT_MEDIA": {
"OTIO_SCHEMA": "ImageSequenceReference.1",
"metadata": {
"ayon.source.colorspace": "ACES - ACES2065-1",
"ayon.source.height": 720,
"ayon.source.pixelAspect": 1.0,
"ayon.source.width": 1280,
"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": "ACES - ACES2065-1",
"foundry.source.duration": "301",
"foundry.source.filename": "output.%07d.exr 948674-948974",
"foundry.source.filesize": "",
"foundry.source.fragments": "301",
"foundry.source.framerate": "25",
"foundry.source.fullpath": "",
"foundry.source.height": "720",
"foundry.source.layers": "colour",
"foundry.source.path": "C:/Users/robin/OneDrive/Bureau/dev_ayon/data/img_sequence/exr_long_frameRange/output.%07d.exr 948674-948974",
"foundry.source.pixelAspect": "1",
"foundry.source.pixelAspectRatio": "",
"foundry.source.pixelformat": "RGBA (Float16) Open Color IO space: 11",
"foundry.source.reelID": "",
"foundry.source.resolution": "",
"foundry.source.samplerate": "Invalid",
"foundry.source.shortfilename": "output.%07d.exr 948674-948974",
"foundry.source.shot": "",
"foundry.source.shotDate": "",
"foundry.source.startTC": "",
"foundry.source.starttime": "948674",
"foundry.source.timecode": "948674",
"foundry.source.umid": "28c4702f-5af7-4980-52c9-6eb875968890",
"foundry.source.umidOriginator": "foundry.source.umid",
"foundry.source.width": "1280",
"foundry.timeline.autodiskcachemode": "Manual",
"foundry.timeline.colorSpace": "ACES - ACES2065-1",
"foundry.timeline.duration": "301",
"foundry.timeline.framerate": "25",
"foundry.timeline.outputformat": "",
"foundry.timeline.poster": "0",
"foundry.timeline.posterLayer": "colour",
"foundry.timeline.readParams": "AAAAAQAAAAAAAAAFAAAACmNvbG9yc3BhY2UAAAAFaW50MzIAAAAAAAAAC2VkZ2VfcGl4ZWxzAAAABWludDMyAAAAAAAAABFpZ25vcmVfcGFydF9uYW1lcwAAAARib29sAAAAAAhub3ByZWZpeAAAAARib29sAAAAAB5vZmZzZXRfbmVnYXRpdmVfZGlzcGxheV93aW5kb3cAAAAEYm9vbAE=",
"foundry.timeline.samplerate": "Invalid",
"isSequence": true,
"media.exr.channels": "B:{1 0 1 1},G:{1 0 1 1},R:{1 0 1 1}",
"media.exr.compression": "2",
"media.exr.compressionName": "Zip (1 scanline)",
"media.exr.dataWindow": "1,1,1278,718",
"media.exr.displayWindow": "0,0,1279,719",
"media.exr.lineOrder": "0",
"media.exr.pixelAspectRatio": "1",
"media.exr.screenWindowCenter": "0,0",
"media.exr.screenWindowWidth": "1",
"media.exr.type": "scanlineimage",
"media.exr.version": "1",
"media.input.bitsperchannel": "16-bit half float",
"media.input.ctime": "2025-01-13 14:26:25",
"media.input.filename": "C:/Users/robin/OneDrive/Bureau/dev_ayon/data/img_sequence/exr_long_frameRange/output.0948674.exr",
"media.input.filereader": "exr",
"media.input.filesize": "214941",
"media.input.frame": "1",
"media.input.height": "720",
"media.input.mtime": "2025-01-13 14:26:25",
"media.input.width": "1280",
"media.nuke.full_layer_names": "0",
"media.nuke.node_hash": "b13e3153b31d8f14",
"media.nuke.version": "15.0v5",
"padding": 7
},
"name": "",
"available_range": {
"OTIO_SCHEMA": "TimeRange.1",
"duration": {
"OTIO_SCHEMA": "RationalTime.1",
"rate": 25.0,
"value": 301.0
},
"start_time": {
"OTIO_SCHEMA": "RationalTime.1",
"rate": 25.0,
"value": 948674.0
}
},
"available_image_bounds": null,
"target_url_base": "C:/Users/robin/OneDrive/Bureau/dev_ayon/data/img_sequence/exr_long_frameRange\\",
"name_prefix": "output.",
"name_suffix": ".exr",
"start_frame": 948674,
"frame_step": 1,
"rate": 25.0,
"frame_zero_padding": 7,
"missing_frame_policy": "error"
}
},
"active_media_reference_key": "DEFAULT_MEDIA"
}
Loading
Loading