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

Fix OTIO range computation 23.976 fps rounding with embedded timecode #1064

Merged
merged 4 commits into from
Jan 10, 2025
Merged
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
81 changes: 54 additions & 27 deletions client/ayon_core/pipeline/editorial.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,11 +196,11 @@ def is_clip_from_media_sequence(otio_clip):
return is_input_sequence or is_input_sequence_legacy


def remap_range_on_file_sequence(otio_clip, in_out_range):
def remap_range_on_file_sequence(otio_clip, otio_range):
robin-ynput marked this conversation as resolved.
Show resolved Hide resolved
"""
Args:
otio_clip (otio.schema.Clip): The OTIO clip to check.
in_out_range (tuple[float, float]): The in-out range to remap.
otio_range (otio.schema.TimeRange): The trim range to apply.

Returns:
tuple(int, int): The remapped range as discrete frame number.
Expand All @@ -211,17 +211,24 @@ def remap_range_on_file_sequence(otio_clip, in_out_range):
if not is_clip_from_media_sequence(otio_clip):
raise ValueError(f"Cannot map on non-file sequence clip {otio_clip}.")

try:
media_in_trimmed, media_out_trimmed = in_out_range

except ValueError as error:
raise ValueError("Invalid in_out_range provided.") from error

media_ref = otio_clip.media_reference
available_range = otio_clip.available_range()
source_range = otio_clip.source_range
available_range_rate = available_range.start_time.rate
media_in = available_range.start_time.value

# Backward-compatibility for Hiero OTIO exporter.
# NTSC compatibility might introduce floating rates, when these are
# not exactly the same (23.976 vs 23.976024627685547)
# this will cause precision issue in computation.
# Currently round to 2 decimals for comparison,
# but this should always rescale after that.
rounded_av_rate = round(available_range_rate, 2)
rounded_range_rate = round(otio_range.start_time.rate, 2)

if rounded_av_rate != rounded_range_rate:
raise ValueError("Inconsistent range between clip and provided clip")

source_range = otio_clip.source_range
media_in = available_range.start_time
available_range_start_frame = (
available_range.start_time.to_frames()
)
Expand All @@ -239,14 +246,18 @@ def remap_range_on_file_sequence(otio_clip, in_out_range):
and available_range_start_frame == media_ref.start_frame
and conformed_src_in.to_frames() < media_ref.start_frame
):
media_in = 0
media_in = otio.opentime.RationalTime(
0, rate=available_range_rate
)

src_offset_in = otio_range.start_time - media_in
frame_in = otio.opentime.RationalTime.from_frames(
media_in_trimmed - media_in + media_ref.start_frame,
media_ref.start_frame + src_offset_in.to_frames(),
rate=available_range_rate,
).to_frames()

frame_out = otio.opentime.RationalTime.from_frames(
media_out_trimmed - media_in + media_ref.start_frame,
frame_in + otio_range.duration.to_frames() - 1,
rate=available_range_rate,
).to_frames()

Expand Down Expand Up @@ -378,31 +389,47 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end):
offset_in, offset_out = offset_out, offset_in
handle_start, handle_end = handle_end, handle_start

# 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
)

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

# If media source is an image sequence, returned
# mediaIn/mediaOut have to correspond
# to frame numbers from source sequence.
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
)

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

# preserve discrete frame numbers
media_in_trimmed, media_out_trimmed = remap_range_on_file_sequence(
otio_clip,
(media_in_trimmed, media_out_trimmed)
trim_range,
)
media_in = media_ref.start_frame
media_out = media_in + available_range.duration.to_frames() - 1

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
)

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

# adjust available handles if needed
if (media_in_trimmed - media_in) < handle_start:
handle_start = max(0, media_in_trimmed - media_in)
Expand Down
6 changes: 1 addition & 5 deletions client/ayon_core/plugins/publish/extract_otio_review.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,13 +209,9 @@ def process(self, instance):
# File sequence way
if is_sequence:
# Remap processing range to input file sequence.
processing_range_as_frames = (
processing_range.start_time.to_frames(),
processing_range.end_time_inclusive().to_frames()
)
first, last = remap_range_on_file_sequence(
r_otio_cl,
processing_range_as_frames,
processing_range,
)
input_fps = processing_range.start_time.rate

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
{
"OTIO_SCHEMA": "Clip.2",
"metadata": {},
"name": "Main088sh110",
"source_range": {
"OTIO_SCHEMA": "TimeRange.1",
"duration": {
"OTIO_SCHEMA": "RationalTime.1",
"rate": 23.976024627685547,
"value": 82.0
},
"start_time": {
"OTIO_SCHEMA": "RationalTime.1",
"rate": 23.976024627685547,
"value": 1937905.9905694576
}
},
"effects": [],
"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/088/Main088sh110\", \"task\": null, \"clip_index\": \"70C9FA86-76A5-A045-A004-3158FB3F27C5\", \"hierarchy\": \"shots/088\", \"folder\": \"shots\", \"episode\": \"404\", \"sequence\": \"088\", \"track\": \"Main\", \"shot\": \"sh110\", \"reviewableSource\": \"Reference\", \"sourceResolution\": false, \"workfileFrameStart\": 1009, \"handleStart\": 8, \"handleEnd\": 8, \"parents\": [{\"entity_type\": \"folder\", \"folder_type\": \"folder\", \"entity_name\": \"shots\"}, {\"entity_type\": \"sequence\", \"folder_type\": \"sequence\", \"entity_name\": \"088\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"404\", \"sequence\": \"088\", \"track\": \"Main\"}, \"heroTrack\": true, \"uuid\": \"8b0d1db8-7094-48ba-b2cd-df0d43cfffda\", \"reviewTrack\": \"Reference\", \"review\": true, \"folderName\": \"Main088sh110\", \"label\": \"/shots/088/Main088sh110 shotMain\", \"newHierarchyIntegration\": true, \"instance_id\": \"f6b7f12c-f3a8-44fd-b4e4-acc63ed80bb1\", \"creator_attributes\": {\"workfileFrameStart\": 1009, \"handleStart\": 8, \"handleEnd\": 8, \"frameStart\": 1009, \"frameEnd\": 1091, \"clipIn\": 80, \"clipOut\": 161, \"clipDuration\": 82, \"sourceIn\": 8.0, \"sourceOut\": 89.0, \"fps\": \"from_selection\"}, \"publish_attributes\": {}}, \"io.ayon.creators.hiero.plate\": {\"id\": \"pyblish.avalon.instance\", \"productType\": \"plate\", \"productName\": \"plateMain\", \"active\": true, \"creator_identifier\": \"io.ayon.creators.hiero.plate\", \"variant\": \"Main\", \"folderPath\": \"/shots/088/Main088sh110\", \"task\": null, \"clip_index\": \"70C9FA86-76A5-A045-A004-3158FB3F27C5\", \"hierarchy\": \"shots/088\", \"folder\": \"shots\", \"episode\": \"404\", \"sequence\": \"088\", \"track\": \"Main\", \"shot\": \"sh110\", \"reviewableSource\": \"Reference\", \"sourceResolution\": false, \"workfileFrameStart\": 1009, \"handleStart\": 8, \"handleEnd\": 8, \"parents\": [{\"entity_type\": \"folder\", \"folder_type\": \"folder\", \"entity_name\": \"shots\"}, {\"entity_type\": \"sequence\", \"folder_type\": \"sequence\", \"entity_name\": \"088\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"404\", \"sequence\": \"088\", \"track\": \"Main\"}, \"heroTrack\": true, \"uuid\": \"8b0d1db8-7094-48ba-b2cd-df0d43cfffda\", \"reviewTrack\": \"Reference\", \"review\": true, \"folderName\": \"Main088sh110\", \"parent_instance_id\": \"f6b7f12c-f3a8-44fd-b4e4-acc63ed80bb1\", \"label\": \"/shots/088/Main088sh110 plateMain\", \"newHierarchyIntegration\": true, \"instance_id\": \"64b54c11-7ab1-45ef-b156-9ed5d5552b9b\", \"creator_attributes\": {\"parentInstance\": \"/shots/088/Main088sh110 shotMain\", \"review\": true, \"reviewableSource\": \"Reference\"}, \"publish_attributes\": {}}}, \"clip_index\": \"70C9FA86-76A5-A045-A004-3158FB3F27C5\"}",
"label": "AYONdata_6b797112",
"note": "AYON data container"
},
"name": "AYONdata_6b797112",
"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
}
},
"comment": ""
}
],
"enabled": true,
"media_references": {
"DEFAULT_MEDIA": {
"OTIO_SCHEMA": "ImageSequenceReference.1",
"metadata": {
"ayon.source.colorspace": "Input - Sony - Linear - Venice S-Gamut3.Cine",
"ayon.source.height": 2160,
"ayon.source.pixelAspect": 1.0,
"ayon.source.width": 4096,
"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": "Input - Sony - Linear - Venice S-Gamut3.Cine",
"foundry.source.duration": "98",
"foundry.source.filename": "409_083_0015.%04d.exr 1001-1098",
"foundry.source.filesize": "",
"foundry.source.fragments": "98",
"foundry.source.framerate": "23.98",
"foundry.source.fullpath": "",
"foundry.source.height": "2160",
"foundry.source.layers": "colour",
"foundry.source.path": "X:/prj/AYON_CIRCUIT_TEST/data/OBX_20240729_P159_DOG_409/EXR/409_083_0015/409_083_0015.%04d.exr 1001-1098",
"foundry.source.pixelAspect": "1",
"foundry.source.pixelAspectRatio": "",
"foundry.source.pixelformat": "RGBA (Float16) Open Color IO space: 368",
"foundry.source.reelID": "",
"foundry.source.resolution": "",
"foundry.source.samplerate": "Invalid",
"foundry.source.shortfilename": "409_083_0015.%04d.exr 1001-1098",
"foundry.source.shot": "",
"foundry.source.shotDate": "",
"foundry.source.startTC": "",
"foundry.source.starttime": "1001",
"foundry.source.timecode": "1937896",
"foundry.source.umid": "4b3e13b3-e465-4df4-cb1f-257091b63815",
"foundry.source.umidOriginator": "foundry.source.umid",
"foundry.source.width": "4096",
"foundry.timeline.colorSpace": "Input - Sony - Linear - Venice S-Gamut3.Cine",
"foundry.timeline.duration": "98",
"foundry.timeline.framerate": "23.98",
"foundry.timeline.outputformat": "",
"foundry.timeline.poster": "0",
"foundry.timeline.posterLayer": "colour",
"foundry.timeline.readParams": "AAAAAQAAAAAAAAAFAAAACmNvbG9yc3BhY2UAAAAFaW50MzIAAABqAAAAC2VkZ2VfcGl4ZWxzAAAABWludDMyAAAAAAAAABFpZ25vcmVfcGFydF9uYW1lcwAAAARib29sAAAAAAhub3ByZWZpeAAAAARib29sAAAAAB5vZmZzZXRfbmVnYXRpdmVfZGlzcGxheV93aW5kb3cAAAAEYm9vbAE=",
"foundry.timeline.samplerate": "Invalid",
"isSequence": true,
"media.exr.camera_camera_type": "AXS-R7",
"media.exr.camera_fps": "23.976",
"media.exr.camera_id": "MPC-3610 0010762 Version6.30",
"media.exr.camera_iso": "2500",
"media.exr.camera_lens_type": "Unknown",
"media.exr.camera_monitor_space": "OBX4_LUT_1_Night.cube",
"media.exr.camera_nd_filter": "1",
"media.exr.camera_roll_angle": "0.3",
"media.exr.camera_shutter_angle": "180.0",
"media.exr.camera_shutter_speed": "0.0208333",
"media.exr.camera_shutter_type": "Speed and Angle",
"media.exr.camera_sl_num": "00011434",
"media.exr.camera_tilt_angle": "-7.4",
"media.exr.camera_type": "Sony",
"media.exr.camera_white_kelvin": "3200",
"media.exr.channels": "B:{1 0 1 1},G:{1 0 1 1},R:{1 0 1 1}",
"media.exr.clip_details_codec": "F55_X-OCN_ST_4096_2160",
"media.exr.clip_details_pixel_aspect_ratio": "1",
"media.exr.clip_details_shot_frame_rate": "23.98p",
"media.exr.compression": "0",
"media.exr.compressionName": "none",
"media.exr.dataWindow": "0,0,4095,2159",
"media.exr.displayWindow": "0,0,4095,2159",
"media.exr.lineOrder": "0",
"media.exr.owner": "C272C010_240530HO",
"media.exr.pixelAspectRatio": "1",
"media.exr.screenWindowCenter": "0,0",
"media.exr.screenWindowWidth": "1",
"media.exr.tech_details_aspect_ratio": "1.8963",
"media.exr.tech_details_cdl_sat": "1",
"media.exr.tech_details_cdl_sop": "(1 1 1)(0 0 0)(1 1 1)",
"media.exr.tech_details_gamma_space": "R709 Video",
"media.exr.tech_details_par": "1",
"media.exr.type": "scanlineimage",
"media.input.bitsperchannel": "16-bit half float",
"media.input.ctime": "2024-07-30 18:51:38",
"media.input.filename": "X:/prj/AYON_CIRCUIT_TEST/data/OBX_20240729_P159_DOG_409/EXR/409_083_0015/409_083_0015.1001.exr",
"media.input.filereader": "exr",
"media.input.filesize": "53120020",
"media.input.frame": "1",
"media.input.frame_rate": "23.976",
"media.input.height": "2160",
"media.input.mtime": "2024-07-30 18:51:38",
"media.input.timecode": "22:25:45:16",
"media.input.width": "4096",
"padding": 4
},
"name": "",
"available_range": {
"OTIO_SCHEMA": "TimeRange.1",
"duration": {
"OTIO_SCHEMA": "RationalTime.1",
"rate": 23.976,
"value": 98.0
},
"start_time": {
"OTIO_SCHEMA": "RationalTime.1",
"rate": 23.976,
"value": 1937896.0
}
},
"available_image_bounds": null,
"target_url_base": "X:/prj/AYON_CIRCUIT_TEST/data/OBX_20240729_P159_DOG_409/EXR/409_083_0015\\",
"name_prefix": "409_083_0015.",
"name_suffix": ".exr",
"start_frame": 1001,
"frame_step": 1,
"rate": 23.976,
"frame_zero_padding": 4,
"missing_frame_policy": "error"
}
},
"active_media_reference_key": "DEFAULT_MEDIA"
}
Original file line number Diff line number Diff line change
Expand Up @@ -252,48 +252,48 @@ def test_multiple_review_clips_no_gap():

'/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i '
f'C:\\no_tc{os.sep}output.%04d.tif '
'-start_number 1199 C:/result/output.%04d.jpg',
'-start_number 1198 C:/result/output.%04d.jpg',

'/path/to/ffmpeg -start_number 1000 -framerate 24.0 -i '
f'C:\\with_tc{os.sep}output.%04d.exr '
'-start_number 1300 C:/result/output.%04d.jpg',
'-start_number 1299 C:/result/output.%04d.jpg',

# Repeated 25fps tiff sequence multiple times till the end
'/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i '
f'C:\\no_tc{os.sep}output.%04d.tif '
'-start_number 1397 C:/result/output.%04d.jpg',
'-start_number 1395 C:/result/output.%04d.jpg',

'/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i '
f'C:\\no_tc{os.sep}output.%04d.tif '
'-start_number 1498 C:/result/output.%04d.jpg',
'-start_number 1496 C:/result/output.%04d.jpg',

'/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i '
f'C:\\no_tc{os.sep}output.%04d.tif '
'-start_number 1599 C:/result/output.%04d.jpg',
'-start_number 1597 C:/result/output.%04d.jpg',

'/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i '
f'C:\\no_tc{os.sep}output.%04d.tif '
'-start_number 1700 C:/result/output.%04d.jpg',
'-start_number 1698 C:/result/output.%04d.jpg',

'/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i '
f'C:\\no_tc{os.sep}output.%04d.tif '
'-start_number 1801 C:/result/output.%04d.jpg',
'-start_number 1799 C:/result/output.%04d.jpg',

'/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i '
f'C:\\no_tc{os.sep}output.%04d.tif '
'-start_number 1902 C:/result/output.%04d.jpg',
'-start_number 1900 C:/result/output.%04d.jpg',

'/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i '
f'C:\\no_tc{os.sep}output.%04d.tif '
'-start_number 2003 C:/result/output.%04d.jpg',
'-start_number 2001 C:/result/output.%04d.jpg',

'/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i '
f'C:\\no_tc{os.sep}output.%04d.tif '
'-start_number 2104 C:/result/output.%04d.jpg',
'-start_number 2102 C:/result/output.%04d.jpg',

'/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i '
f'C:\\no_tc{os.sep}output.%04d.tif '
'-start_number 2205 C:/result/output.%04d.jpg'
'-start_number 2203 C:/result/output.%04d.jpg'
]

assert calls == expected
Expand Down
Loading
Loading