mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Fix multiple review clips in OTIO review plugins with tests.
This commit is contained in:
parent
7bd382a187
commit
41302936c2
4 changed files with 1969 additions and 45 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,289 @@
|
|||
{
|
||||
"OTIO_SCHEMA": "Track.1",
|
||||
"metadata": {},
|
||||
"name": "Video 2",
|
||||
"source_range": null,
|
||||
"effects": [],
|
||||
"markers": [],
|
||||
"enabled": true,
|
||||
"children": [
|
||||
{
|
||||
"OTIO_SCHEMA": "Gap.1",
|
||||
"metadata": {},
|
||||
"name": "",
|
||||
"source_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 2.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 0.0
|
||||
}
|
||||
},
|
||||
"effects": [],
|
||||
"markers": [],
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Clip.2",
|
||||
"metadata": {},
|
||||
"name": "output",
|
||||
"source_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 88.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 0.0
|
||||
}
|
||||
},
|
||||
"effects": [],
|
||||
"markers": [],
|
||||
"enabled": true,
|
||||
"media_references": {
|
||||
"DEFAULT_MEDIA": {
|
||||
"OTIO_SCHEMA": "ImageSequenceReference.1",
|
||||
"metadata": {
|
||||
"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": "scene_linear",
|
||||
"foundry.source.duration": "101",
|
||||
"foundry.source.filename": "output.%04d.exr 1000-1100",
|
||||
"foundry.source.filesize": "",
|
||||
"foundry.source.fragments": "101",
|
||||
"foundry.source.framerate": "24",
|
||||
"foundry.source.fullpath": "",
|
||||
"foundry.source.height": "1080",
|
||||
"foundry.source.layers": "colour",
|
||||
"foundry.source.pixelAspect": "1",
|
||||
"foundry.source.pixelAspectRatio": "",
|
||||
"foundry.source.pixelformat": "RGBA (Float16) Open Color IO space: 7",
|
||||
"foundry.source.reelID": "",
|
||||
"foundry.source.resolution": "",
|
||||
"foundry.source.samplerate": "Invalid",
|
||||
"foundry.source.shortfilename": "output.%04d.exr 1000-1100",
|
||||
"foundry.source.shot": "",
|
||||
"foundry.source.shotDate": "",
|
||||
"foundry.source.startTC": "",
|
||||
"foundry.source.starttime": "1000",
|
||||
"foundry.source.timecode": "87399",
|
||||
"foundry.source.umid": "bbfe0c90-5b76-424a-6351-4dac36a8dde7",
|
||||
"foundry.source.umidOriginator": "foundry.source.umid",
|
||||
"foundry.source.width": "1920",
|
||||
"foundry.timeline.autodiskcachemode": "Manual",
|
||||
"foundry.timeline.colorSpace": "scene_linear",
|
||||
"foundry.timeline.duration": "101",
|
||||
"foundry.timeline.framerate": "24",
|
||||
"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,1918,1078",
|
||||
"media.exr.displayWindow": "0,0,1919,1079",
|
||||
"media.exr.lineOrder": "0",
|
||||
"media.exr.nuke.input.frame_rate": "24",
|
||||
"media.exr.nuke.input.timecode": "01:00:41:15",
|
||||
"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": "2024-09-23 08:37:23",
|
||||
"media.input.filereader": "exr",
|
||||
"media.input.filesize": "1095868",
|
||||
"media.input.frame": "1",
|
||||
"media.input.frame_rate": "24",
|
||||
"media.input.height": "1080",
|
||||
"media.input.mtime": "2024-09-23 08:37:23",
|
||||
"media.input.timecode": "01:00:41:15",
|
||||
"media.input.width": "1920",
|
||||
"media.nuke.full_layer_names": "0",
|
||||
"media.nuke.node_hash": "9b",
|
||||
"media.nuke.version": "15.1v2",
|
||||
"openpype.source.colourtransform": "scene_linear",
|
||||
"openpype.source.height": 1080,
|
||||
"openpype.source.pixelAspect": 1.0,
|
||||
"openpype.source.width": 1920,
|
||||
"padding": 4
|
||||
},
|
||||
"name": "",
|
||||
"available_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 101.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 1000.0
|
||||
}
|
||||
},
|
||||
"available_image_bounds": null,
|
||||
"target_url_base": "C:\\with_tc",
|
||||
"name_prefix": "output.",
|
||||
"name_suffix": ".exr",
|
||||
"start_frame": 1000,
|
||||
"frame_step": 1,
|
||||
"rate": 24.0,
|
||||
"frame_zero_padding": 4,
|
||||
"missing_frame_policy": "error"
|
||||
}
|
||||
},
|
||||
"active_media_reference_key": "DEFAULT_MEDIA"
|
||||
},
|
||||
{
|
||||
"OTIO_SCHEMA": "Clip.2",
|
||||
"metadata": {},
|
||||
"name": "output",
|
||||
"source_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 11.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 0.0
|
||||
}
|
||||
},
|
||||
"effects": [],
|
||||
"markers": [],
|
||||
"enabled": true,
|
||||
"media_references": {
|
||||
"DEFAULT_MEDIA": {
|
||||
"OTIO_SCHEMA": "ImageSequenceReference.1",
|
||||
"metadata": {
|
||||
"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": "scene_linear",
|
||||
"foundry.source.duration": "101",
|
||||
"foundry.source.filename": "output.%04d.exr 1000-1100",
|
||||
"foundry.source.filesize": "",
|
||||
"foundry.source.fragments": "101",
|
||||
"foundry.source.framerate": "24",
|
||||
"foundry.source.fullpath": "",
|
||||
"foundry.source.height": "1080",
|
||||
"foundry.source.layers": "colour",
|
||||
"foundry.source.pixelAspect": "1",
|
||||
"foundry.source.pixelAspectRatio": "",
|
||||
"foundry.source.pixelformat": "RGBA (Float16) Open Color IO space: 7",
|
||||
"foundry.source.reelID": "",
|
||||
"foundry.source.resolution": "",
|
||||
"foundry.source.samplerate": "Invalid",
|
||||
"foundry.source.shortfilename": "output.%04d.exr 1000-1100",
|
||||
"foundry.source.shot": "",
|
||||
"foundry.source.shotDate": "",
|
||||
"foundry.source.startTC": "",
|
||||
"foundry.source.starttime": "1000",
|
||||
"foundry.source.timecode": "87399",
|
||||
"foundry.source.umid": "bbfe0c90-5b76-424a-6351-4dac36a8dde7",
|
||||
"foundry.source.umidOriginator": "foundry.source.umid",
|
||||
"foundry.source.width": "1920",
|
||||
"foundry.timeline.autodiskcachemode": "Manual",
|
||||
"foundry.timeline.colorSpace": "scene_linear",
|
||||
"foundry.timeline.duration": "101",
|
||||
"foundry.timeline.framerate": "24",
|
||||
"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,1918,1078",
|
||||
"media.exr.displayWindow": "0,0,1919,1079",
|
||||
"media.exr.lineOrder": "0",
|
||||
"media.exr.nuke.input.frame_rate": "24",
|
||||
"media.exr.nuke.input.timecode": "01:00:41:15",
|
||||
"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": "2024-09-23 08:37:23",
|
||||
"media.input.filereader": "exr",
|
||||
"media.input.filesize": "1095868",
|
||||
"media.input.frame": "1",
|
||||
"media.input.frame_rate": "24",
|
||||
"media.input.height": "1080",
|
||||
"media.input.mtime": "2024-09-23 08:37:23",
|
||||
"media.input.timecode": "01:00:41:15",
|
||||
"media.input.width": "1920",
|
||||
"media.nuke.full_layer_names": "0",
|
||||
"media.nuke.node_hash": "9b",
|
||||
"media.nuke.version": "15.1v2",
|
||||
"openpype.source.colourtransform": "scene_linear",
|
||||
"openpype.source.height": 1080,
|
||||
"openpype.source.pixelAspect": 1.0,
|
||||
"openpype.source.width": 1920,
|
||||
"padding": 4
|
||||
},
|
||||
"name": "",
|
||||
"available_range": {
|
||||
"OTIO_SCHEMA": "TimeRange.1",
|
||||
"duration": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 101.0
|
||||
},
|
||||
"start_time": {
|
||||
"OTIO_SCHEMA": "RationalTime.1",
|
||||
"rate": 24.0,
|
||||
"value": 1000.0
|
||||
}
|
||||
},
|
||||
"available_image_bounds": null,
|
||||
"target_url_base": "C:\\with_tc",
|
||||
"name_prefix": "output.",
|
||||
"name_suffix": ".exr",
|
||||
"start_frame": 1000,
|
||||
"frame_step": 1,
|
||||
"rate": 24.0,
|
||||
"frame_zero_padding": 4,
|
||||
"missing_frame_policy": "error"
|
||||
}
|
||||
},
|
||||
"active_media_reference_key": "DEFAULT_MEDIA"
|
||||
}
|
||||
],
|
||||
"kind": "Video"
|
||||
}
|
||||
|
|
@ -37,27 +37,31 @@ class CaptureFFmpegCalls():
|
|||
return ["/path/to/ffmpeg"]
|
||||
|
||||
|
||||
def run_process(file_name: str):
|
||||
def run_process(file_name: str, instance_data: dict = None):
|
||||
"""
|
||||
"""
|
||||
# Get OTIO review data from serialized file_name
|
||||
file_path = os.path.join(_RESOURCE_DIR, file_name)
|
||||
clip = otio.schema.Clip.from_json_file(file_path)
|
||||
|
||||
# Prepare dummy instance and capture call object
|
||||
capture_call = CaptureFFmpegCalls()
|
||||
processor = extract_otio_review.ExtractOTIOReview()
|
||||
Anatomy = NamedTuple("Anatomy", project_name=str)
|
||||
instance = MockInstance(
|
||||
{
|
||||
|
||||
if not instance_data:
|
||||
# Get OTIO review data from serialized file_name
|
||||
file_path = os.path.join(_RESOURCE_DIR, file_name)
|
||||
clip = otio.schema.Clip.from_json_file(file_path)
|
||||
|
||||
instance_data = {
|
||||
"otioReviewClips": [clip],
|
||||
"handleStart": 10,
|
||||
"handleEnd": 10,
|
||||
"workfileFrameStart": 1001,
|
||||
"folderPath": "/dummy/path",
|
||||
"anatomy": Anatomy("test_project"),
|
||||
}
|
||||
)
|
||||
|
||||
instance_data.update({
|
||||
"folderPath": "/dummy/path",
|
||||
"anatomy": Anatomy("test_project"),
|
||||
})
|
||||
instance = MockInstance(instance_data)
|
||||
|
||||
# Mock calls to extern and run plugins.
|
||||
with mock.patch.object(
|
||||
|
|
@ -73,9 +77,14 @@ def run_process(file_name: str):
|
|||
with mock.patch.object(
|
||||
processor,
|
||||
"_get_folder_name_based_prefix",
|
||||
return_value="C:/result/output."
|
||||
return_value="output."
|
||||
):
|
||||
processor.process(instance)
|
||||
with mock.patch.object(
|
||||
processor,
|
||||
"staging_dir",
|
||||
return_value="C:/result/"
|
||||
):
|
||||
processor.process(instance)
|
||||
|
||||
# return all calls made to ffmpeg subprocess
|
||||
return capture_call.calls
|
||||
|
|
@ -103,7 +112,7 @@ def test_image_sequence_with_embedded_tc_and_handles_out_of_range():
|
|||
|
||||
# Report from source exr (1001-1101) with enforce framerate
|
||||
"/path/to/ffmpeg -start_number 1000 -framerate 24.0 -i "
|
||||
"C:\\exr_embedded_tc\\output.%04d.exr -start_number 1001 "
|
||||
f"C:\\exr_embedded_tc{os.sep}output.%04d.exr -start_number 1001 "
|
||||
"C:/result/output.%03d.jpg"
|
||||
]
|
||||
|
||||
|
|
@ -133,7 +142,7 @@ def test_image_sequence_and_handles_out_of_range():
|
|||
# 1001-1095 = source range conformed to 25fps
|
||||
# 1096-1096 = additional 1 tail frames
|
||||
"/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i "
|
||||
"C:\\tif_seq\\output.%04d.tif -start_number 996 C:/result/output.%03d.jpg"
|
||||
f"C:\\tif_seq{os.sep}output.%04d.tif -start_number 996 C:/result/output.%03d.jpg"
|
||||
]
|
||||
|
||||
assert calls == expected
|
||||
|
|
@ -203,3 +212,119 @@ def test_short_movie_tail_gap_handles():
|
|||
]
|
||||
|
||||
assert calls == expected
|
||||
|
||||
def test_multiple_review_clips_no_gap():
|
||||
"""
|
||||
Use multiple review clips (image sequence).
|
||||
Timeline 25fps
|
||||
"""
|
||||
file_path = os.path.join(_RESOURCE_DIR, "multiple_review_clips.json")
|
||||
clips = otio.schema.Track.from_json_file(file_path)
|
||||
instance_data = {
|
||||
"otioReviewClips": clips,
|
||||
"handleStart": 10,
|
||||
"handleEnd": 10,
|
||||
"workfileFrameStart": 1001,
|
||||
}
|
||||
|
||||
calls = run_process(
|
||||
None,
|
||||
instance_data=instance_data
|
||||
)
|
||||
|
||||
expected = [
|
||||
# 10 head black frames generated from gap (991-1000)
|
||||
'/path/to/ffmpeg -t 0.4 -r 25.0 -f lavfi -i color=c=black:s=1280x720 -tune '
|
||||
'stillimage -start_number 991 C:/result/output.%03d.jpg',
|
||||
|
||||
# Alternance 25fps tiff sequence and 24fps exr sequence for 100 frames each
|
||||
'/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i '
|
||||
f'C:\\no_tc{os.sep}output.%04d.tif '
|
||||
'-start_number 1001 C:/result/output.%03d.jpg',
|
||||
|
||||
'/path/to/ffmpeg -start_number 1000 -framerate 24.0 -i '
|
||||
f'C:\\with_tc{os.sep}output.%04d.exr '
|
||||
'-start_number 1102 C:/result/output.%03d.jpg',
|
||||
|
||||
'/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.%03d.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.%03d.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.%03d.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.%03d.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.%03d.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.%03d.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.%03d.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.%03d.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.%03d.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.%03d.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.%03d.jpg'
|
||||
]
|
||||
|
||||
assert calls == expected
|
||||
|
||||
def test_multiple_review_clips_with_gap():
|
||||
"""
|
||||
Use multiple review clips (image sequence) with gap.
|
||||
Timeline 24fps
|
||||
"""
|
||||
file_path = os.path.join(_RESOURCE_DIR, "multiple_review_clips_gap.json")
|
||||
clips = otio.schema.Track.from_json_file(file_path)
|
||||
instance_data = {
|
||||
"otioReviewClips": clips,
|
||||
"handleStart": 10,
|
||||
"handleEnd": 10,
|
||||
"workfileFrameStart": 1001,
|
||||
}
|
||||
|
||||
calls = run_process(
|
||||
None,
|
||||
instance_data=instance_data
|
||||
)
|
||||
|
||||
expected = [
|
||||
# Gap on review track (12 frames)
|
||||
'/path/to/ffmpeg -t 0.5 -r 24.0 -f lavfi -i color=c=black:s=1280x720 -tune '
|
||||
'stillimage -start_number 991 C:/result/output.%03d.jpg',
|
||||
|
||||
'/path/to/ffmpeg -start_number 1000 -framerate 24.0 -i '
|
||||
f'C:\\with_tc{os.sep}output.%04d.exr '
|
||||
'-start_number 1003 C:/result/output.%03d.jpg',
|
||||
|
||||
'/path/to/ffmpeg -start_number 1000 -framerate 24.0 -i '
|
||||
f'C:\\with_tc{os.sep}output.%04d.exr '
|
||||
'-start_number 1091 C:/result/output.%03d.jpg'
|
||||
]
|
||||
|
||||
assert calls == expected
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue