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 wrong retimed detection on image sequence clip. #897

Merged
merged 42 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
14244e1
Fix wrong retimed detection on image sequence clip.
robin-ynput Sep 17, 2024
318ce6b
Fix lint.
robin-ynput Sep 17, 2024
5fbe7d4
Merge branch 'develop' into bugfix/#895_fix_otio_subset_resources_img…
robin-ynput Sep 17, 2024
d899f11
Merge branch 'develop' into bugfix/#895_fix_otio_subset_resources_img…
jakubjezek001 Sep 18, 2024
ac226a3
Apply suggestions from code review
robin-ynput Sep 18, 2024
0836fb8
Apply suggestions from code review
robin-ynput Sep 18, 2024
efc31f0
Consolidate pipeline.editorial.get_media_range_with_retimes
robin-ynput Sep 18, 2024
ef6693f
Add unit tests.
robin-ynput Sep 18, 2024
f4967d5
Merge with PR changes
robin-ynput Sep 18, 2024
b0b509d
Add unit tests
robin-ynput Sep 18, 2024
be2b8d5
Fix lint.
robin-ynput Sep 18, 2024
6c66933
Merge branch 'develop' into bugfix/#895_fix_otio_subset_resources_img…
jakubjezek001 Sep 19, 2024
4f2340a
Update client/ayon_core/pipeline/editorial.py
robin-ynput Sep 23, 2024
f9ed6f5
Fix typos.
robin-ynput Sep 23, 2024
d2b9337
Adjust test docstring.
robin-ynput Sep 23, 2024
201c91a
Merge branch 'develop' into bugfix/#895_fix_otio_subset_resources_img…
jakubjezek001 Sep 24, 2024
77fac00
Make it work even with image sequence and embedded timecodes.
robin-ynput Sep 24, 2024
6d31cd7
Add unit tests.
robin-ynput Sep 24, 2024
4b27971
Adjust comment.
robin-ynput Sep 24, 2024
885f8ac
Update client/ayon_core/pipeline/editorial.py
robin-ynput Sep 24, 2024
2980f10
Add client path to sys.path and run repository from code
jakubjezek001 Sep 25, 2024
7a83b8e
Add test for tail handles only.
robin-ynput Sep 25, 2024
ee749d0
Merge branch 'develop' into bugfix/#895_fix_extract_otio_review
robin-ynput Sep 25, 2024
ee95a19
Merge branch 'develop' into bugfix/#895_fix_otio_subset_resources_img…
robin-ynput Sep 25, 2024
8136ca5
merge with latest changes
robin-ynput Sep 25, 2024
a10f0e5
Implement linux counterpart to manage.sh
rdelillo Sep 25, 2024
beee0ef
Adjust feedback from PR.
robin-ynput Sep 30, 2024
3a75ebc
Adjust manage.sh and manage.ps1 run documentation.
robin-ynput Sep 30, 2024
9379365
Update client/ayon_core/plugins/publish/extract_otio_review.py
robin-ynput Oct 1, 2024
d69edc6
Add backward-compatibility for relative source ranges.
robin-ynput Oct 1, 2024
abd0e7d
Add backward-compatibility for relative source ranges.
robin-ynput Oct 1, 2024
8182914
Fix linting.
robin-ynput Oct 1, 2024
1072fc9
Merge pull request #916 from ynput/bugfix/#895_fix_extract_otio_review
iLLiCiTiT Oct 2, 2024
029cbe4
Merge branch 'develop' into bugfix/#895_fix_otio_subset_resources_img…
jakubjezek001 Oct 2, 2024
05291b2
ruff suggestions
jakubjezek001 Oct 2, 2024
7bd382a
Refactor Anatomy NamedTuple
jakubjezek001 Oct 2, 2024
4130293
Fix multiple review clips in OTIO review plugins with tests.
rdelillo Oct 2, 2024
1fce610
Fix linting
rdelillo Oct 2, 2024
89a3b42
Merge branch 'develop' into bugfix/#895_fix_otio_subset_resources_img…
rdelillo Oct 2, 2024
0ab128b
Add rounding support for OTIO <= 0.16.0
rdelillo Oct 2, 2024
df69603
Merge branch 'develop' into bugfix/#895_fix_otio_subset_resources_img…
jakubjezek001 Oct 3, 2024
56b7ebb
Merge branch 'develop' into bugfix/#895_fix_otio_subset_resources_img…
jakubjezek001 Oct 3, 2024
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
194 changes: 164 additions & 30 deletions client/ayon_core/pipeline/editorial.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def trim_media_range(media_range, source_range):

"""
rw_media_start = _ot.RationalTime(
media_range.start_time.value + source_range.start_time.value,
source_range.start_time.value,
media_range.start_time.rate
)
rw_media_duration = _ot.RationalTime(
Expand Down Expand Up @@ -173,11 +173,132 @@ def _sequence_resize(source, length):
yield (1 - ratio) * source[int(low)] + ratio * source[int(high)]


def is_clip_from_media_sequence(otio_clip):
"""
Args:
otio_clip (otio.schema.Clip): The OTIO clip to check.

Returns:
bool. Is the provided clip from an input media sequence ?
"""
media_ref = otio_clip.media_reference
metadata = media_ref.metadata

# OpenTimelineIO 0.13 and newer
is_input_sequence = (
hasattr(otio.schema, "ImageSequenceReference") and
isinstance(media_ref, otio.schema.ImageSequenceReference)
)

# OpenTimelineIO 0.12 and older
is_input_sequence_legacy = bool(metadata.get("padding"))

return is_input_sequence or is_input_sequence_legacy


def remap_range_on_file_sequence(otio_clip, in_out_range):
"""
Args:
otio_clip (otio.schema.Clip): The OTIO clip to check.
in_out_range (tuple[float, float]): The in-out range to remap.

Returns:
tuple(int, int): The remapped range as discrete frame number.

Raises:
ValueError. When the otio_clip or provided range is invalid.
"""
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

# Temporary.
# Some AYON custom OTIO exporter were implemented with relative
# source range for image sequence. Following code maintain
# backward-compatibility by adjusting media_in
# while we are updating those.
if (
is_clip_from_media_sequence(otio_clip)
and otio_clip.available_range().start_time.to_frames() == media_ref.start_frame
and source_range.start_time.to_frames() < media_ref.start_frame
):
media_in = 0

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

return frame_in, frame_out


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
available_range_rate = available_range.start_time.rate

# If media source is an image sequence, returned
# mediaIn/mediaOut have to correspond
# to frame numbers from source sequence.
media_ref = otio_clip.media_reference
is_input_sequence = is_clip_from_media_sequence(otio_clip)

# Temporary.
# Some AYON custom OTIO exporter were implemented with relative
# source range for image sequence. Following code maintain
# backward-compatibility by adjusting available range
# while we are updating those.
if (
is_input_sequence
and available_range.start_time.to_frames() == media_ref.start_frame
and source_range.start_time.to_frames() < media_ref.start_frame
):
available_range = _ot.TimeRange(
_ot.RationalTime(0, rate=available_range_rate),
available_range.duration,
)

# Conform source range bounds to available range rate
# .e.g. embedded TC of (3600 sec/ 1h), duration 100 frames
#
# available |----------------------------------------| 24fps
# 86400 86500
#
#
# 90010 90060
# src |-----|______duration 2s___|----| 25fps
# 90000
#
#
# 86409.6 86466.8
# conformed |-------|_____duration _2.38s____|-------| 24fps
# 86400
#
# Note that 24fps is slower than 25fps hence extended duration
# to preserve media range

# Compute new source range based on available rate
conformed_src_in = source_range.start_time.rescaled_to(available_range_rate)
conformed_src_duration = source_range.duration.rescaled_to(available_range_rate)
conformed_source_range = otio.opentime.TimeRange(
start_time=conformed_src_in,
duration=conformed_src_duration
)

# modifiers
time_scalar = 1.
Expand Down Expand Up @@ -224,55 +345,68 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end):
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
# flip offset and handles if reversed speed
if time_scalar < 0:
_handle_start = handle_end
_handle_end = handle_start
handle_start = _handle_start
handle_end = _handle_end
jakubjezek001 marked this conversation as resolved.
Show resolved Hide resolved

source_in = source_range.start_time.value
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_trimmed = (
media_in + source_in + offset_in)
robin-ynput marked this conversation as resolved.
Show resolved Hide resolved
media_out_trimmed = (
media_in + source_in + (
((source_range.duration.value - 1) * abs(
time_scalar)) + offset_out))
media_in = available_range.start_time.value
media_out = available_range.end_time_inclusive().value

# calculate available handles
# If media source is an image sequence, returned
# mediaIn/mediaOut have to correspond
# to frame numbers from source sequence.
if is_input_sequence:
# preserve discrete frame numbers
media_in_trimmed, media_out_trimmed = remap_range_on_file_sequence(
otio_clip,
(media_in_trimmed, media_out_trimmed)
)
media_in = media_ref.start_frame
media_out = media_in + available_range.duration.to_frames() - 1

# adjust available handles if needed
if (media_in_trimmed - media_in) < handle_start:
handle_start = (media_in_trimmed - media_in)
handle_start = max(0, media_in_trimmed - media_in)
if (media_out - media_out_trimmed) < handle_end:
handle_end = (media_out - media_out_trimmed)
handle_end = max(0, media_out - media_out_trimmed)

# FFmpeg extraction ignores embedded timecode
# so substract to get a (mediaIn-mediaOut) range from 0.
if not is_input_sequence:
media_in_trimmed -= media_in
media_out_trimmed -= media_in

# create version data
version_data = {
"versionData": {
"retime": True,
"speed": time_scalar,
"timewarps": time_warp_nodes,
"handleStart": int(round(handle_start)),
"handleEnd": int(round(handle_end))
"handleStart": int(handle_start),
"handleEnd": int(handle_end)
}
}

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

Expand Down
Loading