From 5780a1797115554ebff370bd4634420ddba4fc0f Mon Sep 17 00:00:00 2001 From: "robin@ynput.io" Date: Wed, 18 Dec 2024 18:07:16 +0100 Subject: [PATCH 001/103] Consolidate 23.976 trim computation. --- client/ayon_core/pipeline/editorial.py | 81 +++++--- .../plugins/publish/extract_otio_review.py | 6 +- .../resources/qt_23.976_embedded_long_tc.json | 174 ++++++++++++++++++ .../editorial/test_extract_otio_review.py | 22 +-- .../test_media_range_with_retimes.py | 22 +++ 5 files changed, 262 insertions(+), 43 deletions(-) create mode 100644 tests/client/ayon_core/pipeline/editorial/resources/qt_23.976_embedded_long_tc.json diff --git a/client/ayon_core/pipeline/editorial.py b/client/ayon_core/pipeline/editorial.py index 2928ef5f63..d71cf6c344 100644 --- a/client/ayon_core/pipeline/editorial.py +++ b/client/ayon_core/pipeline/editorial.py @@ -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): """ 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. @@ -211,17 +211,25 @@ 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 + source_range_rate = source_range.start_time.rate + media_in = available_range.start_time available_range_start_frame = ( available_range.start_time.to_frames() ) @@ -236,14 +244,20 @@ def remap_range_on_file_sequence(otio_clip, in_out_range): and available_range_start_frame == media_ref.start_frame and source_range.start_time.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() + + range_duration = otio_range.duration + 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() @@ -374,31 +388,44 @@ 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) diff --git a/client/ayon_core/plugins/publish/extract_otio_review.py b/client/ayon_core/plugins/publish/extract_otio_review.py index 712ae7a886..d5f5f43cc9 100644 --- a/client/ayon_core/plugins/publish/extract_otio_review.py +++ b/client/ayon_core/plugins/publish/extract_otio_review.py @@ -209,13 +209,9 @@ class ExtractOTIOReview( # 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 diff --git a/tests/client/ayon_core/pipeline/editorial/resources/qt_23.976_embedded_long_tc.json b/tests/client/ayon_core/pipeline/editorial/resources/qt_23.976_embedded_long_tc.json new file mode 100644 index 0000000000..01d81508d1 --- /dev/null +++ b/tests/client/ayon_core/pipeline/editorial/resources/qt_23.976_embedded_long_tc.json @@ -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" +} \ No newline at end of file diff --git a/tests/client/ayon_core/pipeline/editorial/test_extract_otio_review.py b/tests/client/ayon_core/pipeline/editorial/test_extract_otio_review.py index e1fbf514d4..8ad2e44b06 100644 --- a/tests/client/ayon_core/pipeline/editorial/test_extract_otio_review.py +++ b/tests/client/ayon_core/pipeline/editorial/test_extract_otio_review.py @@ -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 diff --git a/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py b/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py index 7f9256c6d8..5a375e4499 100644 --- a/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py +++ b/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py @@ -64,6 +64,28 @@ def test_movie_embedded_tc_handle(): ) +def test_movie_23fps_qt_embedded_tc(): + """ + Movie clip (embedded timecode 1h) + available_range = 1937896-1937994 23.976fps + source_range = 1937905-1937987 23.97602462768554fps + """ + expected_data = { + 'mediaIn': 1009, + 'mediaOut': 1090, + 'handleStart': 8, + 'handleEnd': 8, + 'speed': 1.0 + } + + _check_expected_retimed_values( + "qt_23.976_embedded_long_tc.json", + expected_data, + handle_start=8, + handle_end=8, + ) + + def test_movie_retime_effect(): """ Movie clip (embedded timecode 1h) From c6ea24edcfc3d110c26a5c0084136035d3f1745a Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 7 Jan 2025 15:37:03 +0100 Subject: [PATCH 002/103] return StagingDir object instead of string --- client/ayon_core/pipeline/staging_dir.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/pipeline/staging_dir.py b/client/ayon_core/pipeline/staging_dir.py index 83878f17a2..b7ca1a2cd6 100644 --- a/client/ayon_core/pipeline/staging_dir.py +++ b/client/ayon_core/pipeline/staging_dir.py @@ -161,11 +161,15 @@ def get_staging_dir_info( ) if force_tmp_dir: - return get_temp_dir( - project_name=project_entity["name"], - anatomy=anatomy, - prefix=prefix, - suffix=suffix, + return StagingDir( + get_temp_dir( + project_name=project_entity["name"], + anatomy=anatomy, + prefix=prefix, + suffix=suffix, + ), + is_persistent=False, + is_custom=False ) # making few queries to database From 228a3c6f054388bad49dfeb020615e4b3eb2fdb8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 7 Jan 2025 15:37:33 +0100 Subject: [PATCH 003/103] remove duplicated validation of template name --- client/ayon_core/pipeline/staging_dir.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/client/ayon_core/pipeline/staging_dir.py b/client/ayon_core/pipeline/staging_dir.py index b7ca1a2cd6..7f9ec85466 100644 --- a/client/ayon_core/pipeline/staging_dir.py +++ b/client/ayon_core/pipeline/staging_dir.py @@ -76,7 +76,6 @@ def get_staging_dir_config( # get template from template name template_name = profile["template_name"] - _validate_template_name(project_name, template_name, anatomy) template = anatomy.get_template_item("staging", template_name) @@ -93,19 +92,6 @@ def get_staging_dir_config( return {"template": template, "persistence": data_persistence} -def _validate_template_name(project_name, template_name, anatomy): - """Check that staging dir section with appropriate template exist. - - Raises: - ValueError - if misconfigured template - """ - if template_name not in anatomy.templates["staging"]: - raise ValueError( - f'Anatomy of project "{project_name}" does not have set' - f' "{template_name}" template key at Staging Dir category!' - ) - - def get_staging_dir_info( project_entity, folder_entity, From 2b1a04b5c0742320f1eb5adc501aa610e3d5e11c Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 7 Jan 2025 15:38:44 +0100 Subject: [PATCH 004/103] added typehints --- .../pipeline/create/creator_plugins.py | 4 +- client/ayon_core/pipeline/staging_dir.py | 53 ++++++++++--------- 2 files changed, 30 insertions(+), 27 deletions(-) diff --git a/client/ayon_core/pipeline/create/creator_plugins.py b/client/ayon_core/pipeline/create/creator_plugins.py index 6ccafe1bc7..28e9de20ee 100644 --- a/client/ayon_core/pipeline/create/creator_plugins.py +++ b/client/ayon_core/pipeline/create/creator_plugins.py @@ -15,7 +15,7 @@ from ayon_core.pipeline.plugin_discover import ( deregister_plugin, deregister_plugin_path ) -from ayon_core.pipeline import get_staging_dir_info +from ayon_core.pipeline.staging_dir import get_staging_dir_info, StagingDir from .constants import DEFAULT_VARIANT_VALUE from .product_name import get_product_name @@ -833,7 +833,7 @@ class Creator(BaseCreator): """ return self.pre_create_attr_defs - def get_staging_dir(self, instance): + def get_staging_dir(self, instance) -> Optional[StagingDir]: """Return the staging dir and persistence from instance. Args: diff --git a/client/ayon_core/pipeline/staging_dir.py b/client/ayon_core/pipeline/staging_dir.py index 7f9ec85466..7e0874fbef 100644 --- a/client/ayon_core/pipeline/staging_dir.py +++ b/client/ayon_core/pipeline/staging_dir.py @@ -1,3 +1,6 @@ +import logging +import warnings +from typing import Optional, Dict, Any from dataclasses import dataclass from ayon_core.lib import Logger, filter_profiles @@ -16,16 +19,16 @@ class StagingDir: def get_staging_dir_config( - project_name, - task_type, - task_name, - product_type, - product_name, - host_name, - project_settings=None, - anatomy=None, - log=None, -): + project_name: str, + task_type: Optional[str, None], + task_name: Optional[str, None], + product_type: str, + product_name: str, + host_name: str, + project_settings: Optional[Dict[str, Any]] = None, + anatomy: Optional[Anatomy] = None, + log: Optional[logging.Logger] = None, +) -> Optional[Dict[str, Any]]: """Get matching staging dir profile. Args: @@ -93,21 +96,21 @@ def get_staging_dir_config( def get_staging_dir_info( - project_entity, - folder_entity, - task_entity, - product_type, - product_name, - host_name, - anatomy=None, - project_settings=None, - template_data=None, - always_return_path=True, - force_tmp_dir=False, - logger=None, - prefix=None, - suffix=None, -): + project_entity: Dict[str, Any], + folder_entity: Optional[Dict[str, Any]], + task_entity: Optional[Dict[str, Any]], + product_type: str, + product_name: str, + host_name: str, + anatomy: Optional[Anatomy] = None, + project_settings: Optional[Dict[str, Any]] = None, + template_data: Optional[Dict[str, Any]] = None, + always_return_path: bool = True, + force_tmp_dir: bool = False, + logger: Optional[logging.Logger] = None, + prefix: Optional[str] = None, + suffix: Optional[str] = None, +) -> Optional[StagingDir]: """Get staging dir info data. If `force_temp` is set, staging dir will be created as tempdir. From 25f6ec241bbbbc3fb95e7770561a96e875341b1e Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 7 Jan 2025 15:39:57 +0100 Subject: [PATCH 005/103] added is_ prefix to StagingDir bools --- .../pipeline/create/creator_plugins.py | 2 +- client/ayon_core/pipeline/publish/lib.py | 4 +-- client/ayon_core/pipeline/staging_dir.py | 32 +++++++++++++++---- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/client/ayon_core/pipeline/create/creator_plugins.py b/client/ayon_core/pipeline/create/creator_plugins.py index 28e9de20ee..42e8e0b60f 100644 --- a/client/ayon_core/pipeline/create/creator_plugins.py +++ b/client/ayon_core/pipeline/create/creator_plugins.py @@ -915,7 +915,7 @@ class Creator(BaseCreator): instance.transient_data.update({ "stagingDir": staging_dir_path, - "stagingDir_persistent": staging_dir_info.persistent, + "stagingDir_persistent": staging_dir_info.is_persistent, }) self.log.info(f"Applied staging dir to instance: {staging_dir_path}") diff --git a/client/ayon_core/pipeline/publish/lib.py b/client/ayon_core/pipeline/publish/lib.py index 586b90a3fd..ba0f846fe4 100644 --- a/client/ayon_core/pipeline/publish/lib.py +++ b/client/ayon_core/pipeline/publish/lib.py @@ -716,8 +716,8 @@ def get_instance_staging_dir(instance): os.makedirs(staging_dir_path, exist_ok=True) instance.data.update({ "stagingDir": staging_dir_path, - "stagingDir_persistent": staging_dir_info.persistent, - "stagingDir_custom": staging_dir_info.custom + "stagingDir_persistent": staging_dir_info.is_persistent, + "stagingDir_custom": staging_dir_info.is_custom }) return staging_dir_path diff --git a/client/ayon_core/pipeline/staging_dir.py b/client/ayon_core/pipeline/staging_dir.py index 7e0874fbef..2d94616faf 100644 --- a/client/ayon_core/pipeline/staging_dir.py +++ b/client/ayon_core/pipeline/staging_dir.py @@ -14,8 +14,28 @@ from .tempdir import get_temp_dir @dataclass class StagingDir: directory: str - persistent: bool - custom: bool # Whether the staging dir is a custom staging dir + is_persistent: bool + # Whether the staging dir is a custom staging dir + is_custom: bool + + def __setattr__(self, key, value): + if key == "persistent": + warnings.warn( + "'StagingDir.persistent' is deprecated." + " Use 'StagingDir.is_persistent' instead.", + DeprecationWarning + ) + key = "is_persistent" + super().__setattr__(key, value) + + @property + def persistent(self): + warnings.warn( + "'StagingDir.persistent' is deprecated." + " Use 'StagingDir.is_persistent' instead.", + DeprecationWarning + ) + return self.is_persistent def get_staging_dir_config( @@ -198,8 +218,8 @@ def get_staging_dir_info( dir_template = staging_dir_config["template"]["directory"] return StagingDir( dir_template.format_strict(ctx_data), - persistent=staging_dir_config["persistence"], - custom=True + is_persistent=staging_dir_config["persistence"], + is_custom=True ) # no config found but force an output @@ -211,8 +231,8 @@ def get_staging_dir_info( prefix=prefix, suffix=suffix, ), - persistent=False, - custom=False + is_persistent=False, + is_custom=False ) return None From 47fee3f54bd7d6b18849cdb59f8b5937988efa57 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 7 Jan 2025 15:41:58 +0100 Subject: [PATCH 006/103] change custom key to is_custom --- client/ayon_core/pipeline/publish/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/publish/lib.py b/client/ayon_core/pipeline/publish/lib.py index ba0f846fe4..40a9b47aba 100644 --- a/client/ayon_core/pipeline/publish/lib.py +++ b/client/ayon_core/pipeline/publish/lib.py @@ -717,7 +717,7 @@ def get_instance_staging_dir(instance): instance.data.update({ "stagingDir": staging_dir_path, "stagingDir_persistent": staging_dir_info.is_persistent, - "stagingDir_custom": staging_dir_info.is_custom + "stagingDir_is_custom": staging_dir_info.is_custom }) return staging_dir_path From 4641760bd17a232a0d6c7a24482060a46a475ba3 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 8 Jan 2025 09:16:21 +0100 Subject: [PATCH 007/103] Initial changes for BorisFX Silhouette --- client/ayon_core/hooks/pre_add_last_workfile_arg.py | 3 ++- client/ayon_core/hooks/pre_ocio_hook.py | 3 ++- client/ayon_core/plugins/publish/validate_file_saved.py | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/hooks/pre_add_last_workfile_arg.py b/client/ayon_core/hooks/pre_add_last_workfile_arg.py index d5914c2352..a931fb0cbe 100644 --- a/client/ayon_core/hooks/pre_add_last_workfile_arg.py +++ b/client/ayon_core/hooks/pre_add_last_workfile_arg.py @@ -29,7 +29,8 @@ class AddLastWorkfileToLaunchArgs(PreLaunchHook): "aftereffects", "wrap", "openrv", - "cinema4d" + "cinema4d", + "silhouette" } launch_types = {LaunchTypes.local} diff --git a/client/ayon_core/hooks/pre_ocio_hook.py b/client/ayon_core/hooks/pre_ocio_hook.py index 7406aa42cf..dd81cf053e 100644 --- a/client/ayon_core/hooks/pre_ocio_hook.py +++ b/client/ayon_core/hooks/pre_ocio_hook.py @@ -20,7 +20,8 @@ class OCIOEnvHook(PreLaunchHook): "hiero", "resolve", "openrv", - "cinema4d" + "cinema4d", + "silhouette" } launch_types = set() diff --git a/client/ayon_core/plugins/publish/validate_file_saved.py b/client/ayon_core/plugins/publish/validate_file_saved.py index f52998cef3..4f9e84aee0 100644 --- a/client/ayon_core/plugins/publish/validate_file_saved.py +++ b/client/ayon_core/plugins/publish/validate_file_saved.py @@ -37,7 +37,7 @@ class ValidateCurrentSaveFile(pyblish.api.ContextPlugin): label = "Validate File Saved" order = pyblish.api.ValidatorOrder - 0.1 hosts = ["fusion", "houdini", "max", "maya", "nuke", "substancepainter", - "cinema4d"] + "cinema4d", "silhouette"] actions = [SaveByVersionUpAction, ShowWorkfilesAction] def process(self, context): From be6aac6a72a76bb82300fe4ca612e5408bbea679 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 8 Jan 2025 09:17:21 +0100 Subject: [PATCH 008/103] Remove `TreeViewSpinner` with `QtSvg` dependency - The `TreeViewSpinner` widget was not used anywhere - The `QtSvg` dependency does not exist in BorisFX Silhouette so removing it was easiest to make Silhouette not error on this import --- client/ayon_core/tools/utils/views.py | 45 +-------------------------- 1 file changed, 1 insertion(+), 44 deletions(-) diff --git a/client/ayon_core/tools/utils/views.py b/client/ayon_core/tools/utils/views.py index b501f1ff11..d8ae94bf0c 100644 --- a/client/ayon_core/tools/utils/views.py +++ b/client/ayon_core/tools/utils/views.py @@ -1,7 +1,6 @@ -from ayon_core.resources import get_image_path from ayon_core.tools.flickcharm import FlickCharm -from qtpy import QtWidgets, QtCore, QtGui, QtSvg +from qtpy import QtWidgets, QtCore, QtGui class DeselectableTreeView(QtWidgets.QTreeView): @@ -19,48 +18,6 @@ class DeselectableTreeView(QtWidgets.QTreeView): QtWidgets.QTreeView.mousePressEvent(self, event) -class TreeViewSpinner(QtWidgets.QTreeView): - size = 160 - - def __init__(self, parent=None): - super(TreeViewSpinner, self).__init__(parent=parent) - - loading_image_path = get_image_path("spinner-200.svg") - - self.spinner = QtSvg.QSvgRenderer(loading_image_path) - - self.is_loading = False - self.is_empty = True - - def paint_loading(self, event): - rect = event.rect() - rect = QtCore.QRectF(rect.topLeft(), rect.bottomRight()) - rect.moveTo( - rect.x() + rect.width() / 2 - self.size / 2, - rect.y() + rect.height() / 2 - self.size / 2 - ) - rect.setSize(QtCore.QSizeF(self.size, self.size)) - painter = QtGui.QPainter(self.viewport()) - self.spinner.render(painter, rect) - - def paint_empty(self, event): - painter = QtGui.QPainter(self.viewport()) - rect = event.rect() - rect = QtCore.QRectF(rect.topLeft(), rect.bottomRight()) - qtext_opt = QtGui.QTextOption( - QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter - ) - painter.drawText(rect, "No Data", qtext_opt) - - def paintEvent(self, event): - if self.is_loading: - self.paint_loading(event) - elif self.is_empty: - self.paint_empty(event) - else: - super(TreeViewSpinner, self).paintEvent(event) - - class TreeView(QtWidgets.QTreeView): """Ultimate TreeView with flick charm and double click signals. From cb2758ef54c22da82d172fa54522b9b4bb1fd28e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 8 Jan 2025 13:03:03 +0100 Subject: [PATCH 009/103] Allow folders to be considered as workfiles in workfiles tool, because Silhouette projects are actually folders (with .sfx extension) --- client/ayon_core/tools/workfiles/models/workfiles.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/client/ayon_core/tools/workfiles/models/workfiles.py b/client/ayon_core/tools/workfiles/models/workfiles.py index a268a9bd0e..2c5ec230a6 100644 --- a/client/ayon_core/tools/workfiles/models/workfiles.py +++ b/client/ayon_core/tools/workfiles/models/workfiles.py @@ -185,8 +185,6 @@ class WorkareaModel: for filename in os.listdir(workdir): filepath = os.path.join(workdir, filename) - if not os.path.isfile(filepath): - continue ext = os.path.splitext(filename)[1].lower() if ext not in self._extensions: From 05ca2d42cd1b3306f81436c2da9d64f4285d2373 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 8 Jan 2025 14:23:05 +0100 Subject: [PATCH 010/103] fix typehint --- client/ayon_core/pipeline/staging_dir.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/pipeline/staging_dir.py b/client/ayon_core/pipeline/staging_dir.py index 2d94616faf..37d6b955e2 100644 --- a/client/ayon_core/pipeline/staging_dir.py +++ b/client/ayon_core/pipeline/staging_dir.py @@ -40,8 +40,8 @@ class StagingDir: def get_staging_dir_config( project_name: str, - task_type: Optional[str, None], - task_name: Optional[str, None], + task_type: Optional[str], + task_name: Optional[str], product_type: str, product_name: str, host_name: str, From 2c91d60d6df074ebcc74c1eced35fe191e6edeed Mon Sep 17 00:00:00 2001 From: "robin@ynput.io" Date: Wed, 8 Jan 2025 16:41:06 +0100 Subject: [PATCH 011/103] Fix lint. --- client/ayon_core/pipeline/editorial.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/pipeline/editorial.py b/client/ayon_core/pipeline/editorial.py index d71cf6c344..8ac4d906b1 100644 --- a/client/ayon_core/pipeline/editorial.py +++ b/client/ayon_core/pipeline/editorial.py @@ -228,7 +228,6 @@ def remap_range_on_file_sequence(otio_clip, otio_range): raise ValueError("Inconsistent range between clip and provided clip") source_range = otio_clip.source_range - source_range_rate = source_range.start_time.rate media_in = available_range.start_time available_range_start_frame = ( available_range.start_time.to_frames() @@ -254,8 +253,6 @@ def remap_range_on_file_sequence(otio_clip, otio_range): rate=available_range_rate, ).to_frames() - range_duration = otio_range.duration - frame_out = otio.opentime.RationalTime.from_frames( frame_in + otio_range.duration.to_frames() - 1, rate=available_range_rate, @@ -397,7 +394,10 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end): 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) + offset_duration = otio.opentime.RationalTime( + offset_out, + rate=src_duration.rate + ) trim_range = otio.opentime.TimeRange( start_time=src_in + offset_in, From 8db81098af5d15e20607c661ab510c6b6a6ef767 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 8 Jan 2025 17:55:13 +0100 Subject: [PATCH 012/103] Add comment --- client/ayon_core/tools/workfiles/models/workfiles.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/ayon_core/tools/workfiles/models/workfiles.py b/client/ayon_core/tools/workfiles/models/workfiles.py index 2c5ec230a6..c621a44937 100644 --- a/client/ayon_core/tools/workfiles/models/workfiles.py +++ b/client/ayon_core/tools/workfiles/models/workfiles.py @@ -184,6 +184,9 @@ class WorkareaModel: return items for filename in os.listdir(workdir): + # We want to support both files and folders. e.g. Silhoutte uses + # folders as its project files. So we do not check whether it is + # a file or not. filepath = os.path.join(workdir, filename) ext = os.path.splitext(filename)[1].lower() From ff2893dff04368f26c127080ef824f3bad95b2d1 Mon Sep 17 00:00:00 2001 From: "robin@ynput.io" Date: Wed, 8 Jan 2025 19:04:54 +0100 Subject: [PATCH 013/103] Fix remap with wrongly detected legacy image sequence. --- client/ayon_core/pipeline/editorial.py | 35 +++++++------ .../img_seq_24_to_23.976_no_legacy.json | 51 +++++++++++++++++++ .../test_media_range_with_retimes.py | 26 ++++++++++ 3 files changed, 96 insertions(+), 16 deletions(-) create mode 100644 tests/client/ayon_core/pipeline/editorial/resources/img_seq_24_to_23.976_no_legacy.json diff --git a/client/ayon_core/pipeline/editorial.py b/client/ayon_core/pipeline/editorial.py index 2928ef5f63..1d1859cbb8 100644 --- a/client/ayon_core/pipeline/editorial.py +++ b/client/ayon_core/pipeline/editorial.py @@ -231,10 +231,13 @@ def remap_range_on_file_sequence(otio_clip, in_out_range): # source range for image sequence. Following code maintain # backward-compatibility by adjusting media_in # while we are updating those. + conformed_src_in = source_range.start_time.rescaled_to( + available_range_rate + ) if ( is_clip_from_media_sequence(otio_clip) and available_range_start_frame == media_ref.start_frame - and source_range.start_time.to_frames() < media_ref.start_frame + and conformed_src_in.to_frames() < media_ref.start_frame ): media_in = 0 @@ -261,21 +264,6 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end): 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 # @@ -320,6 +308,21 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end): else: conformed_source_range = source_range + # 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 conformed_source_range.start_time.to_frames() < media_ref.start_frame + ): + available_range = _ot.TimeRange( + _ot.RationalTime(0, rate=available_range_rate), + available_range.duration, + ) + # modifiers time_scalar = 1. offset_in = 0 diff --git a/tests/client/ayon_core/pipeline/editorial/resources/img_seq_24_to_23.976_no_legacy.json b/tests/client/ayon_core/pipeline/editorial/resources/img_seq_24_to_23.976_no_legacy.json new file mode 100644 index 0000000000..108af0f2c1 --- /dev/null +++ b/tests/client/ayon_core/pipeline/editorial/resources/img_seq_24_to_23.976_no_legacy.json @@ -0,0 +1,51 @@ +{ + "OTIO_SCHEMA": "Clip.2", + "metadata": {}, + "name": "", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 23.976, + "value": 108.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 23.976, + "value": 883159.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ImageSequenceReference.1", + "metadata": {}, + "name": "", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24.0, + "value": 755.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24.0, + "value": 883750.0 + } + }, + "available_image_bounds": null, + "target_url_base": "/mnt/jobs/yahoo_theDog_1132/IN/FOOTAGE/SCANS_LINEAR/Panasonic Rec 709 to ACESCG/Panasonic P2 /A001_S001_S001_T004/", + "name_prefix": "A001_S001_S001_T004.", + "name_suffix": ".exr", + "start_frame": 883750, + "frame_step": 1, + "rate": 1.0, + "frame_zero_padding": 0, + "missing_frame_policy": "error" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" +} \ No newline at end of file diff --git a/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py b/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py index 7f9256c6d8..331732b6a4 100644 --- a/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py +++ b/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py @@ -187,3 +187,29 @@ def test_img_sequence_conform_to_23_976fps(): handle_start=0, handle_end=8, ) + + +def test_img_sequence_conform_from_24_to_23_976fps(): + """ + Img sequence clip + available files = 883750-884504 24fps + source_range = 883159-883267 23.976fps + + This test ensures such entries do not trigger + the legacy Hiero export compatibility. + """ + expected_data = { + 'mediaIn': 884043, + 'mediaOut': 884150, + 'handleStart': 0, + 'handleEnd': 0, + 'speed': 1.0 + } + + _check_expected_retimed_values( + "img_seq_24_to_23.976_no_legacy.json", + expected_data, + handle_start=0, + handle_end=0, + ) + From 9fbef06cb0c30b1f11e685b9075bf0f53a2064ac Mon Sep 17 00:00:00 2001 From: "robin@ynput.io" Date: Wed, 8 Jan 2025 19:16:10 +0100 Subject: [PATCH 014/103] Fix lint. --- client/ayon_core/pipeline/editorial.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/editorial.py b/client/ayon_core/pipeline/editorial.py index 1d1859cbb8..a33f439651 100644 --- a/client/ayon_core/pipeline/editorial.py +++ b/client/ayon_core/pipeline/editorial.py @@ -316,7 +316,8 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end): if ( is_input_sequence and available_range.start_time.to_frames() == media_ref.start_frame - and conformed_source_range.start_time.to_frames() < media_ref.start_frame + and conformed_source_range.start_time.to_frames() < + media_ref.start_frame ): available_range = _ot.TimeRange( _ot.RationalTime(0, rate=available_range_rate), From cd9929dfa192d080e5dcfea0f543104dd12cb4af Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 9 Jan 2025 17:01:50 +0100 Subject: [PATCH 015/103] set host name environment variable --- client/ayon_core/plugins/publish/collect_farm_env_variables.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_core/plugins/publish/collect_farm_env_variables.py b/client/ayon_core/plugins/publish/collect_farm_env_variables.py index cb52e5c32e..ee88985905 100644 --- a/client/ayon_core/plugins/publish/collect_farm_env_variables.py +++ b/client/ayon_core/plugins/publish/collect_farm_env_variables.py @@ -24,6 +24,7 @@ class CollectCoreJobEnvVars(pyblish.api.ContextPlugin): # NOTE we should use 'context.data["user"]' but that has higher # order. ("AYON_USERNAME", get_ayon_username()), + ("AYON_HOST_NAME", context.data["hostName"]), ): if value: self.log.debug(f"Setting job env: {key}: {value}") From 3fbb0f4dfb0ecfe46ca83929bbe00a9a92794d34 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 9 Jan 2025 18:18:24 +0100 Subject: [PATCH 016/103] metadata file does not require 'job' key to be filled --- client/ayon_core/plugins/publish/collect_rendered_files.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/collect_rendered_files.py b/client/ayon_core/plugins/publish/collect_rendered_files.py index 42ba096d14..62b649cdd0 100644 --- a/client/ayon_core/plugins/publish/collect_rendered_files.py +++ b/client/ayon_core/plugins/publish/collect_rendered_files.py @@ -105,7 +105,7 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): instance.data.update(instance_data) # stash render job id for later validation - instance.data["render_job_id"] = data.get("job").get("_id") + instance.data["render_job_id"] = data.get("job", {}).get("_id") staging_dir_persistent = instance.data.get( "stagingDir_persistent", False ) From 8a7e11a1a4645bd886154c70eb9ec39b4ee2a831 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 9 Jan 2025 18:18:40 +0100 Subject: [PATCH 017/103] store metadata file content to 'publishJobMetadata' on instance --- client/ayon_core/plugins/publish/collect_rendered_files.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/plugins/publish/collect_rendered_files.py b/client/ayon_core/plugins/publish/collect_rendered_files.py index 62b649cdd0..deecf7ba24 100644 --- a/client/ayon_core/plugins/publish/collect_rendered_files.py +++ b/client/ayon_core/plugins/publish/collect_rendered_files.py @@ -93,8 +93,7 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): # now we can just add instances from json file and we are done any_staging_dir_persistent = False - for instance_data in data.get("instances"): - + for instance_data in data["instances"]: self.log.debug(" - processing instance for {}".format( instance_data.get("productName"))) instance = self._context.create_instance( @@ -105,6 +104,10 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): instance.data.update(instance_data) # stash render job id for later validation + instance.data["publishJobMetadata"] = data + # TODO remove 'render_job_id' here and rather use + # 'publishJobMetadata' where is needed. + # - this is deadline specific instance.data["render_job_id"] = data.get("job", {}).get("_id") staging_dir_persistent = instance.data.get( "stagingDir_persistent", False From eec3f61641b1ca21b9c6e2fdb225beffcb3c7827 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 13 Jan 2025 15:56:18 +0100 Subject: [PATCH 018/103] Correctly emit signal --- client/ayon_core/tools/publisher/widgets/card_view_widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py index 095a4eae7c..6bd4725371 100644 --- a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py +++ b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py @@ -197,7 +197,7 @@ class ConvertorItemsGroupWidget(BaseGroupWidget): else: widget = ConvertorItemCardWidget(item, self) widget.selected.connect(self._on_widget_selection) - widget.double_clicked(self.double_clicked) + widget.double_clicked.emit(self.double_clicked) self._widgets_by_id[item.id] = widget self._content_layout.insertWidget(widget_idx, widget) widget_idx += 1 From f4ea9aeacff690518822bd76459370fec8596d57 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 13 Jan 2025 16:01:11 +0100 Subject: [PATCH 019/103] Should be ``.connect` --- client/ayon_core/tools/publisher/widgets/card_view_widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py index 6bd4725371..2f633b3149 100644 --- a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py +++ b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py @@ -197,7 +197,7 @@ class ConvertorItemsGroupWidget(BaseGroupWidget): else: widget = ConvertorItemCardWidget(item, self) widget.selected.connect(self._on_widget_selection) - widget.double_clicked.emit(self.double_clicked) + widget.double_clicked.connect(self.double_clicked) self._widgets_by_id[item.id] = widget self._content_layout.insertWidget(widget_idx, widget) widget_idx += 1 From 1d23ca6633d5ff8d85ebeda066de7a621a60e869 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 13 Jan 2025 23:35:43 +0200 Subject: [PATCH 020/103] update default order to match values in `CollectUSDLayerContributions` --- server/settings/publish_plugins.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/settings/publish_plugins.py b/server/settings/publish_plugins.py index 8893b00e23..1bf2e853cf 100644 --- a/server/settings/publish_plugins.py +++ b/server/settings/publish_plugins.py @@ -1008,8 +1008,8 @@ DEFAULT_PUBLISH_VALUES = { {"name": "model", "order": 100}, {"name": "assembly", "order": 150}, {"name": "groom", "order": 175}, - {"name": "look", "order": 300}, - {"name": "rig", "order": 100}, + {"name": "look", "order": 200}, + {"name": "rig", "order": 300}, # Shot layers {"name": "layout", "order": 200}, {"name": "animation", "order": 300}, From 11b00e61050b3d4027efe7fbedc14111bce87451 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Tue, 14 Jan 2025 08:27:36 +0000 Subject: [PATCH 021/103] [Automated] Add generated package files from main --- client/ayon_core/version.py | 2 +- package.py | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/version.py b/client/ayon_core/version.py index 2417897a47..0a985e2829 100644 --- a/client/ayon_core/version.py +++ b/client/ayon_core/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring AYON addon 'core' version.""" -__version__ = "1.0.12+dev" +__version__ = "1.0.13" diff --git a/package.py b/package.py index 8ade5ceeed..dae6a9ca0a 100644 --- a/package.py +++ b/package.py @@ -1,6 +1,6 @@ name = "core" title = "Core" -version = "1.0.12+dev" +version = "1.0.13" client_dir = "ayon_core" diff --git a/pyproject.toml b/pyproject.toml index b8d6a5a537..78edf5c2e3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ [tool.poetry] name = "ayon-core" -version = "1.0.12+dev" +version = "1.0.13" description = "" authors = ["Ynput Team "] readme = "README.md" From e3b9bfa29d91efc380bde4389783abc0aa9c868d Mon Sep 17 00:00:00 2001 From: Ynbot Date: Tue, 14 Jan 2025 08:28:17 +0000 Subject: [PATCH 022/103] [Automated] Update version in package.py for develop --- client/ayon_core/version.py | 2 +- package.py | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/version.py b/client/ayon_core/version.py index 0a985e2829..b0ada09e7c 100644 --- a/client/ayon_core/version.py +++ b/client/ayon_core/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring AYON addon 'core' version.""" -__version__ = "1.0.13" +__version__ = "1.0.13+dev" diff --git a/package.py b/package.py index dae6a9ca0a..03b69d4c5c 100644 --- a/package.py +++ b/package.py @@ -1,6 +1,6 @@ name = "core" title = "Core" -version = "1.0.13" +version = "1.0.13+dev" client_dir = "ayon_core" diff --git a/pyproject.toml b/pyproject.toml index 78edf5c2e3..5e42aa7093 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ [tool.poetry] name = "ayon-core" -version = "1.0.13" +version = "1.0.13+dev" description = "" authors = ["Ynput Team "] readme = "README.md" From e90ca81eded332464f184025521e8815f6202725 Mon Sep 17 00:00:00 2001 From: "robin@ynput.io" Date: Tue, 14 Jan 2025 10:27:58 +0100 Subject: [PATCH 023/103] Append more editorial tests for reverse speed. --- ...img_seq_reverse_speed_24_to_23.976fps.json | 59 +++ .../img_seq_reverse_speed_no_tc.json | 369 ++++++++++++++++++ .../test_media_range_with_retimes.py | 56 +++ 3 files changed, 484 insertions(+) create mode 100644 tests/client/ayon_core/pipeline/editorial/resources/img_seq_reverse_speed_24_to_23.976fps.json create mode 100644 tests/client/ayon_core/pipeline/editorial/resources/img_seq_reverse_speed_no_tc.json diff --git a/tests/client/ayon_core/pipeline/editorial/resources/img_seq_reverse_speed_24_to_23.976fps.json b/tests/client/ayon_core/pipeline/editorial/resources/img_seq_reverse_speed_24_to_23.976fps.json new file mode 100644 index 0000000000..b907f53f3d --- /dev/null +++ b/tests/client/ayon_core/pipeline/editorial/resources/img_seq_reverse_speed_24_to_23.976fps.json @@ -0,0 +1,59 @@ +{ + "OTIO_SCHEMA": "Clip.2", + "metadata": {}, + "name": "", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 23.976, + "value": 32.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 23.976, + "value": 947726.0 + } + }, + "effects": [ + { + "OTIO_SCHEMA": "LinearTimeWarp.1", + "metadata": {}, + "name": "", + "effect_name": "LinearTimeWarp", + "time_scalar": -1.0 + } + ], + "markers": [], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ImageSequenceReference.1", + "metadata": {}, + "name": "", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24.0, + "value": 7607.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24.0, + "value": 941478.0 + } + }, + "available_image_bounds": null, + "target_url_base": "/mnt/jobs/yahoo_theDog_1132/IN/FOOTAGE/SCANS_LINEAR/Panasonic Rec 709 to ACESCG/Panasonic P2 /A001_S001_S001_T012/", + "name_prefix": "A001_S001_S001_T012.", + "name_suffix": ".exr", + "start_frame": 941478, + "frame_step": 1, + "rate": 24.0, + "frame_zero_padding": 0, + "missing_frame_policy": "error" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" +} \ No newline at end of file diff --git a/tests/client/ayon_core/pipeline/editorial/resources/img_seq_reverse_speed_no_tc.json b/tests/client/ayon_core/pipeline/editorial/resources/img_seq_reverse_speed_no_tc.json new file mode 100644 index 0000000000..ccf33413ec --- /dev/null +++ b/tests/client/ayon_core/pipeline/editorial/resources/img_seq_reverse_speed_no_tc.json @@ -0,0 +1,369 @@ +{ + "OTIO_SCHEMA": "Clip.2", + "metadata": { + "Resolve_OTIO": {} + }, + "name": "output.[1000-1099].tif", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24.0, + "value": 41.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24.0, + "value": 20.000000000000004 + } + }, + "effects": [ + { + "OTIO_SCHEMA": "LinearTimeWarp.1", + "metadata": {}, + "name": "", + "effect_name": "", + "time_scalar": -1.0 + }, + { + "OTIO_SCHEMA": "Effect.1", + "metadata": { + "Resolve_OTIO": { + "Effect Name": "Transform", + "Enabled": true, + "Name": "Transform", + "Parameters": [], + "Type": 2 + } + }, + "name": "", + "effect_name": "Resolve Effect" + }, + { + "OTIO_SCHEMA": "Effect.1", + "metadata": { + "Resolve_OTIO": { + "Effect Name": "Cropping", + "Enabled": true, + "Name": "Cropping", + "Parameters": [], + "Type": 3 + } + }, + "name": "", + "effect_name": "Resolve Effect" + }, + { + "OTIO_SCHEMA": "Effect.1", + "metadata": { + "Resolve_OTIO": { + "Effect Name": "Dynamic Zoom", + "Enabled": false, + "Name": "Dynamic Zoom", + "Parameters": [ + { + "Default Parameter Value": [ + 0.0, + 0.0 + ], + "Key Frames": { + "-39": { + "Value": [ + 0.0, + 0.0 + ], + "Variant Type": "POINTF" + }, + "961": { + "Value": [ + 0.0, + 0.0 + ], + "Variant Type": "POINTF" + } + }, + "Parameter ID": "dynamicZoomCenter", + "Parameter Value": [ + 0.0, + 0.0 + ], + "Variant Type": "POINTF" + }, + { + "Default Parameter Value": 1.0, + "Key Frames": { + "-39": { + "Value": 0.8, + "Variant Type": "Double" + }, + "961": { + "Value": 1.0, + "Variant Type": "Double" + } + }, + "Parameter ID": "dynamicZoomScale", + "Parameter Value": 1.0, + "Variant Type": "Double", + "maxValue": 100.0, + "minValue": 0.01 + } + ], + "Type": 59 + } + }, + "name": "", + "effect_name": "Resolve Effect" + }, + { + "OTIO_SCHEMA": "Effect.1", + "metadata": { + "Resolve_OTIO": { + "Effect Name": "Composite", + "Enabled": true, + "Name": "Composite", + "Parameters": [], + "Type": 1 + } + }, + "name": "", + "effect_name": "Resolve Effect" + }, + { + "OTIO_SCHEMA": "Effect.1", + "metadata": { + "Resolve_OTIO": { + "Effect Name": "Lens Correction", + "Enabled": true, + "Name": "Lens Correction", + "Parameters": [], + "Type": 43 + } + }, + "name": "", + "effect_name": "Resolve Effect" + }, + { + "OTIO_SCHEMA": "Effect.1", + "metadata": { + "Resolve_OTIO": { + "Effect Name": "Retime and Scaling", + "Enabled": true, + "Name": "Retime and Scaling", + "Parameters": [], + "Type": 22 + } + }, + "name": "", + "effect_name": "Resolve Effect" + }, + { + "OTIO_SCHEMA": "Effect.1", + "metadata": { + "Resolve_OTIO": { + "Effect Name": "Video Faders", + "Enabled": true, + "Name": "Video Faders", + "Parameters": [], + "Type": 36 + } + }, + "name": "", + "effect_name": "Resolve Effect" + } + ], + "markers": [ + { + "OTIO_SCHEMA": "Marker.2", + "metadata": { + "Resolve_OTIO": { + "Keywords": [], + "Note": "{\"resolve_sub_products\": {\"io.ayon.creators.resolve.shot\": {\"id\": \"pyblish.avalon.instance\", \"productType\": \"shot\", \"productName\": \"shotMain\", \"active\": true, \"creator_identifier\": \"io.ayon.creators.resolve.shot\", \"variant\": \"Main\", \"folderPath\": \"/shots/reverse_speed/sh010\", \"task\": null, \"clip_variant\": \"\", \"clip_index\": \"2cb93726-2f27-41b0-b7f7-f48998327ce8\", \"clip_source_resolution\": {\"width\": \"1920\", \"height\": \"1080\", \"pixelAspect\": 1.0}, \"folder\": \"/shots/reverse_speed/sh010\", \"episode\": \"ep01\", \"sequence\": \"reverse_speed\", \"track\": \"{_track_}\", \"shot\": \"sh###\", \"hierarchy\": \"shots/reverse_speed\", \"sourceResolution\": false, \"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"parents\": [{\"folder_type\": \"folder\", \"entity_name\": \"shots\"}, {\"folder_type\": \"sequence\", \"entity_name\": \"reverse_speed\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"reverse_speed\", \"track\": \"Video_1\", \"shot\": \"sh010\"}, \"heroTrack\": true, \"uuid\": \"d08c8422-29a9-46e9-9d6d-37e2dd9f9f8b\", \"reviewTrack\": null, \"label\": \"/shots/reverse_speed/sh010 shot\", \"has_promised_context\": true, \"newHierarchyIntegration\": true, \"newAssetPublishing\": true, \"instance_id\": \"087e8c66-3ce7-41bf-a27e-3e5f7abc12fb\", \"creator_attributes\": {\"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"frameStart\": 1001, \"frameEnd\": 1042, \"clipIn\": 86400, \"clipOut\": 86441, \"clipDuration\": 41, \"sourceIn\": 39, \"sourceOut\": 80, \"fps\": \"from_selection\"}, \"publish_attributes\": {\"CollectSlackFamilies\": {\"additional_message\": \"\"}}}, \"io.ayon.creators.resolve.plate\": {\"id\": \"pyblish.avalon.instance\", \"productType\": \"plate\", \"productName\": \"plateVideo_1\", \"active\": true, \"creator_identifier\": \"io.ayon.creators.resolve.plate\", \"variant\": \"Video_1\", \"folderPath\": \"/shots/reverse_speed/sh010\", \"task\": null, \"clip_variant\": \"\", \"clip_index\": \"2cb93726-2f27-41b0-b7f7-f48998327ce8\", \"clip_source_resolution\": {\"width\": \"1920\", \"height\": \"1080\", \"pixelAspect\": 1.0}, \"folder\": \"/shots/reverse_speed/sh010\", \"episode\": \"ep01\", \"sequence\": \"reverse_speed\", \"track\": \"{_track_}\", \"shot\": \"sh###\", \"hierarchy\": \"shots/reverse_speed\", \"sourceResolution\": false, \"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"parents\": [{\"folder_type\": \"folder\", \"entity_name\": \"shots\"}, {\"folder_type\": \"sequence\", \"entity_name\": \"reverse_speed\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"reverse_speed\", \"track\": \"Video_1\", \"shot\": \"sh010\"}, \"heroTrack\": true, \"uuid\": \"d08c8422-29a9-46e9-9d6d-37e2dd9f9f8b\", \"reviewTrack\": null, \"parent_instance_id\": \"087e8c66-3ce7-41bf-a27e-3e5f7abc12fb\", \"label\": \"/shots/reverse_speed/sh010 plate\", \"has_promised_context\": true, \"newHierarchyIntegration\": true, \"newAssetPublishing\": true, \"instance_id\": \"63b97e7f-834f-490d-bba5-ae0e584f4a17\", \"creator_attributes\": {\"parentInstance\": \"/shots/reverse_speed/sh010 shot\", \"vSyncOn\": false, \"vSyncTrack\": \"Video 1\"}, \"publish_attributes\": {\"CollectSlackFamilies\": {\"additional_message\": \"\"}}}}, \"clip_index\": \"2cb93726-2f27-41b0-b7f7-f48998327ce8\", \"publish\": true}" + }, + "clip_index": "2cb93726-2f27-41b0-b7f7-f48998327ce8", + "publish": true, + "resolve_sub_products": { + "io.ayon.creators.resolve.plate": { + "active": true, + "clip_index": "2cb93726-2f27-41b0-b7f7-f48998327ce8", + "clip_source_resolution": { + "height": "1080", + "pixelAspect": 1.0, + "width": "1920" + }, + "clip_variant": "", + "creator_attributes": { + "parentInstance": "/shots/reverse_speed/sh010 shot", + "vSyncOn": false, + "vSyncTrack": "Video 1" + }, + "creator_identifier": "io.ayon.creators.resolve.plate", + "episode": "ep01", + "folder": "/shots/reverse_speed/sh010", + "folderPath": "/shots/reverse_speed/sh010", + "handleEnd": 10, + "handleStart": 10, + "has_promised_context": true, + "heroTrack": true, + "hierarchy": "shots/reverse_speed", + "hierarchyData": { + "episode": "ep01", + "folder": "shots", + "sequence": "reverse_speed", + "shot": "sh010", + "track": "Video_1" + }, + "id": "pyblish.avalon.instance", + "instance_id": "63b97e7f-834f-490d-bba5-ae0e584f4a17", + "label": "/shots/reverse_speed/sh010 plate", + "newAssetPublishing": true, + "newHierarchyIntegration": true, + "parent_instance_id": "087e8c66-3ce7-41bf-a27e-3e5f7abc12fb", + "parents": [ + { + "entity_name": "shots", + "folder_type": "folder" + }, + { + "entity_name": "reverse_speed", + "folder_type": "sequence" + } + ], + "productName": "plateVideo_1", + "productType": "plate", + "publish_attributes": { + "CollectSlackFamilies": { + "additional_message": "" + } + }, + "reviewTrack": null, + "sequence": "reverse_speed", + "shot": "sh###", + "sourceResolution": false, + "task": null, + "track": "{_track_}", + "uuid": "d08c8422-29a9-46e9-9d6d-37e2dd9f9f8b", + "variant": "Video_1", + "workfileFrameStart": 1001 + }, + "io.ayon.creators.resolve.shot": { + "active": true, + "clip_index": "2cb93726-2f27-41b0-b7f7-f48998327ce8", + "clip_source_resolution": { + "height": "1080", + "pixelAspect": 1.0, + "width": "1920" + }, + "clip_variant": "", + "creator_attributes": { + "clipDuration": 41, + "clipIn": 86400, + "clipOut": 86441, + "fps": "from_selection", + "frameEnd": 1042, + "frameStart": 1001, + "handleEnd": 10, + "handleStart": 10, + "sourceIn": 39, + "sourceOut": 80, + "workfileFrameStart": 1001 + }, + "creator_identifier": "io.ayon.creators.resolve.shot", + "episode": "ep01", + "folder": "/shots/reverse_speed/sh010", + "folderPath": "/shots/reverse_speed/sh010", + "handleEnd": 10, + "handleStart": 10, + "has_promised_context": true, + "heroTrack": true, + "hierarchy": "shots/reverse_speed", + "hierarchyData": { + "episode": "ep01", + "folder": "shots", + "sequence": "reverse_speed", + "shot": "sh010", + "track": "Video_1" + }, + "id": "pyblish.avalon.instance", + "instance_id": "087e8c66-3ce7-41bf-a27e-3e5f7abc12fb", + "label": "/shots/reverse_speed/sh010 shot", + "newAssetPublishing": true, + "newHierarchyIntegration": true, + "parents": [ + { + "entity_name": "shots", + "folder_type": "folder" + }, + { + "entity_name": "reverse_speed", + "folder_type": "sequence" + } + ], + "productName": "shotMain", + "productType": "shot", + "publish_attributes": { + "CollectSlackFamilies": { + "additional_message": "" + } + }, + "reviewTrack": null, + "sequence": "reverse_speed", + "shot": "sh###", + "sourceResolution": false, + "task": null, + "track": "{_track_}", + "uuid": "d08c8422-29a9-46e9-9d6d-37e2dd9f9f8b", + "variant": "Main", + "workfileFrameStart": 1001 + } + } + }, + "name": "AYONData", + "color": "GREEN", + "marked_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24.0, + "value": 1.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24.0, + "value": 59.0 + } + }, + "comment": "" + } + ], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ImageSequenceReference.1", + "metadata": {}, + "name": "output.[1000-1099].tif", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24.0, + "value": 100.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24.0, + "value": 0.0 + } + }, + "available_image_bounds": null, + "target_url_base": "C:\\Users\\robin\\OneDrive\\Bureau\\dev_ayon\\data\\img_sequence\\tif", + "name_prefix": "output.", + "name_suffix": ".tif", + "start_frame": 1000, + "frame_step": 1, + "rate": 24.0, + "frame_zero_padding": 4, + "missing_frame_policy": "error" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" +} \ No newline at end of file diff --git a/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py b/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py index 5147c2394a..2e7193444c 100644 --- a/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py +++ b/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py @@ -235,3 +235,59 @@ def test_img_sequence_conform_from_24_to_23_976fps(): handle_end=0, ) + +def test_img_sequence_reverse_speed_no_tc(): + """ + Img sequence clip + available files = 0-100 24fps + source_range = 20-41 24fps + """ + expected_data = { + 'mediaIn': 1020, + 'mediaOut': 1060, + 'handleStart': 0, + 'handleEnd': 0, + 'speed': -1.0, + 'versionData': { + 'retime': True, + 'speed': -1.0, + 'timewarps': [], + 'handleStart': 0, + 'handleEnd': 0 + } + } + + _check_expected_retimed_values( + "img_seq_reverse_speed_no_tc.json", + expected_data, + handle_start=0, + handle_end=0, + ) + +def test_img_sequence_reverse_speed_from_24_to_23_976fps(): + """ + Img sequence clip + available files = 941478-949084 24fps + source_range = 947726-947757 23.976fps + """ + expected_data = { + 'mediaIn': 948674, + 'mediaOut': 948705, + 'handleStart': 10, + 'handleEnd': 10, + 'speed': -1.0, + 'versionData': { + 'retime': True, + 'speed': -1.0, + 'timewarps': [], + 'handleStart': 10, + 'handleEnd': 10 + } + } + + _check_expected_retimed_values( + "img_seq_reverse_speed_24_to_23.976fps.json", + expected_data, + handle_start=10, + handle_end=10, + ) From 951fd51ab695d2194650237237b73a9170cdc9dc Mon Sep 17 00:00:00 2001 From: "robin@ynput.io" Date: Tue, 14 Jan 2025 13:17:39 +0100 Subject: [PATCH 024/103] Fix rouding issue with extract_otio_review --- client/ayon_core/plugins/publish/extract_otio_review.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/ayon_core/plugins/publish/extract_otio_review.py b/client/ayon_core/plugins/publish/extract_otio_review.py index d5f5f43cc9..275c3e7c58 100644 --- a/client/ayon_core/plugins/publish/extract_otio_review.py +++ b/client/ayon_core/plugins/publish/extract_otio_review.py @@ -147,7 +147,6 @@ class ExtractOTIOReview( 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 @@ -157,7 +156,7 @@ class ExtractOTIOReview( 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), From 3d9459f3c9002cba98652bf05968959deee5cfe8 Mon Sep 17 00:00:00 2001 From: "robin@ynput.io" Date: Tue, 14 Jan 2025 14:17:51 +0100 Subject: [PATCH 025/103] Report stagingDir_is_custom to apply_staging_dir. --- client/ayon_core/pipeline/create/creator_plugins.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_core/pipeline/create/creator_plugins.py b/client/ayon_core/pipeline/create/creator_plugins.py index 42e8e0b60f..445b41cb4b 100644 --- a/client/ayon_core/pipeline/create/creator_plugins.py +++ b/client/ayon_core/pipeline/create/creator_plugins.py @@ -916,6 +916,7 @@ class Creator(BaseCreator): instance.transient_data.update({ "stagingDir": staging_dir_path, "stagingDir_persistent": staging_dir_info.is_persistent, + "stagingDir_is_custom": staging_dir_info.is_custom, }) self.log.info(f"Applied staging dir to instance: {staging_dir_path}") From 2be4d7c2e8b7ff7d9d133c60d3eb0141640ba3f3 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Jan 2025 16:03:30 +0100 Subject: [PATCH 026/103] Disallow work area save as and browse if not in active task --- client/ayon_core/tools/workfiles/widgets/files_widget.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/ayon_core/tools/workfiles/widgets/files_widget.py b/client/ayon_core/tools/workfiles/widgets/files_widget.py index 16f0b6fce3..c5b5047c83 100644 --- a/client/ayon_core/tools/workfiles/widgets/files_widget.py +++ b/client/ayon_core/tools/workfiles/widgets/files_widget.py @@ -136,6 +136,8 @@ class FilesWidget(QtWidgets.QWidget): # Initial setup workarea_btn_open.setEnabled(False) + workarea_btn_browse.setEnabled(False) + workarea_btn_save.setEnabled(False) published_btn_copy_n_open.setEnabled(False) published_btn_change_context.setEnabled(False) published_btn_cancel.setVisible(False) @@ -264,6 +266,9 @@ class FilesWidget(QtWidgets.QWidget): def _on_workarea_path_changed(self, event): valid_path = event["path"] is not None self._workarea_btn_open.setEnabled(valid_path) + valid_task = self._selected_task_id is not None + self._workarea_btn_save.setEnabled(valid_task) + self._workarea_btn_browse.setEnabled(valid_task) # ------------------------------------------------------------- # Published workfiles From 44da46411c765d92611692e870dd6ef9322bfd1a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Jan 2025 16:18:42 +0100 Subject: [PATCH 027/103] Move logic to correct location --- client/ayon_core/tools/workfiles/widgets/files_widget.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/tools/workfiles/widgets/files_widget.py b/client/ayon_core/tools/workfiles/widgets/files_widget.py index c5b5047c83..dbe5966c31 100644 --- a/client/ayon_core/tools/workfiles/widgets/files_widget.py +++ b/client/ayon_core/tools/workfiles/widgets/files_widget.py @@ -266,9 +266,6 @@ class FilesWidget(QtWidgets.QWidget): def _on_workarea_path_changed(self, event): valid_path = event["path"] is not None self._workarea_btn_open.setEnabled(valid_path) - valid_task = self._selected_task_id is not None - self._workarea_btn_save.setEnabled(valid_task) - self._workarea_btn_browse.setEnabled(valid_task) # ------------------------------------------------------------- # Published workfiles @@ -283,8 +280,9 @@ class FilesWidget(QtWidgets.QWidget): self._published_btn_change_context.setEnabled(enabled) def _update_workarea_btns_state(self): - enabled = self._is_save_enabled + enabled = self._is_save_enabled and self._valid_selected_context self._workarea_btn_save.setEnabled(enabled) + self._workarea_btn_browse.setEnabled(self._valid_selected_context) def _on_published_repre_changed(self, event): self._valid_representation_id = event["representation_id"] is not None @@ -299,6 +297,7 @@ class FilesWidget(QtWidgets.QWidget): and self._selected_task_id is not None ) self._update_published_btns_state() + self._update_workarea_btns_state() def _on_published_save_clicked(self): result = self._exec_save_as_dialog() From 6ed19f26114e6e3f738ec9b86b3a653e91586875 Mon Sep 17 00:00:00 2001 From: "robin@ynput.io" Date: Tue, 14 Jan 2025 17:38:29 +0100 Subject: [PATCH 028/103] Append fix for retiming image sequence. --- client/ayon_core/pipeline/editorial.py | 6 +- .../editorial/resources/img_seq_2x_speed.json | 160 ++++++++ .../resources/img_seq_2x_speed_resolve.json | 369 ++++++++++++++++++ .../test_media_range_with_retimes.py | 60 +++ 4 files changed, 594 insertions(+), 1 deletion(-) create mode 100644 tests/client/ayon_core/pipeline/editorial/resources/img_seq_2x_speed.json create mode 100644 tests/client/ayon_core/pipeline/editorial/resources/img_seq_2x_speed_resolve.json diff --git a/client/ayon_core/pipeline/editorial.py b/client/ayon_core/pipeline/editorial.py index 0e9ee57bf9..c027d90d47 100644 --- a/client/ayon_core/pipeline/editorial.py +++ b/client/ayon_core/pipeline/editorial.py @@ -403,9 +403,13 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end): rate=src_duration.rate ) + retimed_duration = otio.opentime.RationalTime( + src_duration.value * abs(time_scalar), + src_duration.rate + ) trim_range = otio.opentime.TimeRange( start_time=src_in + offset_in, - duration=src_duration + offset_duration + duration=retimed_duration + offset_duration ) # preserve discrete frame numbers diff --git a/tests/client/ayon_core/pipeline/editorial/resources/img_seq_2x_speed.json b/tests/client/ayon_core/pipeline/editorial/resources/img_seq_2x_speed.json new file mode 100644 index 0000000000..80dfa34d4c --- /dev/null +++ b/tests/client/ayon_core/pipeline/editorial/resources/img_seq_2x_speed.json @@ -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" +} \ No newline at end of file diff --git a/tests/client/ayon_core/pipeline/editorial/resources/img_seq_2x_speed_resolve.json b/tests/client/ayon_core/pipeline/editorial/resources/img_seq_2x_speed_resolve.json new file mode 100644 index 0000000000..07daaf1548 --- /dev/null +++ b/tests/client/ayon_core/pipeline/editorial/resources/img_seq_2x_speed_resolve.json @@ -0,0 +1,369 @@ +{ + "OTIO_SCHEMA": "Clip.2", + "metadata": { + "Resolve_OTIO": {} + }, + "name": "output.[1001-1099].tif", + "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": 39.0 + } + }, + "effects": [ + { + "OTIO_SCHEMA": "LinearTimeWarp.1", + "metadata": {}, + "name": "", + "effect_name": "", + "time_scalar": 2.0 + }, + { + "OTIO_SCHEMA": "Effect.1", + "metadata": { + "Resolve_OTIO": { + "Effect Name": "Transform", + "Enabled": true, + "Name": "Transform", + "Parameters": [], + "Type": 2 + } + }, + "name": "", + "effect_name": "Resolve Effect" + }, + { + "OTIO_SCHEMA": "Effect.1", + "metadata": { + "Resolve_OTIO": { + "Effect Name": "Cropping", + "Enabled": true, + "Name": "Cropping", + "Parameters": [], + "Type": 3 + } + }, + "name": "", + "effect_name": "Resolve Effect" + }, + { + "OTIO_SCHEMA": "Effect.1", + "metadata": { + "Resolve_OTIO": { + "Effect Name": "Dynamic Zoom", + "Enabled": false, + "Name": "Dynamic Zoom", + "Parameters": [ + { + "Default Parameter Value": [ + 0.0, + 0.0 + ], + "Key Frames": { + "-19": { + "Value": [ + 0.0, + 0.0 + ], + "Variant Type": "POINTF" + }, + "981": { + "Value": [ + 0.0, + 0.0 + ], + "Variant Type": "POINTF" + } + }, + "Parameter ID": "dynamicZoomCenter", + "Parameter Value": [ + 0.0, + 0.0 + ], + "Variant Type": "POINTF" + }, + { + "Default Parameter Value": 1.0, + "Key Frames": { + "-19": { + "Value": 0.8, + "Variant Type": "Double" + }, + "981": { + "Value": 1.0, + "Variant Type": "Double" + } + }, + "Parameter ID": "dynamicZoomScale", + "Parameter Value": 1.0, + "Variant Type": "Double", + "maxValue": 100.0, + "minValue": 0.01 + } + ], + "Type": 59 + } + }, + "name": "", + "effect_name": "Resolve Effect" + }, + { + "OTIO_SCHEMA": "Effect.1", + "metadata": { + "Resolve_OTIO": { + "Effect Name": "Composite", + "Enabled": true, + "Name": "Composite", + "Parameters": [], + "Type": 1 + } + }, + "name": "", + "effect_name": "Resolve Effect" + }, + { + "OTIO_SCHEMA": "Effect.1", + "metadata": { + "Resolve_OTIO": { + "Effect Name": "Lens Correction", + "Enabled": true, + "Name": "Lens Correction", + "Parameters": [], + "Type": 43 + } + }, + "name": "", + "effect_name": "Resolve Effect" + }, + { + "OTIO_SCHEMA": "Effect.1", + "metadata": { + "Resolve_OTIO": { + "Effect Name": "Retime and Scaling", + "Enabled": true, + "Name": "Retime and Scaling", + "Parameters": [], + "Type": 22 + } + }, + "name": "", + "effect_name": "Resolve Effect" + }, + { + "OTIO_SCHEMA": "Effect.1", + "metadata": { + "Resolve_OTIO": { + "Effect Name": "Video Faders", + "Enabled": true, + "Name": "Video Faders", + "Parameters": [], + "Type": 36 + } + }, + "name": "", + "effect_name": "Resolve Effect" + } + ], + "markers": [ + { + "OTIO_SCHEMA": "Marker.2", + "metadata": { + "Resolve_OTIO": { + "Keywords": [], + "Note": "{\"resolve_sub_products\": {\"io.ayon.creators.resolve.shot\": {\"id\": \"pyblish.avalon.instance\", \"productType\": \"shot\", \"productName\": \"shotMain\", \"active\": true, \"creator_identifier\": \"io.ayon.creators.resolve.shot\", \"variant\": \"Main\", \"folderPath\": \"/shots/resolve_2x/sh010\", \"task\": null, \"clip_variant\": \"\", \"clip_index\": \"51983d2a-8a54-45fc-b17d-b837bdcb2545\", \"clip_source_resolution\": {\"width\": \"1920\", \"height\": \"1080\", \"pixelAspect\": 1.0}, \"folder\": \"/shots/resolve_2x/sh010\", \"episode\": \"ep01\", \"sequence\": \"resolve_2x\", \"track\": \"{_track_}\", \"shot\": \"sh###\", \"hierarchy\": \"shots/resolve_2x\", \"sourceResolution\": false, \"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"parents\": [{\"folder_type\": \"folder\", \"entity_name\": \"shots\"}, {\"folder_type\": \"sequence\", \"entity_name\": \"resolve_2x\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"resolve_2x\", \"track\": \"Video_1\", \"shot\": \"sh010\"}, \"heroTrack\": true, \"uuid\": \"04cd97b0-7e6e-4f58-b8b1-5f1956d53bfb\", \"reviewTrack\": \"Video 1\", \"label\": \"/shots/resolve_2x/sh010 shot\", \"has_promised_context\": true, \"newHierarchyIntegration\": true, \"newAssetPublishing\": true, \"instance_id\": \"cc8b970c-69c1-4eab-b94f-ae41358a80ba\", \"creator_attributes\": {\"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"frameStart\": 1001, \"frameEnd\": 1012, \"clipIn\": 86400, \"clipOut\": 86411, \"clipDuration\": 11, \"sourceIn\": 19, \"sourceOut\": 30, \"fps\": \"from_selection\"}, \"publish_attributes\": {\"CollectSlackFamilies\": {\"additional_message\": \"\"}}}, \"io.ayon.creators.resolve.plate\": {\"id\": \"pyblish.avalon.instance\", \"productType\": \"plate\", \"productName\": \"plateVideo_1\", \"active\": true, \"creator_identifier\": \"io.ayon.creators.resolve.plate\", \"variant\": \"Video_1\", \"folderPath\": \"/shots/resolve_2x/sh010\", \"task\": null, \"clip_variant\": \"\", \"clip_index\": \"51983d2a-8a54-45fc-b17d-b837bdcb2545\", \"clip_source_resolution\": {\"width\": \"1920\", \"height\": \"1080\", \"pixelAspect\": 1.0}, \"folder\": \"/shots/resolve_2x/sh010\", \"episode\": \"ep01\", \"sequence\": \"resolve_2x\", \"track\": \"{_track_}\", \"shot\": \"sh###\", \"hierarchy\": \"shots/resolve_2x\", \"sourceResolution\": false, \"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"parents\": [{\"folder_type\": \"folder\", \"entity_name\": \"shots\"}, {\"folder_type\": \"sequence\", \"entity_name\": \"resolve_2x\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"resolve_2x\", \"track\": \"Video_1\", \"shot\": \"sh010\"}, \"heroTrack\": true, \"uuid\": \"04cd97b0-7e6e-4f58-b8b1-5f1956d53bfb\", \"reviewTrack\": \"Video 1\", \"parent_instance_id\": \"cc8b970c-69c1-4eab-b94f-ae41358a80ba\", \"label\": \"/shots/resolve_2x/sh010 plate\", \"has_promised_context\": true, \"newHierarchyIntegration\": true, \"newAssetPublishing\": true, \"instance_id\": \"564ef731-c518-4c8f-918d-b27d0c35856c\", \"creator_attributes\": {\"parentInstance\": \"/shots/resolve_2x/sh010 shot\", \"vSyncOn\": false, \"vSyncTrack\": \"Video 1\"}, \"publish_attributes\": {\"CollectSlackFamilies\": {\"additional_message\": \"\"}}}}, \"clip_index\": \"51983d2a-8a54-45fc-b17d-b837bdcb2545\", \"publish\": true}" + }, + "clip_index": "51983d2a-8a54-45fc-b17d-b837bdcb2545", + "publish": true, + "resolve_sub_products": { + "io.ayon.creators.resolve.plate": { + "active": true, + "clip_index": "51983d2a-8a54-45fc-b17d-b837bdcb2545", + "clip_source_resolution": { + "height": "1080", + "pixelAspect": 1.0, + "width": "1920" + }, + "clip_variant": "", + "creator_attributes": { + "parentInstance": "/shots/resolve_2x/sh010 shot", + "vSyncOn": false, + "vSyncTrack": "Video 1" + }, + "creator_identifier": "io.ayon.creators.resolve.plate", + "episode": "ep01", + "folder": "/shots/resolve_2x/sh010", + "folderPath": "/shots/resolve_2x/sh010", + "handleEnd": 10, + "handleStart": 10, + "has_promised_context": true, + "heroTrack": true, + "hierarchy": "shots/resolve_2x", + "hierarchyData": { + "episode": "ep01", + "folder": "shots", + "sequence": "resolve_2x", + "shot": "sh010", + "track": "Video_1" + }, + "id": "pyblish.avalon.instance", + "instance_id": "564ef731-c518-4c8f-918d-b27d0c35856c", + "label": "/shots/resolve_2x/sh010 plate", + "newAssetPublishing": true, + "newHierarchyIntegration": true, + "parent_instance_id": "cc8b970c-69c1-4eab-b94f-ae41358a80ba", + "parents": [ + { + "entity_name": "shots", + "folder_type": "folder" + }, + { + "entity_name": "resolve_2x", + "folder_type": "sequence" + } + ], + "productName": "plateVideo_1", + "productType": "plate", + "publish_attributes": { + "CollectSlackFamilies": { + "additional_message": "" + } + }, + "reviewTrack": "Video 1", + "sequence": "resolve_2x", + "shot": "sh###", + "sourceResolution": false, + "task": null, + "track": "{_track_}", + "uuid": "04cd97b0-7e6e-4f58-b8b1-5f1956d53bfb", + "variant": "Video_1", + "workfileFrameStart": 1001 + }, + "io.ayon.creators.resolve.shot": { + "active": true, + "clip_index": "51983d2a-8a54-45fc-b17d-b837bdcb2545", + "clip_source_resolution": { + "height": "1080", + "pixelAspect": 1.0, + "width": "1920" + }, + "clip_variant": "", + "creator_attributes": { + "clipDuration": 11, + "clipIn": 86400, + "clipOut": 86411, + "fps": "from_selection", + "frameEnd": 1012, + "frameStart": 1001, + "handleEnd": 10, + "handleStart": 10, + "sourceIn": 19, + "sourceOut": 30, + "workfileFrameStart": 1001 + }, + "creator_identifier": "io.ayon.creators.resolve.shot", + "episode": "ep01", + "folder": "/shots/resolve_2x/sh010", + "folderPath": "/shots/resolve_2x/sh010", + "handleEnd": 10, + "handleStart": 10, + "has_promised_context": true, + "heroTrack": true, + "hierarchy": "shots/resolve_2x", + "hierarchyData": { + "episode": "ep01", + "folder": "shots", + "sequence": "resolve_2x", + "shot": "sh010", + "track": "Video_1" + }, + "id": "pyblish.avalon.instance", + "instance_id": "cc8b970c-69c1-4eab-b94f-ae41358a80ba", + "label": "/shots/resolve_2x/sh010 shot", + "newAssetPublishing": true, + "newHierarchyIntegration": true, + "parents": [ + { + "entity_name": "shots", + "folder_type": "folder" + }, + { + "entity_name": "resolve_2x", + "folder_type": "sequence" + } + ], + "productName": "shotMain", + "productType": "shot", + "publish_attributes": { + "CollectSlackFamilies": { + "additional_message": "" + } + }, + "reviewTrack": "Video 1", + "sequence": "resolve_2x", + "shot": "sh###", + "sourceResolution": false, + "task": null, + "track": "{_track_}", + "uuid": "04cd97b0-7e6e-4f58-b8b1-5f1956d53bfb", + "variant": "Main", + "workfileFrameStart": 1001 + } + } + }, + "name": "AYONData", + "color": "GREEN", + "marked_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24.0, + "value": 1.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24.0, + "value": 24.0 + } + }, + "comment": "" + } + ], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ImageSequenceReference.1", + "metadata": {}, + "name": "output.[1001-1099].tif", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24.0, + "value": 99.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24.0, + "value": 0.0 + } + }, + "available_image_bounds": null, + "target_url_base": "C:\\Users\\robin\\OneDrive\\Bureau\\dev_ayon\\data\\img_sequence\\tif", + "name_prefix": "output.", + "name_suffix": ".tif", + "start_frame": 1001, + "frame_step": 1, + "rate": 24.0, + "frame_zero_padding": 4, + "missing_frame_policy": "error" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" +} \ No newline at end of file diff --git a/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py b/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py index 2e7193444c..da257eca58 100644 --- a/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py +++ b/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py @@ -291,3 +291,63 @@ def test_img_sequence_reverse_speed_from_24_to_23_976fps(): handle_start=10, handle_end=10, ) + + +def test_img_sequence_2x_speed(): + """ + Img sequence clip + available files = 948674-948974 25fps + source_range = 948850-948870 23.976fps + speed = 2.0 + """ + expected_data = { + 'mediaIn': 948850, + 'mediaOut': 948871, + 'handleStart': 20, + 'handleEnd': 20, + 'speed': 2.0, + 'versionData': { + 'retime': True, + 'speed': 2.0, + 'timewarps': [], + 'handleStart': 20, + 'handleEnd': 20 + } + } + + _check_expected_retimed_values( + "img_seq_2x_speed.json", + expected_data, + handle_start=10, + handle_end=10, + ) + + +def test_img_sequence_2x_speed_resolve(): + """ + Img sequence clip + available files = 0-99 24fps + source_range = 38-49 24fps + speed = 2.0 + """ + expected_data = { + 'mediaIn': 1040, + 'mediaOut': 1061, + 'handleStart': 20, + 'handleEnd': 20, + 'speed': 2.0, + 'versionData': { + 'retime': True, + 'speed': 2.0, + 'timewarps': [], + 'handleStart': 20, + 'handleEnd': 20 + } + } + + _check_expected_retimed_values( + "img_seq_2x_speed_resolve.json", + expected_data, + handle_start=10, + handle_end=10, + ) From 76c7d90c8c1c8c1eeb81c893adead84f6be5f7c7 Mon Sep 17 00:00:00 2001 From: "robin@ynput.io" Date: Tue, 14 Jan 2025 18:15:01 +0100 Subject: [PATCH 029/103] Add fix + test for image sequence frozen frame. --- client/ayon_core/pipeline/editorial.py | 8 +- .../resources/img_seq_freeze_frame.json | 160 ++++++++++++++++++ .../test_media_range_with_retimes.py | 31 ++++ 3 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 tests/client/ayon_core/pipeline/editorial/resources/img_seq_freeze_frame.json diff --git a/client/ayon_core/pipeline/editorial.py b/client/ayon_core/pipeline/editorial.py index c027d90d47..9a241087f6 100644 --- a/client/ayon_core/pipeline/editorial.py +++ b/client/ayon_core/pipeline/editorial.py @@ -256,8 +256,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() diff --git a/tests/client/ayon_core/pipeline/editorial/resources/img_seq_freeze_frame.json b/tests/client/ayon_core/pipeline/editorial/resources/img_seq_freeze_frame.json new file mode 100644 index 0000000000..05b48370b2 --- /dev/null +++ b/tests/client/ayon_core/pipeline/editorial/resources/img_seq_freeze_frame.json @@ -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": 5.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 23.976024627685547, + "value": 909990.8339241028 + } + }, + "effects": [ + { + "OTIO_SCHEMA": "FreezeFrame.1", + "metadata": {}, + "name": "FreezeFrame", + "effect_name": "FreezeFrame", + "time_scalar": 0.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_freeze_frame/sh010\", \"task\": null, \"clip_index\": \"85ABEEEA-6A90-CE47-9DE2-73BAB11EE31D\", \"hierarchy\": \"shots/hiero_freeze_frame\", \"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"hiero_freeze_frame\", \"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_freeze_frame\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"hiero_freeze_frame\", \"track\": \"Video_1\"}, \"heroTrack\": true, \"uuid\": \"08ba1c0a-fc51-4275-b6c8-1cb81381b043\", \"reviewTrack\": \"Video 1\", \"review\": true, \"folderName\": \"sh010\", \"label\": \"/shots/hiero_freeze_frame/sh010 shotMain\", \"newHierarchyIntegration\": true, \"instance_id\": \"892de813-fc78-4d92-b25f-4ea5c4791bb8\", \"creator_attributes\": {\"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"frameStart\": 1001, \"frameEnd\": 1006, \"clipIn\": 0, \"clipOut\": 4, \"clipDuration\": 5, \"sourceIn\": 181.0, \"sourceOut\": 181.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_freeze_frame/sh010\", \"task\": null, \"clip_index\": \"85ABEEEA-6A90-CE47-9DE2-73BAB11EE31D\", \"hierarchy\": \"shots/hiero_freeze_frame\", \"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"hiero_freeze_frame\", \"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_freeze_frame\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"hiero_freeze_frame\", \"track\": \"Video_1\"}, \"heroTrack\": true, \"uuid\": \"08ba1c0a-fc51-4275-b6c8-1cb81381b043\", \"reviewTrack\": \"Video 1\", \"review\": true, \"folderName\": \"sh010\", \"parent_instance_id\": \"892de813-fc78-4d92-b25f-4ea5c4791bb8\", \"label\": \"/shots/hiero_freeze_frame/sh010 plateVideo_1\", \"newHierarchyIntegration\": true, \"instance_id\": \"24eb8386-4c42-4439-ac41-17ec4efb0073\", \"creator_attributes\": {\"parentInstance\": \"/shots/hiero_freeze_frame/sh010 shotMain\", \"review\": true, \"reviewableSource\": \"Video 1\"}, \"publish_attributes\": {\"CollectSlackFamilies\": {\"additional_message\": \"\"}}}}, \"clip_index\": \"85ABEEEA-6A90-CE47-9DE2-73BAB11EE31D\"}", + "label": "AYONdata_a8304fcf", + "note": "AYON data container" + }, + "name": "AYONdata_a8304fcf", + "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" +} \ No newline at end of file diff --git a/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py b/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py index da257eca58..1041dfe4dd 100644 --- a/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py +++ b/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py @@ -351,3 +351,34 @@ def test_img_sequence_2x_speed_resolve(): handle_start=10, handle_end=10, ) + + +def test_img_sequence_frozen_frame(): + """ + Img sequence clip + available files = 948674-948974 25fps + source_range = 909990.8339241028 + - 909995.8339241028 23.976fps + speed = 0.0 + """ + expected_data = { + 'mediaIn': 948855, + 'mediaOut': 948855, + 'handleStart': 0, + 'handleEnd': 0, + 'speed': 0.0, + 'versionData': { + 'retime': True, + 'speed': 0.0, + 'timewarps': [], + 'handleStart': 0, + 'handleEnd': 0, + } + } + + _check_expected_retimed_values( + "img_seq_freeze_frame.json", + expected_data, + handle_start=10, + handle_end=10, + ) From 1123d31750260fef569df2217990a7daa8157956 Mon Sep 17 00:00:00 2001 From: "robin@ynput.io" Date: Wed, 15 Jan 2025 11:18:23 +0100 Subject: [PATCH 030/103] Add fixes and tests for qt retime and frozen frame. --- client/ayon_core/pipeline/editorial.py | 13 +- .../publish/collect_otio_subset_resources.py | 14 +- .../editorial/resources/qt_freeze_frame.json | 159 +++++++++++++++++ .../resources/qt_reverse_speed_2x.json | 160 ++++++++++++++++++ .../test_media_range_with_retimes.py | 64 +++++++ 5 files changed, 400 insertions(+), 10 deletions(-) create mode 100644 tests/client/ayon_core/pipeline/editorial/resources/qt_freeze_frame.json create mode 100644 tests/client/ayon_core/pipeline/editorial/resources/qt_reverse_speed_2x.json diff --git a/client/ayon_core/pipeline/editorial.py b/client/ayon_core/pipeline/editorial.py index 9a241087f6..f5a876b5ba 100644 --- a/client/ayon_core/pipeline/editorial.py +++ b/client/ayon_core/pipeline/editorial.py @@ -429,13 +429,12 @@ 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 += offset_out + offset_duration -= 1 # duration 1 frame -> freeze frame -> end = start + 0 + offset_duration = max(0, offset_duration) # negative duration = frozen frame + media_out_trimmed = media_in_trimmed + offset_duration media_in = available_range.start_time.value media_out = available_range.end_time_inclusive().value diff --git a/client/ayon_core/plugins/publish/collect_otio_subset_resources.py b/client/ayon_core/plugins/publish/collect_otio_subset_resources.py index 0fb30326c6..ed7e8ac4f1 100644 --- a/client/ayon_core/plugins/publish/collect_otio_subset_resources.py +++ b/client/ayon_core/plugins/publish/collect_otio_subset_resources.py @@ -174,9 +174,17 @@ class CollectOtioSubsetResources( 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) + 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") diff --git a/tests/client/ayon_core/pipeline/editorial/resources/qt_freeze_frame.json b/tests/client/ayon_core/pipeline/editorial/resources/qt_freeze_frame.json new file mode 100644 index 0000000000..de665eaea7 --- /dev/null +++ b/tests/client/ayon_core/pipeline/editorial/resources/qt_freeze_frame.json @@ -0,0 +1,159 @@ +{ + "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": 29.970030784606934 + } + }, + "effects": [ + { + "OTIO_SCHEMA": "FreezeFrame.1", + "metadata": {}, + "name": "FreezeFrame", + "effect_name": "FreezeFrame", + "time_scalar": 0.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_qt_freeze_frame/sh010\", \"task\": null, \"clip_index\": \"D812B65C-F1C7-DA48-9060-F932A50B2BB4\", \"hierarchy\": \"shots/hiero_qt_freeze_frame\", \"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"hiero_qt_freeze_frame\", \"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_qt_freeze_frame\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"hiero_qt_freeze_frame\", \"track\": \"Video_1\"}, \"heroTrack\": true, \"uuid\": \"e896c630-8c44-408f-a1a0-bffbe330dbe9\", \"reviewTrack\": \"Video 1\", \"review\": true, \"folderName\": \"sh010\", \"label\": \"/shots/hiero_qt_freeze_frame/sh010 shotMain\", \"newHierarchyIntegration\": true, \"instance_id\": \"75b9112f-6357-4235-8a74-252467d6553d\", \"creator_attributes\": {\"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"frameStart\": 1001, \"frameEnd\": 1012, \"clipIn\": 0, \"clipOut\": 10, \"clipDuration\": 11, \"sourceIn\": 30.0, \"sourceOut\": 30.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_qt_freeze_frame/sh010\", \"task\": null, \"clip_index\": \"D812B65C-F1C7-DA48-9060-F932A50B2BB4\", \"hierarchy\": \"shots/hiero_qt_freeze_frame\", \"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"hiero_qt_freeze_frame\", \"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_qt_freeze_frame\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"hiero_qt_freeze_frame\", \"track\": \"Video_1\"}, \"heroTrack\": true, \"uuid\": \"e896c630-8c44-408f-a1a0-bffbe330dbe9\", \"reviewTrack\": \"Video 1\", \"review\": true, \"folderName\": \"sh010\", \"parent_instance_id\": \"75b9112f-6357-4235-8a74-252467d6553d\", \"label\": \"/shots/hiero_qt_freeze_frame/sh010 plateVideo_1\", \"newHierarchyIntegration\": true, \"instance_id\": \"95912bf0-aa1c-47ae-a821-fef410c32687\", \"creator_attributes\": {\"parentInstance\": \"/shots/hiero_qt_freeze_frame/sh010 shotMain\", \"review\": true, \"reviewableSource\": \"Video 1\"}, \"publish_attributes\": {\"CollectSlackFamilies\": {\"additional_message\": \"\"}}}}, \"clip_index\": \"D812B65C-F1C7-DA48-9060-F932A50B2BB4\"}", + "label": "AYONdata_e681ec48", + "note": "AYON data container" + }, + "name": "AYONdata_e681ec48", + "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": "ExternalReference.1", + "metadata": { + "ayon.source.colorspace": "Gamma2.2", + "ayon.source.height": 1080, + "ayon.source.pixelAspect": 1.0, + "ayon.source.width": 1920, + "clip.properties.blendfunc": "0", + "clip.properties.colourspacename": "error", + "clip.properties.domainroot": "", + "clip.properties.enabled": "1", + "clip.properties.expanded": "1", + "clip.properties.opacity": "1", + "clip.properties.valuesource": "", + "clip.properties.ycbcrmatrix": "default (Rec 709)", + "com.apple.quicktime.codec": "H.264", + "foundry.source.audio": "", + "foundry.source.bitmapsize": "0", + "foundry.source.bitsperchannel": "0", + "foundry.source.channelformat": "integer", + "foundry.source.colourtransform": "Gamma2.2", + "foundry.source.duration": "101", + "foundry.source.filename": "qt_no_tc_24fps.mov", + "foundry.source.filesize": "", + "foundry.source.fragments": "1", + "foundry.source.framerate": "24", + "foundry.source.fullpath": "", + "foundry.source.height": "1080", + "foundry.source.layers": "colour", + "foundry.source.path": "C:/Users/robin/OneDrive/Bureau/dev_ayon/data/qt_no_tc_24fps.mov", + "foundry.source.pixelAspect": "1", + "foundry.source.pixelAspectRatio": "", + "foundry.source.pixelformat": "RGBA (Float32) Open Color IO space: 114", + "foundry.source.reelID": "", + "foundry.source.resolution": "", + "foundry.source.samplerate": "Invalid", + "foundry.source.shoottime": "4294967295", + "foundry.source.shortfilename": "qt_no_tc_24fps.mov", + "foundry.source.shot": "", + "foundry.source.shotDate": "", + "foundry.source.startTC": "", + "foundry.source.starttime": "0", + "foundry.source.timecode": "0", + "foundry.source.type": "QuickTime H.264", + "foundry.source.umid": "16634e88-6450-4727-6c6e-501f4b31b637", + "foundry.source.width": "1920", + "foundry.timeline.autodiskcachemode": "Manual", + "foundry.timeline.colorSpace": "Gamma2.2", + "foundry.timeline.duration": "101", + "foundry.timeline.framerate": "24", + "foundry.timeline.outputformat": "", + "foundry.timeline.poster": "0", + "foundry.timeline.posterLayer": "colour", + "foundry.timeline.samplerate": "Invalid", + "media.input.bitsperchannel": "8-bit fixed", + "media.input.ctime": "2024-09-25 17:16:12", + "media.input.filename": "C:/Users/robin/OneDrive/Bureau/dev_ayon/data/qt_no_tc_24fps.mov", + "media.input.filereader": "mov64", + "media.input.filesize": "14631252", + "media.input.frame": "1", + "media.input.frame_rate": "24", + "media.input.height": "1080", + "media.input.mtime": "2024-09-25 17:16:16", + "media.input.pixel_aspect": "1", + "media.input.timecode": "00:00:00:00", + "media.input.width": "1920", + "media.quicktime.codec_id": "avc1", + "media.quicktime.codec_name": "h264", + "media.quicktime.encoder": "H.264", + "media.quicktime.nclc_matrix": "BT709", + "media.quicktime.nclc_primaries": "ITU-R BT.709", + "media.quicktime.nclc_transfer_function": "ITU-R BT.709", + "media.quicktime.thefoundry.Application": "Nuke", + "media.quicktime.thefoundry.ApplicationVersion": "15.0v5", + "media.quicktime.thefoundry.Colorspace": "Gamma2.2", + "media.quicktime.thefoundry.Writer": "mov64", + "media.quicktime.thefoundry.YCbCrMatrix": "Rec 709", + "media.stream.pixel_format": "yuv420p", + "uk.co.thefoundry.Application": "Nuke", + "uk.co.thefoundry.ApplicationVersion": "15.0v5", + "uk.co.thefoundry.Colorspace": "Gamma2.2", + "uk.co.thefoundry.Writer": "mov64", + "uk.co.thefoundry.YCbCrMatrix": "Rec 709" + }, + "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": 0.0 + } + }, + "available_image_bounds": null, + "target_url": "C:/Users/robin/OneDrive/Bureau/dev_ayon/data/qt_no_tc_24fps.mov" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" +} \ No newline at end of file diff --git a/tests/client/ayon_core/pipeline/editorial/resources/qt_reverse_speed_2x.json b/tests/client/ayon_core/pipeline/editorial/resources/qt_reverse_speed_2x.json new file mode 100644 index 0000000000..467bb8d7a1 --- /dev/null +++ b/tests/client/ayon_core/pipeline/editorial/resources/qt_reverse_speed_2x.json @@ -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": 29.970030784606934 + } + }, + "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_qt_neg_2x/sh010\", \"task\": null, \"clip_index\": \"D812B65C-F1C7-DA48-9060-F932A50B2BB4\", \"hierarchy\": \"shots/hiero_qt_neg_2x\", \"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"hiero_qt_neg_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_qt_neg_2x\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"hiero_qt_neg_2x\", \"track\": \"Video_1\"}, \"heroTrack\": true, \"uuid\": \"a59e3db6-0b60-41f5-827c-9d280547bf31\", \"reviewTrack\": \"Video 1\", \"review\": true, \"folderName\": \"sh010\", \"label\": \"/shots/hiero_qt_neg_2x/sh010 shotMain\", \"newHierarchyIntegration\": true, \"instance_id\": \"f98ac652-ed03-4985-bad9-5b028ceeddba\", \"creator_attributes\": {\"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"frameStart\": 1001, \"frameEnd\": 1012, \"clipIn\": 0, \"clipOut\": 10, \"clipDuration\": 11, \"sourceIn\": 50.0, \"sourceOut\": 30.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_qt_neg_2x/sh010\", \"task\": null, \"clip_index\": \"D812B65C-F1C7-DA48-9060-F932A50B2BB4\", \"hierarchy\": \"shots/hiero_qt_neg_2x\", \"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"hiero_qt_neg_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_qt_neg_2x\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"hiero_qt_neg_2x\", \"track\": \"Video_1\"}, \"heroTrack\": true, \"uuid\": \"a59e3db6-0b60-41f5-827c-9d280547bf31\", \"reviewTrack\": \"Video 1\", \"review\": true, \"folderName\": \"sh010\", \"parent_instance_id\": \"f98ac652-ed03-4985-bad9-5b028ceeddba\", \"label\": \"/shots/hiero_qt_neg_2x/sh010 plateVideo_1\", \"newHierarchyIntegration\": true, \"instance_id\": \"5c9c047c-43fa-42a1-a00f-e9c9d6e5a3c4\", \"creator_attributes\": {\"parentInstance\": \"/shots/hiero_qt_neg_2x/sh010 shotMain\", \"review\": true, \"reviewableSource\": \"Video 1\"}, \"publish_attributes\": {\"CollectSlackFamilies\": {\"additional_message\": \"\"}}}}, \"clip_index\": \"D812B65C-F1C7-DA48-9060-F932A50B2BB4\"}", + "label": "AYONdata_fd6d196e", + "note": "AYON data container" + }, + "name": "AYONdata_fd6d196e", + "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": "ExternalReference.1", + "metadata": { + "ayon.source.colorspace": "Gamma2.2", + "ayon.source.height": 1080, + "ayon.source.pixelAspect": 1.0, + "ayon.source.width": 1920, + "clip.properties.blendfunc": "0", + "clip.properties.colourspacename": "error", + "clip.properties.domainroot": "", + "clip.properties.enabled": "1", + "clip.properties.expanded": "1", + "clip.properties.opacity": "1", + "clip.properties.valuesource": "", + "clip.properties.ycbcrmatrix": "0", + "com.apple.quicktime.codec": "H.264", + "foundry.source.audio": "", + "foundry.source.bitmapsize": "0", + "foundry.source.bitsperchannel": "0", + "foundry.source.channelformat": "integer", + "foundry.source.colourtransform": "Gamma2.2", + "foundry.source.duration": "101", + "foundry.source.filename": "qt_no_tc_24fps.mov", + "foundry.source.filesize": "", + "foundry.source.fragments": "1", + "foundry.source.framerate": "24", + "foundry.source.fullpath": "", + "foundry.source.height": "1080", + "foundry.source.layers": "colour", + "foundry.source.path": "C:/Users/robin/OneDrive/Bureau/dev_ayon/data/qt_no_tc_24fps.mov", + "foundry.source.pixelAspect": "1", + "foundry.source.pixelAspectRatio": "", + "foundry.source.pixelformat": "RGBA (Float32) Open Color IO space: 114", + "foundry.source.reelID": "", + "foundry.source.resolution": "", + "foundry.source.samplerate": "Invalid", + "foundry.source.shoottime": "4294967295", + "foundry.source.shortfilename": "qt_no_tc_24fps.mov", + "foundry.source.shot": "", + "foundry.source.shotDate": "", + "foundry.source.startTC": "", + "foundry.source.starttime": "0", + "foundry.source.timecode": "0", + "foundry.source.type": "QuickTime H.264", + "foundry.source.umid": "16634e88-6450-4727-6c6e-501f4b31b637", + "foundry.source.umidOriginator": "foundry.source.umid", + "foundry.source.width": "1920", + "foundry.timeline.autodiskcachemode": "Manual", + "foundry.timeline.colorSpace": "Gamma2.2", + "foundry.timeline.duration": "101", + "foundry.timeline.framerate": "24", + "foundry.timeline.outputformat": "", + "foundry.timeline.poster": "0", + "foundry.timeline.posterLayer": "colour", + "foundry.timeline.samplerate": "Invalid", + "media.input.bitsperchannel": "8-bit fixed", + "media.input.ctime": "2024-09-25 17:16:12", + "media.input.filename": "C:/Users/robin/OneDrive/Bureau/dev_ayon/data/qt_no_tc_24fps.mov", + "media.input.filereader": "mov64", + "media.input.filesize": "14631252", + "media.input.frame": "1", + "media.input.frame_rate": "24", + "media.input.height": "1080", + "media.input.mtime": "2024-09-25 17:16:16", + "media.input.pixel_aspect": "1", + "media.input.timecode": "00:00:00:00", + "media.input.width": "1920", + "media.quicktime.codec_id": "avc1", + "media.quicktime.codec_name": "h264", + "media.quicktime.encoder": "H.264", + "media.quicktime.nclc_matrix": "BT709", + "media.quicktime.nclc_primaries": "ITU-R BT.709", + "media.quicktime.nclc_transfer_function": "ITU-R BT.709", + "media.quicktime.thefoundry.Application": "Nuke", + "media.quicktime.thefoundry.ApplicationVersion": "15.0v5", + "media.quicktime.thefoundry.Colorspace": "Gamma2.2", + "media.quicktime.thefoundry.Writer": "mov64", + "media.quicktime.thefoundry.YCbCrMatrix": "Rec 709", + "media.stream.pixel_format": "yuv420p", + "uk.co.thefoundry.Application": "Nuke", + "uk.co.thefoundry.ApplicationVersion": "15.0v5", + "uk.co.thefoundry.Colorspace": "Gamma2.2", + "uk.co.thefoundry.Writer": "mov64", + "uk.co.thefoundry.YCbCrMatrix": "Rec 709" + }, + "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": 0.0 + } + }, + "available_image_bounds": null, + "target_url": "C:/Users/robin/OneDrive/Bureau/dev_ayon/data/qt_no_tc_24fps.mov" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" +} \ No newline at end of file diff --git a/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py b/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py index 1041dfe4dd..d4badd8345 100644 --- a/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py +++ b/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py @@ -113,6 +113,70 @@ def test_movie_retime_effect(): ) +def test_movie_reverse_speed_2x(): + """ + Movie clip (no timecode) + available files = 0-100 24fps + source_range = 29.97-40.97 23.976fps + speed = -2.0 + """ + expected_data = { + # not exactly 30 because of 23.976 rouding + 'mediaIn': 30.000000000000004, + # not exactly 50 because of 23.976 rouding + 'mediaOut': 51.02199940144827, + 'handleStart': 20, + 'handleEnd': 20, + 'speed': -2.0, + 'versionData': { + 'retime': True, + 'speed': -2.0, + 'timewarps': [], + 'handleStart': 20, + 'handleEnd': 20, + } + } + + _check_expected_retimed_values( + "qt_reverse_speed_2x.json", + expected_data, + handle_start=10, + handle_end=10, + ) + + + +def test_movie_frozen_frame(): + """ + Movie clip (no timecode) + available files = 0-100 24fps + source_range = 29.97-40.97 23.976fps + speed = 0.0 + """ + expected_data = { + # not exactly 30 because of OTIO rounding + 'mediaIn': 30.000000000000004, + 'mediaOut': 30.000000000000004, + 'handleStart': 0, + 'handleEnd': 0, + 'speed': 0.0, + 'versionData': { + 'retime': True, + 'speed': 0.0, + 'timewarps': [], + 'handleStart': 0, + 'handleEnd': 0, + } + } + + _check_expected_retimed_values( + "qt_freeze_frame.json", + expected_data, + handle_start=10, + handle_end=10, + ) + + def test_img_sequence_no_handles(): """ Img sequence clip (no embedded timecode) From 704ef232b6c20ac7f0767f0a997e11092986eb94 Mon Sep 17 00:00:00 2001 From: "robin@ynput.io" Date: Wed, 15 Jan 2025 13:20:22 +0100 Subject: [PATCH 031/103] Edit link to OTIO issue. --- .../pipeline/editorial/test_media_range_with_retimes.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py b/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py index d4badd8345..c045379806 100644 --- a/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py +++ b/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py @@ -122,8 +122,9 @@ def test_movie_reverse_speed_2x(): """ expected_data = { # not exactly 30 because of 23.976 rouding + # https://github.com/AcademySoftwareFoundation/ + # OpenTimelineIO/issues/1822 'mediaIn': 30.000000000000004, - # not exactly 50 because of 23.976 rouding 'mediaOut': 51.02199940144827, 'handleStart': 20, 'handleEnd': 20, @@ -155,6 +156,8 @@ def test_movie_frozen_frame(): """ expected_data = { # not exactly 30 because of OTIO rounding + # https://github.com/AcademySoftwareFoundation/ + # OpenTimelineIO/issues/1822 'mediaIn': 30.000000000000004, 'mediaOut': 30.000000000000004, 'handleStart': 0, From b185972a954f7111591576b90ee3436a7b775bf2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 15 Jan 2025 14:02:20 +0100 Subject: [PATCH 032/103] Avoid database queries when collecting managed staging dir --- client/ayon_core/pipeline/publish/lib.py | 1 + client/ayon_core/pipeline/staging_dir.py | 5 ++++- client/ayon_core/pipeline/template_data.py | 13 ++++++++++--- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/pipeline/publish/lib.py b/client/ayon_core/pipeline/publish/lib.py index 40a9b47aba..25495ed38b 100644 --- a/client/ayon_core/pipeline/publish/lib.py +++ b/client/ayon_core/pipeline/publish/lib.py @@ -708,6 +708,7 @@ def get_instance_staging_dir(instance): project_settings=context.data["project_settings"], template_data=template_data, always_return_path=True, + username=context.data["user"], ) staging_dir_path = staging_dir_info.directory diff --git a/client/ayon_core/pipeline/staging_dir.py b/client/ayon_core/pipeline/staging_dir.py index 37d6b955e2..2004096bd0 100644 --- a/client/ayon_core/pipeline/staging_dir.py +++ b/client/ayon_core/pipeline/staging_dir.py @@ -130,6 +130,7 @@ def get_staging_dir_info( logger: Optional[logging.Logger] = None, prefix: Optional[str] = None, suffix: Optional[str] = None, + username: Optional[str] = None, ) -> Optional[StagingDir]: """Get staging dir info data. @@ -183,7 +184,9 @@ def get_staging_dir_info( # making few queries to database ctx_data = get_template_data( - project_entity, folder_entity, task_entity, host_name + project_entity, folder_entity, task_entity, host_name, + settings=project_settings, + username=username ) # add additional data diff --git a/client/ayon_core/pipeline/template_data.py b/client/ayon_core/pipeline/template_data.py index c7aa46fd62..0a95a98be8 100644 --- a/client/ayon_core/pipeline/template_data.py +++ b/client/ayon_core/pipeline/template_data.py @@ -4,7 +4,7 @@ from ayon_core.settings import get_studio_settings from ayon_core.lib.local_settings import get_ayon_username -def get_general_template_data(settings=None): +def get_general_template_data(settings=None, username=None): """General template data based on system settings or machine. Output contains formatting keys: @@ -14,17 +14,22 @@ def get_general_template_data(settings=None): Args: settings (Dict[str, Any]): Studio or project settings. + username (Optional[str]): AYON Username. """ if not settings: settings = get_studio_settings() + + if username is None: + username = get_ayon_username() + core_settings = settings["core"] return { "studio": { "name": core_settings["studio_name"], "code": core_settings["studio_code"] }, - "user": get_ayon_username() + "user": username } @@ -145,6 +150,7 @@ def get_template_data( task_entity=None, host_name=None, settings=None, + username=None ): """Prepare data for templates filling from entered documents and info. @@ -167,12 +173,13 @@ def get_template_data( host_name (Optional[str]): Used to fill '{app}' key. settings (Union[Dict, None]): Prepared studio or project settings. They're queried if not passed (may be slower). + username (Optional[str]): AYON Username. Returns: Dict[str, Any]: Data prepared for filling workdir template. """ - template_data = get_general_template_data(settings) + template_data = get_general_template_data(settings, username=username) template_data.update(get_project_template_data(project_entity)) if folder_entity: template_data.update(get_folder_template_data( From 8351395a8279f09d08e1ec0097bb39402882ab8f Mon Sep 17 00:00:00 2001 From: "robin@ynput.io" Date: Wed, 15 Jan 2025 18:43:02 +0100 Subject: [PATCH 033/103] Consolidate timewarp computation and add tests. --- client/ayon_core/pipeline/editorial.py | 48 +++- .../resources/img_seq_2x_time_warp.json | 181 ++++++++++++++ .../resources/img_seq_multiple_tws.json | 216 ++++++++++++++++ .../resources/img_seq_tw_beyond_range.json | 174 +++++++++++++ .../editorial/resources/qt_timewarp.json | 174 +++++++++++++ .../test_media_range_with_retimes.py | 235 ++++++++++++++++++ 6 files changed, 1015 insertions(+), 13 deletions(-) create mode 100644 tests/client/ayon_core/pipeline/editorial/resources/img_seq_2x_time_warp.json create mode 100644 tests/client/ayon_core/pipeline/editorial/resources/img_seq_multiple_tws.json create mode 100644 tests/client/ayon_core/pipeline/editorial/resources/img_seq_tw_beyond_range.json create mode 100644 tests/client/ayon_core/pipeline/editorial/resources/qt_timewarp.json diff --git a/client/ayon_core/pipeline/editorial.py b/client/ayon_core/pipeline/editorial.py index f5a876b5ba..ed1cdf9974 100644 --- a/client/ayon_core/pipeline/editorial.py +++ b/client/ayon_core/pipeline/editorial.py @@ -375,10 +375,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) @@ -403,19 +399,14 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end): 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 - ) - retimed_duration = otio.opentime.RationalTime( src_duration.value * abs(time_scalar), src_duration.rate ) trim_range = otio.opentime.TimeRange( - start_time=src_in + offset_in, - duration=retimed_duration + offset_duration + start_time=src_in, + duration=retimed_duration, + ) # preserve discrete frame numbers @@ -431,7 +422,6 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end): media_in_trimmed = conformed_source_range.start_time.value + offset_in offset_duration = conformed_source_range.duration.value * abs(time_scalar) - offset_duration += offset_out offset_duration -= 1 # duration 1 frame -> freeze frame -> end = start + 0 offset_duration = max(0, offset_duration) # negative duration = frozen frame media_out_trimmed = media_in_trimmed + offset_duration @@ -439,6 +429,38 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end): 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. + frame_range = [media_in_trimmed] + in_frame = media_in_trimmed + time_scalar + while in_frame <= media_out_trimmed: + frame_range.append(in_frame) + in_frame += time_scalar + + 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 not 0 <= new_idx < len(frame_range): + # 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)" + ) + + frame_range[idx] = frame_range[new_idx] + + # adjust range if needed + media_in_trimmed = min(media_in_trimmed, min(frame_range)) + media_out_trimmed = max(media_out_trimmed, max(frame_range)) + # adjust available handles if needed if (media_in_trimmed - media_in) < handle_start: handle_start = max(0, media_in_trimmed - media_in) diff --git a/tests/client/ayon_core/pipeline/editorial/resources/img_seq_2x_time_warp.json b/tests/client/ayon_core/pipeline/editorial/resources/img_seq_2x_time_warp.json new file mode 100644 index 0000000000..0876dcd179 --- /dev/null +++ b/tests/client/ayon_core/pipeline/editorial/resources/img_seq_2x_time_warp.json @@ -0,0 +1,181 @@ +{ + "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 + }, + { + "OTIO_SCHEMA": "TimeEffect.1", + "metadata": { + "length": 4.0, + "lookup": [ + 2.0, + 1.7039999923706057, + 1.431999991416931, + 1.2079999942779533, + 1.055999998092652, + 1.0, + 1.056000007629395, + 1.208000022888184, + 1.432000034332276, + 1.7040000305175766, + 2.0 + ] + }, + "name": "TimeWarp6", + "effect_name": "TimeWarp" + } + ], + "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_img_seq_tw_speed/sh010\", \"task\": null, \"clip_index\": \"699C12C3-07B7-E74E-A8BC-07554560B91E\", \"hierarchy\": \"shots/hiero_img_seq_tw_speed\", \"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"hiero_img_seq_tw_speed\", \"track\": \"Video_1\", \"shot\": \"sh010\", \"reviewableSource\": null, \"sourceResolution\": false, \"workfileFrameStart\": 1001, \"handleStart\": 0, \"handleEnd\": 10, \"parents\": [{\"entity_type\": \"folder\", \"folder_type\": \"folder\", \"entity_name\": \"shots\"}, {\"entity_type\": \"sequence\", \"folder_type\": \"sequence\", \"entity_name\": \"hiero_img_seq_tw_speed\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"hiero_img_seq_tw_speed\", \"track\": \"Video_1\"}, \"heroTrack\": true, \"uuid\": \"731977d8-6f06-415d-9086-b04b58a16ce3\", \"reviewTrack\": null, \"folderName\": \"sh010\", \"label\": \"/shots/hiero_img_seq_tw_speed/sh010 shotMain\", \"newHierarchyIntegration\": true, \"instance_id\": \"d157ce1c-3157-4a34-a8b5-14c881387239\", \"creator_attributes\": {\"workfileFrameStart\": 1001, \"handleStart\": 0, \"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_img_seq_tw_speed/sh010\", \"task\": null, \"clip_index\": \"699C12C3-07B7-E74E-A8BC-07554560B91E\", \"hierarchy\": \"shots/hiero_img_seq_tw_speed\", \"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"hiero_img_seq_tw_speed\", \"track\": \"Video_1\", \"shot\": \"sh010\", \"reviewableSource\": null, \"sourceResolution\": false, \"workfileFrameStart\": 1001, \"handleStart\": 0, \"handleEnd\": 10, \"parents\": [{\"entity_type\": \"folder\", \"folder_type\": \"folder\", \"entity_name\": \"shots\"}, {\"entity_type\": \"sequence\", \"folder_type\": \"sequence\", \"entity_name\": \"hiero_img_seq_tw_speed\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"hiero_img_seq_tw_speed\", \"track\": \"Video_1\"}, \"heroTrack\": true, \"uuid\": \"731977d8-6f06-415d-9086-b04b58a16ce3\", \"reviewTrack\": null, \"folderName\": \"sh010\", \"parent_instance_id\": \"d157ce1c-3157-4a34-a8b5-14c881387239\", \"label\": \"/shots/hiero_img_seq_tw_speed/sh010 plateVideo_1\", \"newHierarchyIntegration\": true, \"instance_id\": \"daf5d8e4-5698-4a41-90eb-05eea2992dff\", \"creator_attributes\": {\"parentInstance\": \"/shots/hiero_img_seq_tw_speed/sh010 shotMain\", \"review\": false, \"reviewableSource\": \"clip_media\"}, \"publish_attributes\": {\"CollectSlackFamilies\": {\"additional_message\": \"\"}}}}, \"clip_index\": \"699C12C3-07B7-E74E-A8BC-07554560B91E\"}", + "label": "AYONdata_9f37cdbf", + "note": "AYON data container" + }, + "name": "AYONdata_9f37cdbf", + "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" +} \ No newline at end of file diff --git a/tests/client/ayon_core/pipeline/editorial/resources/img_seq_multiple_tws.json b/tests/client/ayon_core/pipeline/editorial/resources/img_seq_multiple_tws.json new file mode 100644 index 0000000000..88f2dbc86c --- /dev/null +++ b/tests/client/ayon_core/pipeline/editorial/resources/img_seq_multiple_tws.json @@ -0,0 +1,216 @@ +{ + "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": "TimeEffect.1", + "metadata": { + "length": 1.0, + "lookup": [ + -5.0, + -3.9440000305175777, + -2.852000034332275, + -1.6880000228881844, + -0.4160000076293944, + 1.0, + 2.5839999923706056, + 4.311999977111817, + 6.147999965667726, + 8.055999969482421, + 10.0 + ] + }, + "name": "TimeWarp3", + "effect_name": "TimeWarp" + }, + { + "OTIO_SCHEMA": "TimeEffect.1", + "metadata": { + "length": 1.0, + "lookup": [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + }, + "name": "TimeWarp4", + "effect_name": "TimeWarp" + }, + { + "OTIO_SCHEMA": "TimeEffect.1", + "metadata": { + "length": 1.0, + "lookup": [ + 0.0, + -1.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -1.0 + ] + }, + "name": "TimeWarp5", + "effect_name": "TimeWarp" + } + ], + "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_seq_max_tw/sh010\", \"task\": null, \"clip_index\": \"4C055A68-8354-474A-A6F8-B0CBF9A537CD\", \"hierarchy\": \"shots/hiero_seq_max_tw\", \"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"hiero_seq_max_tw\", \"track\": \"Video_1\", \"shot\": \"sh010\", \"reviewableSource\": null, \"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_seq_max_tw\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"hiero_seq_max_tw\", \"track\": \"Video_1\"}, \"heroTrack\": true, \"uuid\": \"5e82a346-17c4-4ccb-a795-35e1a809b243\", \"reviewTrack\": null, \"folderName\": \"sh010\", \"label\": \"/shots/hiero_seq_max_tw/sh010 shotMain\", \"newHierarchyIntegration\": true, \"instance_id\": \"9cb2a119-8aa6-487e-a46b-9b9ff25323be\", \"creator_attributes\": {\"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"frameStart\": 1001, \"frameEnd\": 1012, \"clipIn\": 0, \"clipOut\": 10, \"clipDuration\": 11, \"sourceIn\": 176.0, \"sourceOut\": 186.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_seq_max_tw/sh010\", \"task\": null, \"clip_index\": \"4C055A68-8354-474A-A6F8-B0CBF9A537CD\", \"hierarchy\": \"shots/hiero_seq_max_tw\", \"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"hiero_seq_max_tw\", \"track\": \"Video_1\", \"shot\": \"sh010\", \"reviewableSource\": null, \"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_seq_max_tw\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"hiero_seq_max_tw\", \"track\": \"Video_1\"}, \"heroTrack\": true, \"uuid\": \"5e82a346-17c4-4ccb-a795-35e1a809b243\", \"reviewTrack\": null, \"folderName\": \"sh010\", \"parent_instance_id\": \"9cb2a119-8aa6-487e-a46b-9b9ff25323be\", \"label\": \"/shots/hiero_seq_max_tw/sh010 plateVideo_1\", \"newHierarchyIntegration\": true, \"instance_id\": \"771e41ed-74b0-4fcc-882c-6a248d45a464\", \"creator_attributes\": {\"parentInstance\": \"/shots/hiero_seq_max_tw/sh010 shotMain\", \"review\": false, \"reviewableSource\": \"clip_media\"}, \"publish_attributes\": {\"CollectSlackFamilies\": {\"additional_message\": \"\"}}}}, \"clip_index\": \"4C055A68-8354-474A-A6F8-B0CBF9A537CD\"}", + "label": "AYONdata_b6896763", + "note": "AYON data container" + }, + "name": "AYONdata_b6896763", + "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" +} \ No newline at end of file diff --git a/tests/client/ayon_core/pipeline/editorial/resources/img_seq_tw_beyond_range.json b/tests/client/ayon_core/pipeline/editorial/resources/img_seq_tw_beyond_range.json new file mode 100644 index 0000000000..1a753098d7 --- /dev/null +++ b/tests/client/ayon_core/pipeline/editorial/resources/img_seq_tw_beyond_range.json @@ -0,0 +1,174 @@ +{ + "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": "TimeEffect.1", + "metadata": { + "length": 1.0, + "lookup": [ + -5.0, + -3.9440000305175777, + -2.852000034332275, + -1.6880000228881844, + -0.4160000076293944, + 1.0, + 2.5839999923706056, + 4.311999977111817, + 6.147999965667726, + 8.055999969482421, + 10.0 + ] + }, + "name": "TimeWarp3", + "effect_name": "TimeWarp" + } + ], + "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_seq_max_tw/sh010\", \"task\": null, \"clip_index\": \"4C055A68-8354-474A-A6F8-B0CBF9A537CD\", \"hierarchy\": \"shots/hiero_seq_max_tw\", \"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"hiero_seq_max_tw\", \"track\": \"Video_1\", \"shot\": \"sh010\", \"reviewableSource\": null, \"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_seq_max_tw\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"hiero_seq_max_tw\", \"track\": \"Video_1\"}, \"heroTrack\": true, \"uuid\": \"5e82a346-17c4-4ccb-a795-35e1a809b243\", \"reviewTrack\": null, \"folderName\": \"sh010\", \"label\": \"/shots/hiero_seq_max_tw/sh010 shotMain\", \"newHierarchyIntegration\": true, \"instance_id\": \"9cb2a119-8aa6-487e-a46b-9b9ff25323be\", \"creator_attributes\": {\"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"frameStart\": 1001, \"frameEnd\": 1012, \"clipIn\": 0, \"clipOut\": 10, \"clipDuration\": 11, \"sourceIn\": 176.0, \"sourceOut\": 186.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_seq_max_tw/sh010\", \"task\": null, \"clip_index\": \"4C055A68-8354-474A-A6F8-B0CBF9A537CD\", \"hierarchy\": \"shots/hiero_seq_max_tw\", \"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"hiero_seq_max_tw\", \"track\": \"Video_1\", \"shot\": \"sh010\", \"reviewableSource\": null, \"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_seq_max_tw\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"hiero_seq_max_tw\", \"track\": \"Video_1\"}, \"heroTrack\": true, \"uuid\": \"5e82a346-17c4-4ccb-a795-35e1a809b243\", \"reviewTrack\": null, \"folderName\": \"sh010\", \"parent_instance_id\": \"9cb2a119-8aa6-487e-a46b-9b9ff25323be\", \"label\": \"/shots/hiero_seq_max_tw/sh010 plateVideo_1\", \"newHierarchyIntegration\": true, \"instance_id\": \"771e41ed-74b0-4fcc-882c-6a248d45a464\", \"creator_attributes\": {\"parentInstance\": \"/shots/hiero_seq_max_tw/sh010 shotMain\", \"review\": false, \"reviewableSource\": \"clip_media\"}, \"publish_attributes\": {\"CollectSlackFamilies\": {\"additional_message\": \"\"}}}}, \"clip_index\": \"4C055A68-8354-474A-A6F8-B0CBF9A537CD\"}", + "label": "AYONdata_b6896763", + "note": "AYON data container" + }, + "name": "AYONdata_b6896763", + "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" +} \ No newline at end of file diff --git a/tests/client/ayon_core/pipeline/editorial/resources/qt_timewarp.json b/tests/client/ayon_core/pipeline/editorial/resources/qt_timewarp.json new file mode 100644 index 0000000000..88ee7130f4 --- /dev/null +++ b/tests/client/ayon_core/pipeline/editorial/resources/qt_timewarp.json @@ -0,0 +1,174 @@ +{ + "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": "TimeEffect.1", + "metadata": { + "length": 4.0, + "lookup": [ + 2.0, + 1.8959999809265136, + 1.767999971389771, + 1.59199997138977, + 1.3439999809265135, + 1.0, + 0.5440000181198119, + -0.007999974250793684, + -0.6319999756813051, + -1.3039999847412114, + -2.0 + ] + }, + "name": "TimeWarp2", + "effect_name": "TimeWarp" + } + ], + "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_seq_tw/sh010\", \"task\": null, \"clip_index\": \"27126150-EDFA-9F45-908C-59F5CD1A94E2\", \"hierarchy\": \"shots/hiero_seq_tw\", \"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"hiero_seq_tw\", \"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_seq_tw\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"hiero_seq_tw\", \"track\": \"Video_1\"}, \"heroTrack\": true, \"uuid\": \"5c0e0d32-fa09-4331-afbb-5b194cfa258c\", \"reviewTrack\": \"Video 1\", \"review\": true, \"folderName\": \"sh010\", \"label\": \"/shots/hiero_seq_tw/sh010 shotMain\", \"newHierarchyIntegration\": true, \"instance_id\": \"b88fe40d-f92d-42b0-b7f6-7cb7a206e878\", \"creator_attributes\": {\"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"frameStart\": 1001, \"frameEnd\": 1012, \"clipIn\": 0, \"clipOut\": 10, \"clipDuration\": 11, \"sourceIn\": 176.0, \"sourceOut\": 186.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_seq_tw/sh010\", \"task\": null, \"clip_index\": \"27126150-EDFA-9F45-908C-59F5CD1A94E2\", \"hierarchy\": \"shots/hiero_seq_tw\", \"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"hiero_seq_tw\", \"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_seq_tw\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"hiero_seq_tw\", \"track\": \"Video_1\"}, \"heroTrack\": true, \"uuid\": \"5c0e0d32-fa09-4331-afbb-5b194cfa258c\", \"reviewTrack\": \"Video 1\", \"review\": true, \"folderName\": \"sh010\", \"parent_instance_id\": \"b88fe40d-f92d-42b0-b7f6-7cb7a206e878\", \"label\": \"/shots/hiero_seq_tw/sh010 plateVideo_1\", \"newHierarchyIntegration\": true, \"instance_id\": \"e3ea1467-dfaf-48db-bf3c-6cbbbd2cd972\", \"creator_attributes\": {\"parentInstance\": \"/shots/hiero_seq_tw/sh010 shotMain\", \"review\": true, \"reviewableSource\": \"Video 1\"}, \"publish_attributes\": {\"CollectSlackFamilies\": {\"additional_message\": \"\"}}}}, \"clip_index\": \"27126150-EDFA-9F45-908C-59F5CD1A94E2\"}", + "label": "AYONdata_ef8f52f1", + "note": "AYON data container" + }, + "name": "AYONdata_ef8f52f1", + "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" +} \ No newline at end of file diff --git a/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py b/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py index c045379806..45cba64558 100644 --- a/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py +++ b/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py @@ -180,6 +180,55 @@ def test_movie_frozen_frame(): ) +def test_movie_timewarp(): + """ + Movie clip (no timecode) + available files = 0-100 24fps + source_range = 29.97-40.97 23.976fps + speed = timewarp + """ + expected_data = { + 'handleEnd': 10, + 'handleStart': 0, + 'mediaIn': 948850, + 'mediaOut': 948860, + 'speed': 1.0, + 'versionData': {'handleEnd': 10, + 'handleStart': 0, + 'retime': True, + 'speed': 1.0, + 'timewarps': [ + { + 'Class': 'TimeWarp', + 'length': 4.0, + 'lookup': [ + 2.0, + 1.8959999809265136, + 1.767999971389771, + 1.59199997138977, + 1.3439999809265135, + 1.0, + 0.5440000181198119, + -0.007999974250793684, + -0.6319999756813051, + -1.3039999847412114, + -2.0 + ], + 'name': 'TimeWarp2' + } + ] + } + } + + _check_expected_retimed_values( + "qt_timewarp.json", + expected_data, + handle_start=0, + handle_end=10, + ) + + + def test_img_sequence_no_handles(): """ Img sequence clip (no embedded timecode) @@ -449,3 +498,189 @@ def test_img_sequence_frozen_frame(): handle_start=10, handle_end=10, ) + + +def test_img_sequence_timewarp_beyond_range(): + """ + Img sequence clip + available files = 948674-948974 25fps + source_range = 909990.8339241028 + - 909995.8339241028 23.976fps + timewarp to get from 948845 to 948870 + """ + expected_data = { + 'mediaIn': 948845, + 'mediaOut': 948870, + 'handleStart': 0, + 'handleEnd': 10, + 'speed': 1.0, + 'versionData': {'handleEnd': 10, + 'handleStart': 0, + 'retime': True, + 'speed': 1.0, + 'timewarps': [ + { + 'Class': 'TimeWarp', + 'length': 1.0, + 'lookup': [ + -5.0, + -3.9440000305175777, + -2.852000034332275, + -1.6880000228881844, + -0.4160000076293944, + 1.0, + 2.5839999923706056, + 4.311999977111817, + 6.147999965667726, + 8.055999969482421, + 10.0 + ], + 'name': 'TimeWarp3' + } + ] + } + } + + _check_expected_retimed_values( + "img_seq_tw_beyond_range.json", + expected_data, + handle_start=0, + handle_end=10, + ) + + +def test_img_sequence_2X_speed_timewarp(): + """ + Img sequence clip + available files = 948674-948974 25fps + source_range = 909990.8339241028 + - 909995.8339241028 23.976fps + speed: 200% + timewarp to get from 948854 to 948874 + """ + expected_data = { + 'mediaIn': 948850, + 'mediaOut': 948874, + 'handleStart': 0, + 'handleEnd': 20, + 'speed': 2.0, + 'versionData': { + 'handleEnd': 20, + 'handleStart': 0, + 'retime': True, + 'speed': 2.0, + 'timewarps': [ + { + 'Class': 'TimeWarp', + 'length': 4.0, + 'lookup': [ + 2.0, + 1.7039999923706055, + 1.431999991416931, + 1.2079999942779531, + 1.055999998092652, + 1.0, + 1.056000007629395, + 1.208000022888184, + 1.432000034332276, + 1.7040000305175766, + 2.0 + ], + 'name': 'TimeWarp6' + } + ] + } + } + + _check_expected_retimed_values( + "img_seq_2x_time_warp.json", + expected_data, + handle_start=0, + handle_end=10, + ) + + +def test_img_sequence_multiple_timewarps(): + """ + Img sequence clip + available files = 948674-948974 25fps + source_range = 909990.8339241028 + - 909995.8339241028 23.976fps + multiple timewarps to get from 948842 to 948864 + """ + expected_data = { + 'mediaIn': 948845, + 'mediaOut': 948867, + 'handleStart': 0, + 'handleEnd': 10, + 'speed': 1.0, + 'versionData': { + 'handleEnd': 10, + 'handleStart': 0, + 'retime': True, + 'speed': 1.0, + 'timewarps': [ + { + 'Class': 'TimeWarp', + 'length': 1.0, + 'lookup': [ + -5.0, + -3.9440000305175777, + -2.852000034332275, + -1.6880000228881844, + -0.4160000076293944, + 1.0, + 2.5839999923706056, + 4.311999977111817, + 6.147999965667726, + 8.055999969482421, + 10.0 + ], + 'name': 'TimeWarp3' + }, + { + 'Class': 'TimeWarp', + 'length': 1.0, + 'lookup': [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + 'name': 'TimeWarp4' + }, + { + 'Class': 'TimeWarp', + 'length': 1.0, + 'lookup': [ + 0.0, + -1.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -1.0 + ], + 'name': 'TimeWarp5' + } + ] + } + } + + _check_expected_retimed_values( + "img_seq_multiple_tws.json", + expected_data, + handle_start=0, + handle_end=10, + ) From 199ed55357fedcb81e84a32c4bc67ecf64478778 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 16 Jan 2025 15:48:02 +0800 Subject: [PATCH 034/103] add substance designer into OCIO and last workfile pre-launch hook --- client/ayon_core/hooks/pre_add_last_workfile_arg.py | 1 + client/ayon_core/hooks/pre_ocio_hook.py | 1 + client/ayon_core/pipeline/farm/pyblish_functions.py | 2 ++ 3 files changed, 4 insertions(+) diff --git a/client/ayon_core/hooks/pre_add_last_workfile_arg.py b/client/ayon_core/hooks/pre_add_last_workfile_arg.py index d5914c2352..daea8c5502 100644 --- a/client/ayon_core/hooks/pre_add_last_workfile_arg.py +++ b/client/ayon_core/hooks/pre_add_last_workfile_arg.py @@ -26,6 +26,7 @@ class AddLastWorkfileToLaunchArgs(PreLaunchHook): "photoshop", "tvpaint", "substancepainter", + "substancedesigner", "aftereffects", "wrap", "openrv", diff --git a/client/ayon_core/hooks/pre_ocio_hook.py b/client/ayon_core/hooks/pre_ocio_hook.py index 7406aa42cf..78fc8c78de 100644 --- a/client/ayon_core/hooks/pre_ocio_hook.py +++ b/client/ayon_core/hooks/pre_ocio_hook.py @@ -10,6 +10,7 @@ class OCIOEnvHook(PreLaunchHook): order = 0 hosts = { "substancepainter", + "substancedesigner", "fusion", "blender", "aftereffects", diff --git a/client/ayon_core/pipeline/farm/pyblish_functions.py b/client/ayon_core/pipeline/farm/pyblish_functions.py index e48d99602e..16174a47a9 100644 --- a/client/ayon_core/pipeline/farm/pyblish_functions.py +++ b/client/ayon_core/pipeline/farm/pyblish_functions.py @@ -935,7 +935,9 @@ def _collect_expected_files_for_aov(files): ValueError: If there are multiple collections. """ + print(f"files: {files}") cols, rem = clique.assemble(files) + print(cols) # we shouldn't have any reminders. And if we do, it should # be just one item for single frame renders. if not cols and rem: From 03790a5f0c158f531315a806a2bf5a4dedbad0c1 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 16 Jan 2025 23:39:26 +0100 Subject: [PATCH 035/103] Fix too big chunky icons in BorisFX Silhouette - This change was also tested in Fusion (where Qt runs completely separately and this addition didn't result in a visual difference there) --- client/ayon_core/style/style.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/ayon_core/style/style.css b/client/ayon_core/style/style.css index bd96a3aeed..fa26605354 100644 --- a/client/ayon_core/style/style.css +++ b/client/ayon_core/style/style.css @@ -23,6 +23,9 @@ Enabled vs Disabled logic in most of stylesheets font-family: "Noto Sans"; font-weight: 450; outline: none; + + /* Fix icons in BorisFX Silhouette */ + icon-size: 16px; } QWidget { From 43f8764b7fb7d2481927f4bc55e91122765f28e9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 17 Jan 2025 00:01:54 +0100 Subject: [PATCH 036/103] Reduce margins on Workfiles tool due to nested layouts --- client/ayon_core/tools/workfiles/widgets/window.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_core/tools/workfiles/widgets/window.py b/client/ayon_core/tools/workfiles/widgets/window.py index 8bcff66f50..1649a059cb 100644 --- a/client/ayon_core/tools/workfiles/widgets/window.py +++ b/client/ayon_core/tools/workfiles/widgets/window.py @@ -113,6 +113,7 @@ class WorkfilesToolWindow(QtWidgets.QWidget): main_layout = QtWidgets.QHBoxLayout(self) main_layout.addWidget(pages_widget, 1) + main_layout.setContentsMargins(0, 0, 0, 0) overlay_messages_widget = MessageOverlayObject(self) overlay_invalid_host = InvalidHostOverlay(self) From 17e20a2d0f007843f04a91873cbb7b1f11c323b2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 17 Jan 2025 00:07:14 +0100 Subject: [PATCH 037/103] Set the icon size in the stylesheet to avoid too big clunky icons in BorisFX Silhouette. The sizes appeared the same in Fusion and Maya with this added to the stylesheet (no changes there) --- client/ayon_core/style/style.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/ayon_core/style/style.css b/client/ayon_core/style/style.css index bd96a3aeed..fa26605354 100644 --- a/client/ayon_core/style/style.css +++ b/client/ayon_core/style/style.css @@ -23,6 +23,9 @@ Enabled vs Disabled logic in most of stylesheets font-family: "Noto Sans"; font-weight: 450; outline: none; + + /* Fix icons in BorisFX Silhouette */ + icon-size: 16px; } QWidget { From e0133f54b66473aa8133e2759d6d95219e552eeb Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 17 Jan 2025 16:15:21 +0800 Subject: [PATCH 038/103] remove unrelated code --- client/ayon_core/pipeline/farm/pyblish_functions.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/client/ayon_core/pipeline/farm/pyblish_functions.py b/client/ayon_core/pipeline/farm/pyblish_functions.py index 16174a47a9..e48d99602e 100644 --- a/client/ayon_core/pipeline/farm/pyblish_functions.py +++ b/client/ayon_core/pipeline/farm/pyblish_functions.py @@ -935,9 +935,7 @@ def _collect_expected_files_for_aov(files): ValueError: If there are multiple collections. """ - print(f"files: {files}") cols, rem = clique.assemble(files) - print(cols) # we shouldn't have any reminders. And if we do, it should # be just one item for single frame renders. if not cols and rem: From c718d0596f3612780de061ea3c7ce05596067a1d Mon Sep 17 00:00:00 2001 From: "robin@ynput.io" Date: Mon, 20 Jan 2025 11:25:44 +0100 Subject: [PATCH 039/103] Enforce floating retime speed for Qt. --- client/ayon_core/pipeline/editorial.py | 9 +- .../publish/collect_otio_subset_resources.py | 15 +- .../resources/qt_reverse_speed_0_7.json | 160 ++++++++++++++++++ .../test_media_range_with_retimes.py | 29 ++++ 4 files changed, 205 insertions(+), 8 deletions(-) create mode 100644 tests/client/ayon_core/pipeline/editorial/resources/qt_reverse_speed_0_7.json diff --git a/client/ayon_core/pipeline/editorial.py b/client/ayon_core/pipeline/editorial.py index ed1cdf9974..7076b31ed9 100644 --- a/client/ayon_core/pipeline/editorial.py +++ b/client/ayon_core/pipeline/editorial.py @@ -406,7 +406,6 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end): trim_range = otio.opentime.TimeRange( start_time=src_in, duration=retimed_duration, - ) # preserve discrete frame numbers @@ -479,16 +478,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": round(handle_start), + "handleEnd": round(handle_end) } } returning_dict = { "mediaIn": media_in_trimmed, "mediaOut": media_out_trimmed, - "handleStart": int(handle_start), - "handleEnd": int(handle_end), + "handleStart": round(handle_start), + "handleEnd": round(handle_end), "speed": time_scalar } diff --git a/client/ayon_core/plugins/publish/collect_otio_subset_resources.py b/client/ayon_core/plugins/publish/collect_otio_subset_resources.py index ed7e8ac4f1..bfcf5a71bb 100644 --- a/client/ayon_core/plugins/publish/collect_otio_subset_resources.py +++ b/client/ayon_core/plugins/publish/collect_otio_subset_resources.py @@ -6,6 +6,7 @@ Provides: instance -> otioReviewClips """ import os +import math import clique import pyblish.api @@ -69,9 +70,17 @@ class CollectOtioSubsetResources( 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"]) diff --git a/tests/client/ayon_core/pipeline/editorial/resources/qt_reverse_speed_0_7.json b/tests/client/ayon_core/pipeline/editorial/resources/qt_reverse_speed_0_7.json new file mode 100644 index 0000000000..3ed27bcf8b --- /dev/null +++ b/tests/client/ayon_core/pipeline/editorial/resources/qt_reverse_speed_0_7.json @@ -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": 29.970030784606934 + } + }, + "effects": [ + { + "OTIO_SCHEMA": "LinearTimeWarp.1", + "metadata": {}, + "name": "Speed", + "effect_name": "LinearTimeWarp", + "time_scalar": -0.699999988079071 + } + ], + "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_qt_neg07x/sh010\", \"task\": null, \"clip_index\": \"D812B65C-F1C7-DA48-9060-F932A50B2BB4\", \"hierarchy\": \"shots/hiero_qt_neg07x\", \"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"hiero_qt_neg07x\", \"track\": \"Video_1\", \"shot\": \"sh010\", \"reviewableSource\": \"clip_media\", \"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_qt_neg07x\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"hiero_qt_neg07x\", \"track\": \"Video_1\"}, \"heroTrack\": true, \"uuid\": \"d7c96d32-6884-452f-9f8c-2383e20ca2db\", \"reviewTrack\": \"clip_media\", \"review\": true, \"folderName\": \"sh010\", \"label\": \"/shots/hiero_qt_neg07x/sh010 shotMain\", \"newHierarchyIntegration\": true, \"instance_id\": \"dae8823d-d664-4afd-9d9d-be20647ad756\", \"creator_attributes\": {\"workfileFrameStart\": 1001, \"handleStart\": 10, \"handleEnd\": 10, \"frameStart\": 1001, \"frameEnd\": 1012, \"clipIn\": 0, \"clipOut\": 10, \"clipDuration\": 11, \"sourceOut\": 30.0, \"fps\": \"from_selection\", \"sourceIn\": 0}, \"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_qt_neg07x/sh010\", \"task\": null, \"clip_index\": \"D812B65C-F1C7-DA48-9060-F932A50B2BB4\", \"hierarchy\": \"shots/hiero_qt_neg07x\", \"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"hiero_qt_neg07x\", \"track\": \"Video_1\", \"shot\": \"sh010\", \"reviewableSource\": \"clip_media\", \"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_qt_neg07x\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"ep01\", \"sequence\": \"hiero_qt_neg07x\", \"track\": \"Video_1\"}, \"heroTrack\": true, \"uuid\": \"d7c96d32-6884-452f-9f8c-2383e20ca2db\", \"reviewTrack\": \"clip_media\", \"review\": true, \"folderName\": \"sh010\", \"parent_instance_id\": \"dae8823d-d664-4afd-9d9d-be20647ad756\", \"label\": \"/shots/hiero_qt_neg07x/sh010 plateVideo_1\", \"newHierarchyIntegration\": true, \"instance_id\": \"a1aa49c0-49a1-4499-a3ec-1ac35982d92b\", \"creator_attributes\": {\"parentInstance\": \"/shots/hiero_qt_neg07x/sh010 shotMain\", \"review\": true, \"reviewableSource\": \"clip_media\"}, \"publish_attributes\": {\"CollectSlackFamilies\": {\"additional_message\": \"\"}}}}, \"clip_index\": \"D812B65C-F1C7-DA48-9060-F932A50B2BB4\"}", + "label": "AYONdata_26480dbf", + "note": "AYON data container" + }, + "name": "AYONdata_26480dbf", + "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": "ExternalReference.1", + "metadata": { + "ayon.source.colorspace": "Gamma2.2", + "ayon.source.height": 1080, + "ayon.source.pixelAspect": 1.0, + "ayon.source.width": 1920, + "clip.properties.blendfunc": "0", + "clip.properties.colourspacename": "error", + "clip.properties.domainroot": "", + "clip.properties.enabled": "1", + "clip.properties.expanded": "1", + "clip.properties.opacity": "1", + "clip.properties.valuesource": "", + "clip.properties.ycbcrmatrix": "0", + "com.apple.quicktime.codec": "H.264", + "foundry.source.audio": "", + "foundry.source.bitmapsize": "0", + "foundry.source.bitsperchannel": "0", + "foundry.source.channelformat": "integer", + "foundry.source.colourtransform": "Gamma2.2", + "foundry.source.duration": "101", + "foundry.source.filename": "qt_no_tc_24fps.mov", + "foundry.source.filesize": "", + "foundry.source.fragments": "1", + "foundry.source.framerate": "24", + "foundry.source.fullpath": "", + "foundry.source.height": "1080", + "foundry.source.layers": "colour", + "foundry.source.path": "C:/Users/robin/OneDrive/Bureau/dev_ayon/data/qt_no_tc_24fps.mov", + "foundry.source.pixelAspect": "1", + "foundry.source.pixelAspectRatio": "", + "foundry.source.pixelformat": "RGBA (Float32) Open Color IO space: 114", + "foundry.source.reelID": "", + "foundry.source.resolution": "", + "foundry.source.samplerate": "Invalid", + "foundry.source.shoottime": "4294967295", + "foundry.source.shortfilename": "qt_no_tc_24fps.mov", + "foundry.source.shot": "", + "foundry.source.shotDate": "", + "foundry.source.startTC": "", + "foundry.source.starttime": "0", + "foundry.source.timecode": "0", + "foundry.source.type": "QuickTime H.264", + "foundry.source.umid": "16634e88-6450-4727-6c6e-501f4b31b637", + "foundry.source.umidOriginator": "foundry.source.umid", + "foundry.source.width": "1920", + "foundry.timeline.autodiskcachemode": "Manual", + "foundry.timeline.colorSpace": "Gamma2.2", + "foundry.timeline.duration": "101", + "foundry.timeline.framerate": "24", + "foundry.timeline.outputformat": "", + "foundry.timeline.poster": "0", + "foundry.timeline.posterLayer": "colour", + "foundry.timeline.samplerate": "Invalid", + "media.input.bitsperchannel": "8-bit fixed", + "media.input.ctime": "2024-09-25 17:16:12", + "media.input.filename": "C:/Users/robin/OneDrive/Bureau/dev_ayon/data/qt_no_tc_24fps.mov", + "media.input.filereader": "mov64", + "media.input.filesize": "14631252", + "media.input.frame": "1", + "media.input.frame_rate": "24", + "media.input.height": "1080", + "media.input.mtime": "2024-09-25 17:16:16", + "media.input.pixel_aspect": "1", + "media.input.timecode": "00:00:00:00", + "media.input.width": "1920", + "media.quicktime.codec_id": "avc1", + "media.quicktime.codec_name": "h264", + "media.quicktime.encoder": "H.264", + "media.quicktime.nclc_matrix": "BT709", + "media.quicktime.nclc_primaries": "ITU-R BT.709", + "media.quicktime.nclc_transfer_function": "ITU-R BT.709", + "media.quicktime.thefoundry.Application": "Nuke", + "media.quicktime.thefoundry.ApplicationVersion": "15.0v5", + "media.quicktime.thefoundry.Colorspace": "Gamma2.2", + "media.quicktime.thefoundry.Writer": "mov64", + "media.quicktime.thefoundry.YCbCrMatrix": "Rec 709", + "media.stream.pixel_format": "yuv420p", + "uk.co.thefoundry.Application": "Nuke", + "uk.co.thefoundry.ApplicationVersion": "15.0v5", + "uk.co.thefoundry.Colorspace": "Gamma2.2", + "uk.co.thefoundry.Writer": "mov64", + "uk.co.thefoundry.YCbCrMatrix": "Rec 709" + }, + "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": 0.0 + } + }, + "available_image_bounds": null, + "target_url": "C:/Users/robin/OneDrive/Bureau/dev_ayon/data/qt_no_tc_24fps.mov" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" +} \ No newline at end of file diff --git a/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py b/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py index 45cba64558..9ff2a7fdb2 100644 --- a/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py +++ b/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py @@ -146,6 +146,35 @@ def test_movie_reverse_speed_2x(): ) +def test_movie_reverse_speed_0_7x(): + """ + Movie clip (no timecode) + available files = 0-100 24fps + source_range = 29.97-40.97 23.976fps + speed = -0.7 + """ + expected_data = { + 'handleEnd': 7, + 'handleStart': 7, + 'mediaIn': 30.000000000000004, + 'mediaOut': 36.70769965924555, + 'speed': -0.699999988079071, + 'versionData': { + 'handleEnd': 7, + 'handleStart': 7, + 'retime': True, + 'speed': -0.699999988079071, + 'timewarps': [] + } + } + + _check_expected_retimed_values( + "qt_reverse_speed_0_7.json", + expected_data, + handle_start=10, + handle_end=10, + ) + def test_movie_frozen_frame(): """ From c166bf0514952bcef3e722d68f4c67cac47c0a92 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 20 Jan 2025 21:55:34 +0800 Subject: [PATCH 040/103] add sbsar as the families --- client/ayon_core/plugins/publish/collect_resources_path.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/collect_resources_path.py b/client/ayon_core/plugins/publish/collect_resources_path.py index 7a80d0054c..2e5b296228 100644 --- a/client/ayon_core/plugins/publish/collect_resources_path.py +++ b/client/ayon_core/plugins/publish/collect_resources_path.py @@ -66,7 +66,8 @@ class CollectResourcesPath(pyblish.api.InstancePlugin): "yeticacheUE", "tycache", "usd", - "oxrig" + "oxrig", + "sbsar", ] def process(self, instance): From 81bb74c7ea0e0c46a17f8bc16e54f0044fb81066 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 20 Jan 2025 18:07:18 +0100 Subject: [PATCH 041/103] Apply suggestions from code review Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/hooks/pre_add_last_workfile_arg.py | 2 +- client/ayon_core/hooks/pre_ocio_hook.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hooks/pre_add_last_workfile_arg.py b/client/ayon_core/hooks/pre_add_last_workfile_arg.py index a931fb0cbe..5b10357632 100644 --- a/client/ayon_core/hooks/pre_add_last_workfile_arg.py +++ b/client/ayon_core/hooks/pre_add_last_workfile_arg.py @@ -30,7 +30,7 @@ class AddLastWorkfileToLaunchArgs(PreLaunchHook): "wrap", "openrv", "cinema4d", - "silhouette" + "silhouette", } launch_types = {LaunchTypes.local} diff --git a/client/ayon_core/hooks/pre_ocio_hook.py b/client/ayon_core/hooks/pre_ocio_hook.py index dd81cf053e..8765dbd0b2 100644 --- a/client/ayon_core/hooks/pre_ocio_hook.py +++ b/client/ayon_core/hooks/pre_ocio_hook.py @@ -21,7 +21,7 @@ class OCIOEnvHook(PreLaunchHook): "resolve", "openrv", "cinema4d", - "silhouette" + "silhouette", } launch_types = set() From 827651a4a669fefe8823db7095315d5ebb96599d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 21 Jan 2025 12:20:50 +0100 Subject: [PATCH 042/103] Update client/ayon_core/style/style.css Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/style/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/style/style.css b/client/ayon_core/style/style.css index fa26605354..a5e54453cc 100644 --- a/client/ayon_core/style/style.css +++ b/client/ayon_core/style/style.css @@ -24,7 +24,7 @@ Enabled vs Disabled logic in most of stylesheets font-weight: 450; outline: none; - /* Fix icons in BorisFX Silhouette */ + /* Define icon size to fix size issues for most of DCCs */ icon-size: 16px; } From 41045c1092fce14032a09a2a4dd4608eddd8a71f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 21 Jan 2025 12:52:02 +0100 Subject: [PATCH 043/103] Add missing argument in docstring --- client/ayon_core/pipeline/staging_dir.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_core/pipeline/staging_dir.py b/client/ayon_core/pipeline/staging_dir.py index 2004096bd0..1cb2979415 100644 --- a/client/ayon_core/pipeline/staging_dir.py +++ b/client/ayon_core/pipeline/staging_dir.py @@ -158,6 +158,7 @@ def get_staging_dir_info( logger (Optional[logging.Logger]): Logger instance. prefix (Optional[str]) Optional prefix for staging dir name. suffix (Optional[str]): Optional suffix for staging dir name. + username (Optional[str]): AYON Username. Returns: Optional[StagingDir]: Staging dir info data From 2712260d08c9d8d777dfea9dbe7cfc4ec735f644 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Tue, 21 Jan 2025 14:49:22 +0000 Subject: [PATCH 044/103] [Automated] Add generated package files from main --- client/ayon_core/version.py | 2 +- package.py | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/version.py b/client/ayon_core/version.py index b0ada09e7c..e90676d739 100644 --- a/client/ayon_core/version.py +++ b/client/ayon_core/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring AYON addon 'core' version.""" -__version__ = "1.0.13+dev" +__version__ = "1.0.14" diff --git a/package.py b/package.py index 03b69d4c5c..bb38b431b1 100644 --- a/package.py +++ b/package.py @@ -1,6 +1,6 @@ name = "core" title = "Core" -version = "1.0.13+dev" +version = "1.0.14" client_dir = "ayon_core" diff --git a/pyproject.toml b/pyproject.toml index 5e42aa7093..2496e3fa34 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ [tool.poetry] name = "ayon-core" -version = "1.0.13+dev" +version = "1.0.14" description = "" authors = ["Ynput Team "] readme = "README.md" From dec3dd2178130a0f197aeb4147c6eb1e290f84fb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 21 Jan 2025 14:50:15 +0000 Subject: [PATCH 045/103] chore(): update bug report / version --- .github/ISSUE_TEMPLATE/bug_report.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 54f5d68b98..c0ab04abef 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,20 @@ body: label: Version description: What version are you running? Look to AYON Tray options: + - 1.0.14 + - 1.0.13 + - 1.0.12 + - 1.0.11 + - 1.0.10 + - 1.0.9 + - 1.0.8 + - 1.0.7 + - 1.0.6 + - 1.0.5 + - 1.0.4 + - 1.0.3 + - 1.0.2 + - 1.0.1 - 1.0.0 - 0.4.4 - 0.4.3 From cdf7b743e8e8f38a3ccdf22ab0ec3ecb126e835d Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 21 Jan 2025 15:56:12 +0100 Subject: [PATCH 046/103] bump version to '1.0.15-dev' --- client/ayon_core/version.py | 2 +- package.py | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/version.py b/client/ayon_core/version.py index e90676d739..2775cb606a 100644 --- a/client/ayon_core/version.py +++ b/client/ayon_core/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring AYON addon 'core' version.""" -__version__ = "1.0.14" +__version__ = "1.0.15-dev" diff --git a/package.py b/package.py index bb38b431b1..af3342f3f2 100644 --- a/package.py +++ b/package.py @@ -1,6 +1,6 @@ name = "core" title = "Core" -version = "1.0.14" +version = "1.0.15-dev" client_dir = "ayon_core" diff --git a/pyproject.toml b/pyproject.toml index 2496e3fa34..e040ce986f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ [tool.poetry] name = "ayon-core" -version = "1.0.14" +version = "1.0.15-dev" description = "" authors = ["Ynput Team "] readme = "README.md" From bb2d38602e8abb9b83f923355145d9b7534a6cc8 Mon Sep 17 00:00:00 2001 From: "robin@ynput.io" Date: Tue, 21 Jan 2025 21:03:41 +0100 Subject: [PATCH 047/103] Restrict source range to available range. --- client/ayon_core/pipeline/editorial.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/ayon_core/pipeline/editorial.py b/client/ayon_core/pipeline/editorial.py index 7076b31ed9..933d1f1758 100644 --- a/client/ayon_core/pipeline/editorial.py +++ b/client/ayon_core/pipeline/editorial.py @@ -458,7 +458,10 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end): # adjust range if needed media_in_trimmed = min(media_in_trimmed, min(frame_range)) + media_in_trimmed = max(media_in_trimmed, media_in) + media_out_trimmed = max(media_out_trimmed, max(frame_range)) + media_out_trimmed = min(media_out_trimmed, media_out) # adjust available handles if needed if (media_in_trimmed - media_in) < handle_start: From e44901e53e4219961ef082df7c1dfa6986e84b29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 22 Jan 2025 11:00:06 +0100 Subject: [PATCH 048/103] :sparkles: add SourceApplication trait --- client/ayon_core/pipeline/traits/README.md | 1 + client/ayon_core/pipeline/traits/meta.py | 20 ++++++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/pipeline/traits/README.md b/client/ayon_core/pipeline/traits/README.md index f9d7be5de1..447fb427c6 100644 --- a/client/ayon_core/pipeline/traits/README.md +++ b/client/ayon_core/pipeline/traits/README.md @@ -165,6 +165,7 @@ to different packages based on their use: | | Variant | Used to differentiate between data variants of the same output (mp4 as h.264 and h.265 for example) | | KeepOriginalLocation | Marks the representation to keep the original location of the file | | KeepOriginalName | Marks the representation to keep the original name of the file +| | SourceApplication | Holds information about producing application, about it's version, variant and platform. | three dimensional | Spatial | Spatial information like up-axis, units and handedness. | | Geometry | Type trait to mark the representation as a geometry. | | Shader | Type trait to mark the representation as a Shader. diff --git a/client/ayon_core/pipeline/traits/meta.py b/client/ayon_core/pipeline/traits/meta.py index 5cf5f54c36..2dc8ea5a27 100644 --- a/client/ayon_core/pipeline/traits/meta.py +++ b/client/ayon_core/pipeline/traits/meta.py @@ -78,7 +78,7 @@ class Variant(TraitBase): class KeepOriginalLocation(TraitBase): """Keep files in its original location. - + Note: This is not a persistent trait. @@ -86,11 +86,11 @@ class KeepOriginalLocation(TraitBase): name: ClassVar[str] = "KeepOriginalLocation" description: ClassVar[str] = "Keep Original Location Trait Model" id: ClassVar[str] = "ayon.meta.KeepOriginalLocation.v1" - persistent: bool = Field(False, title="Persistent") + persistent: bool = Field(default=False, title="Persistent") class KeepOriginalName(TraitBase): """Keep files in its original name. - + Note: This is not a persistent trait. @@ -98,4 +98,16 @@ class KeepOriginalName(TraitBase): name: ClassVar[str] = "KeepOriginalName" description: ClassVar[str] = "Keep Original Name Trait Model" id: ClassVar[str] = "ayon.meta.KeepOriginalName.v1" - persistent: bool = Field(False, title="Persistent") + persistent: bool = Field(default=False, title="Persistent") + + +class SourceApplication(TraitBase): + """Metadata about the source (producing) application.""" + + name: ClassVar[str] = "SourceApplication" + description: ClassVar[str] = "Source Application Trait Model" + id: ClassVar[str] = "ayon.meta.SourceApplication.v1" + application: str = Field(..., title="Application Name") + variant: str = Field(..., title="Application Variant (e.g. Pro)") + version: str = Field(..., title="Application Version") + platform: str = Field(..., title="Platform Name") From 5f0aec7bbf1415b70a8729566f770187b873c897 Mon Sep 17 00:00:00 2001 From: "robin@ynput.io" Date: Wed, 22 Jan 2025 14:23:27 +0100 Subject: [PATCH 049/103] Rework tw computation. --- client/ayon_core/pipeline/editorial.py | 15 ++++++--------- .../editorial/test_media_range_with_retimes.py | 7 ++++--- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/client/ayon_core/pipeline/editorial.py b/client/ayon_core/pipeline/editorial.py index 933d1f1758..02986753b7 100644 --- a/client/ayon_core/pipeline/editorial.py +++ b/client/ayon_core/pipeline/editorial.py @@ -431,11 +431,11 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end): if time_warp_nodes: # Naive approach: Resolve consecutive timewarp(s) on range, # then check if plate range has to be extended beyond source range. - frame_range = [media_in_trimmed] - in_frame = media_in_trimmed + time_scalar - while in_frame <= media_out_trimmed: - frame_range.append(in_frame) + 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): @@ -457,11 +457,8 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end): frame_range[idx] = frame_range[new_idx] # adjust range if needed - media_in_trimmed = min(media_in_trimmed, min(frame_range)) - media_in_trimmed = max(media_in_trimmed, media_in) - - media_out_trimmed = max(media_out_trimmed, max(frame_range)) - media_out_trimmed = min(media_out_trimmed, media_out) + 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: diff --git a/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py b/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py index 9ff2a7fdb2..730e6b24cd 100644 --- a/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py +++ b/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py @@ -219,8 +219,8 @@ def test_movie_timewarp(): expected_data = { 'handleEnd': 10, 'handleStart': 0, - 'mediaIn': 948850, - 'mediaOut': 948860, + 'mediaIn': 948852, + 'mediaOut': 948858, 'speed': 1.0, 'versionData': {'handleEnd': 10, 'handleStart': 0, @@ -249,6 +249,7 @@ def test_movie_timewarp(): } } + import pdb ; pdb.set_trace() _check_expected_retimed_values( "qt_timewarp.json", expected_data, @@ -588,7 +589,7 @@ def test_img_sequence_2X_speed_timewarp(): timewarp to get from 948854 to 948874 """ expected_data = { - 'mediaIn': 948850, + 'mediaIn': 948854, 'mediaOut': 948874, 'handleStart': 0, 'handleEnd': 20, From 874abdb38c633cdea1887c0435802077a9cb8bd4 Mon Sep 17 00:00:00 2001 From: "robin@ynput.io" Date: Wed, 22 Jan 2025 15:36:25 +0100 Subject: [PATCH 050/103] Remove pdb. --- .../pipeline/editorial/test_media_range_with_retimes.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py b/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py index 730e6b24cd..a1d609b56e 100644 --- a/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py +++ b/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py @@ -249,7 +249,6 @@ def test_movie_timewarp(): } } - import pdb ; pdb.set_trace() _check_expected_retimed_values( "qt_timewarp.json", expected_data, From 2569015b9fe1a5b0baf5ee44539417ff0e678b3d Mon Sep 17 00:00:00 2001 From: Liam Hoflay Date: Wed, 22 Jan 2025 16:44:45 +0000 Subject: [PATCH 051/103] changing localhost for 0.0.0.0 in client/ayon_core/tools/tray/webserver/server.py --- client/ayon_core/tools/tray/webserver/server.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/tools/tray/webserver/server.py b/client/ayon_core/tools/tray/webserver/server.py index d2a9b0fc6b..d9b3b857f5 100644 --- a/client/ayon_core/tools/tray/webserver/server.py +++ b/client/ayon_core/tools/tray/webserver/server.py @@ -28,7 +28,7 @@ def find_free_port( exclude_ports (list, tuple, set): List of ports that won't be checked form entered range. host (str): Host where will check for free ports. Set to - "localhost" by default. + "0.0.0.0" by default. """ if port_from is None: port_from = 8079 @@ -42,7 +42,7 @@ def find_free_port( # Default host is localhost but it is possible to look for other hosts if host is None: - host = "localhost" + host = "0.0.0.0" found_port = None while True: @@ -78,7 +78,7 @@ class WebServerManager: self._log = None self.port = port or 8079 - self.host = host or "localhost" + self.host = host or "0.0.0.0" self.on_stop_callbacks = [] From 66da9fa39a2f141895364031b4ad4ed7d4d02c3c Mon Sep 17 00:00:00 2001 From: "robin@ynput.io" Date: Wed, 22 Jan 2025 20:27:55 +0100 Subject: [PATCH 052/103] Add tests to handle fractional speed. --- client/ayon_core/pipeline/editorial.py | 18 +- .../resources/img_seq_reverse_speed_0_7.json | 235 ++++++++++++++++++ .../test_media_range_with_retimes.py | 29 +++ 3 files changed, 274 insertions(+), 8 deletions(-) create mode 100644 tests/client/ayon_core/pipeline/editorial/resources/img_seq_reverse_speed_0_7.json diff --git a/client/ayon_core/pipeline/editorial.py b/client/ayon_core/pipeline/editorial.py index 02986753b7..434fff0105 100644 --- a/client/ayon_core/pipeline/editorial.py +++ b/client/ayon_core/pipeline/editorial.py @@ -1,6 +1,7 @@ import os import re import clique +import math import opentimelineio as otio from opentimelineio import opentime as _ot @@ -397,12 +398,13 @@ 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 - + src_duration = math.ceil(otio_clip.source_range.duration.value * abs(time_scalar)) retimed_duration = otio.opentime.RationalTime( - src_duration.value * abs(time_scalar), - src_duration.rate + 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, duration=retimed_duration, @@ -478,16 +480,16 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end): "retime": True, "speed": time_scalar, "timewarps": time_warp_nodes, - "handleStart": round(handle_start), - "handleEnd": round(handle_end) + "handleStart": math.ceil(handle_start), + "handleEnd": math.ceil(handle_end) } } returning_dict = { "mediaIn": media_in_trimmed, "mediaOut": media_out_trimmed, - "handleStart": round(handle_start), - "handleEnd": round(handle_end), + "handleStart": math.ceil(handle_start), + "handleEnd": math.ceil(handle_end), "speed": time_scalar } diff --git a/tests/client/ayon_core/pipeline/editorial/resources/img_seq_reverse_speed_0_7.json b/tests/client/ayon_core/pipeline/editorial/resources/img_seq_reverse_speed_0_7.json new file mode 100644 index 0000000000..0939161817 --- /dev/null +++ b/tests/client/ayon_core/pipeline/editorial/resources/img_seq_reverse_speed_0_7.json @@ -0,0 +1,235 @@ +{ + "OTIO_SCHEMA": "Clip.2", + "metadata": {}, + "name": "img_seq_revsh0010", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 25.0, + "value": 41.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 25.0, + "value": 1040.0 + } + }, + "effects": [ + { + "OTIO_SCHEMA": "LinearTimeWarp.1", + "metadata": {}, + "name": "Speed", + "effect_name": "LinearTimeWarp", + "time_scalar": -0.7 + } + ], + "markers": [ + { + "OTIO_SCHEMA": "Marker.2", + "metadata": { + "clip_index": "e7fede03-d769-4827-a014-35b50170e914", + "flame_sub_products": { + "io.ayon.creators.flame.plate": { + "active": true, + "clipName": "{sequence}{shot}", + "clipRename": true, + "clipVariant": "", + "clip_index": "e7fede03-d769-4827-a014-35b50170e914", + "countFrom": 10, + "countSteps": 10, + "creator_attributes": { + "parentInstance": "/test_robin/img_seq_rev/img_seq_revsh0010 shot", + "review": false, + "reviewableSource": "clip_media" + }, + "creator_identifier": "io.ayon.creators.flame.plate", + "episode": "ep01", + "export_audio": false, + "folder": "test_robin", + "folderName": "img_seq_revsh0010", + "folderPath": "/test_robin/img_seq_rev/img_seq_revsh0010", + "handleEnd": 5, + "handleStart": 5, + "heroTrack": true, + "hierarchy": "test_robin/img_seq_rev", + "hierarchyData": { + "episode": "ep01", + "folder": "test_robin", + "sequence": "img_seq_rev", + "track": "noname1" + }, + "id": "pyblish.avalon.instance", + "includeHandles": false, + "instance_id": "a06107cd-49ad-48cb-a84f-67f53dfd58ef", + "label": "/test_robin/img_seq_rev/img_seq_revsh0010 plateNoname1", + "newAssetPublishing": true, + "newHierarchyIntegration": true, + "parent_instance_id": "896c2dad-03a6-4a18-97f5-ecf8f00a6180", + "parents": [ + { + "entity_name": "test_robin", + "folder_type": "folder" + }, + { + "entity_name": "img_seq_rev", + "folder_type": "sequence" + } + ], + "productName": "plateNoname1", + "productType": "plate", + "publish": true, + "publish_attributes": {}, + "retimedFramerange": true, + "retimedHandles": true, + "reviewTrack": null, + "reviewableSource": null, + "segmentIndex": true, + "sequence": "img_seq_rev", + "shot": "sh####", + "sourceResolution": false, + "task": null, + "track": "{_track_}", + "useShotName": false, + "use_selection": true, + "vSyncOn": false, + "vSyncTrack": "*", + "variant": "noname1", + "workfileFrameStart": 1001 + }, + "io.ayon.creators.flame.shot": { + "active": true, + "clipName": "{sequence}{shot}", + "clipRename": true, + "clipVariant": "", + "clip_index": "e7fede03-d769-4827-a014-35b50170e914", + "countFrom": 10, + "countSteps": 10, + "creator_attributes": { + "clipDuration": 41, + "clipIn": 1, + "clipOut": 41, + "fps": "from_selection", + "frameEnd": 1042, + "frameStart": 1001, + "handleEnd": 5, + "handleStart": 5, + "includeHandles": false, + "retimedFramerange": true, + "retimedHandles": true, + "sourceIn": 1068, + "sourceOut": 1040, + "workfileFrameStart": 1001 + }, + "creator_identifier": "io.ayon.creators.flame.shot", + "episode": "ep01", + "export_audio": false, + "folder": "test_robin", + "folderName": "img_seq_revsh0010", + "folderPath": "/test_robin/img_seq_rev/img_seq_revsh0010", + "handleEnd": 5, + "handleStart": 5, + "heroTrack": true, + "hierarchy": "test_robin/img_seq_rev", + "hierarchyData": { + "episode": "ep01", + "folder": "test_robin", + "sequence": "img_seq_rev", + "track": "noname1" + }, + "id": "pyblish.avalon.instance", + "includeHandles": false, + "instance_id": "896c2dad-03a6-4a18-97f5-ecf8f00a6180", + "label": "/test_robin/img_seq_rev/img_seq_revsh0010 shot", + "newAssetPublishing": true, + "newHierarchyIntegration": true, + "parents": [ + { + "entity_name": "test_robin", + "folder_type": "folder" + }, + { + "entity_name": "img_seq_rev", + "folder_type": "sequence" + } + ], + "productName": "shotMain", + "productType": "shot", + "publish": true, + "publish_attributes": {}, + "retimedFramerange": true, + "retimedHandles": true, + "reviewTrack": null, + "reviewableSource": null, + "segmentIndex": true, + "sequence": "img_seq_rev", + "shot": "sh####", + "sourceResolution": false, + "task": null, + "track": "{_track_}", + "useShotName": false, + "use_selection": true, + "vSyncOn": false, + "vSyncTrack": "*", + "variant": "main", + "workfileFrameStart": 1001 + } + }, + "publish": true + }, + "name": "AYONData", + "color": "CYAN", + "marked_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 25.0, + "value": 1.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 25.0, + "value": 22.0 + } + }, + "comment": "" + } + ], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ImageSequenceReference.1", + "metadata": { + "ayon.source.height": 1080, + "ayon.source.pixelAspect": 1.0, + "ayon.source.width": 1920, + "isSequence": true, + "padding": 4 + }, + "name": "", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 25.0, + "value": 101.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 25.0, + "value": 1000.0 + } + }, + "available_image_bounds": null, + "target_url_base": "/home/ynput/CODE/testing_flame/test_data/sample_media_robin/Samples media/img_sequence/tif/", + "name_prefix": "output.", + "name_suffix": ".tif", + "start_frame": 1000, + "frame_step": 1, + "rate": 25.0, + "frame_zero_padding": 4, + "missing_frame_policy": "error" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" +} \ No newline at end of file diff --git a/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py b/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py index a1d609b56e..ab67d49e22 100644 --- a/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py +++ b/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py @@ -438,6 +438,35 @@ def test_img_sequence_reverse_speed_from_24_to_23_976fps(): ) +def test_img_sequence_reverse_speed_0_7(): + """ + Img sequence clip + available files = 1000-1100 24fps + source_range = 1040-1081 25fps + """ + expected_data = { + 'mediaIn': 1040, + 'mediaOut': 1068, + 'handleStart': 4, + 'handleEnd': 4, + 'speed': -0.7, + 'versionData': { + 'retime': True, + 'speed': -0.7, + 'timewarps': [], + 'handleStart': 4, + 'handleEnd': 4 + } + } + + _check_expected_retimed_values( + "img_seq_reverse_speed_0_7.json", + expected_data, + handle_start=5, + handle_end=5, + ) + + def test_img_sequence_2x_speed(): """ Img sequence clip From 133bb32c8154234761b6e798528d41a121f275ea Mon Sep 17 00:00:00 2001 From: "robin@ynput.io" Date: Wed, 22 Jan 2025 20:52:15 +0100 Subject: [PATCH 053/103] Fix lint. --- client/ayon_core/pipeline/editorial.py | 30 ++++++++++++++----- .../test_media_range_with_retimes.py | 10 +++---- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/client/ayon_core/pipeline/editorial.py b/client/ayon_core/pipeline/editorial.py index 434fff0105..2ecc786581 100644 --- a/client/ayon_core/pipeline/editorial.py +++ b/client/ayon_core/pipeline/editorial.py @@ -398,7 +398,10 @@ 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 = math.ceil(otio_clip.source_range.duration.value * abs(time_scalar)) + 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 @@ -422,9 +425,15 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end): # compute retimed range media_in_trimmed = conformed_source_range.start_time.value + offset_in - offset_duration = conformed_source_range.duration.value * abs(time_scalar) - offset_duration -= 1 # duration 1 frame -> freeze frame -> end = start + 0 - offset_duration = max(0, offset_duration) # negative duration = frozen frame + offset_duration = ( + conformed_source_range.duration.value + * abs(time_scalar) + ) + + # duration 1 frame -> freeze frame -> end = start + 0 + offset_duration -= 1 + # negative duration = frozen frame + offset_duration = max(0, offset_duration) media_out_trimmed = media_in_trimmed + offset_duration media_in = available_range.start_time.value @@ -443,17 +452,22 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end): 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) + 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 not 0 <= new_idx < len(frame_range): # 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. + # 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)" + "Unsupported consecutive timewarps " + "(out of computed range)" ) frame_range[idx] = frame_range[new_idx] diff --git a/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py b/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py index ab67d49e22..fbab60623f 100644 --- a/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py +++ b/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py @@ -501,7 +501,7 @@ def test_img_sequence_2x_speed_resolve(): """ Img sequence clip available files = 0-99 24fps - source_range = 38-49 24fps + source_range = 38-49 24fps speed = 2.0 """ expected_data = { @@ -531,7 +531,7 @@ def test_img_sequence_frozen_frame(): """ Img sequence clip available files = 948674-948974 25fps - source_range = 909990.8339241028 + source_range = 909990.8339241028 - 909995.8339241028 23.976fps speed = 0.0 """ @@ -562,7 +562,7 @@ def test_img_sequence_timewarp_beyond_range(): """ Img sequence clip available files = 948674-948974 25fps - source_range = 909990.8339241028 + source_range = 909990.8339241028 - 909995.8339241028 23.976fps timewarp to get from 948845 to 948870 """ @@ -611,7 +611,7 @@ def test_img_sequence_2X_speed_timewarp(): """ Img sequence clip available files = 948674-948974 25fps - source_range = 909990.8339241028 + source_range = 909990.8339241028 - 909995.8339241028 23.976fps speed: 200% timewarp to get from 948854 to 948874 @@ -662,7 +662,7 @@ def test_img_sequence_multiple_timewarps(): """ Img sequence clip available files = 948674-948974 25fps - source_range = 909990.8339241028 + source_range = 909990.8339241028 - 909995.8339241028 23.976fps multiple timewarps to get from 948842 to 948864 """ From 8dc243f2fef0e523bf639b2ccf53539eb7c487bb Mon Sep 17 00:00:00 2001 From: "robin@ynput.io" Date: Wed, 22 Jan 2025 21:03:41 +0100 Subject: [PATCH 054/103] Address feedback from PR. --- client/ayon_core/pipeline/editorial.py | 29 ++++++++++--------- .../publish/collect_otio_subset_resources.py | 6 ++-- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/client/ayon_core/pipeline/editorial.py b/client/ayon_core/pipeline/editorial.py index 2ecc786581..fc962300d8 100644 --- a/client/ayon_core/pipeline/editorial.py +++ b/client/ayon_core/pipeline/editorial.py @@ -430,10 +430,10 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end): * abs(time_scalar) ) - # duration 1 frame -> freeze frame -> end = start + 0 - offset_duration -= 1 - # negative duration = frozen frame - offset_duration = max(0, offset_duration) + # 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 @@ -460,17 +460,18 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end): else: new_idx = round(idx + tw["lookup"][idx]) - if not 0 <= new_idx < len(frame_range): - # 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)" - ) + if 0 <= new_idx < len(frame_range): + frame_range[idx] = frame_range[new_idx] + continue - frame_range[idx] = frame_range[new_idx] + # 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) diff --git a/client/ayon_core/plugins/publish/collect_otio_subset_resources.py b/client/ayon_core/plugins/publish/collect_otio_subset_resources.py index bfcf5a71bb..deb51f62a5 100644 --- a/client/ayon_core/plugins/publish/collect_otio_subset_resources.py +++ b/client/ayon_core/plugins/publish/collect_otio_subset_resources.py @@ -188,7 +188,7 @@ class CollectOtioSubsetResources( repre = self._create_representation( frame_start, frame_end, collection=collection) else: - filename, = tuple(collection) + filename = tuple(collection)[0] self.log.debug(filename) # TODO: discuss this, it erases frame number. @@ -200,8 +200,8 @@ class CollectOtioSubsetResources( 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 From b2bbec42ba2d068076d7526e96e84e9c47ae839e Mon Sep 17 00:00:00 2001 From: "robin@ynput.io" Date: Wed, 22 Jan 2025 21:04:54 +0100 Subject: [PATCH 055/103] Fix lint. --- client/ayon_core/pipeline/editorial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/editorial.py b/client/ayon_core/pipeline/editorial.py index fc962300d8..6e5e2ec67a 100644 --- a/client/ayon_core/pipeline/editorial.py +++ b/client/ayon_core/pipeline/editorial.py @@ -432,7 +432,7 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end): # Offset duration by 1 for media out frame # - only if duration is not single frame (start frame != end frame) - if offset_duration > 0: + if offset_duration > 0: offset_duration -= 1 media_out_trimmed = media_in_trimmed + offset_duration From 27145add3aefa23bf34040f7e307307bd473fd00 Mon Sep 17 00:00:00 2001 From: Liam Hoflay Date: Thu, 23 Jan 2025 10:25:13 +0000 Subject: [PATCH 056/103] making 127.0.0.1 rather than 0.0.0.0 --- client/ayon_core/tools/tray/webserver/server.py | 6 +++--- client/ayon_core/version.py | 2 +- package.py | 2 +- pyproject.toml | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/tools/tray/webserver/server.py b/client/ayon_core/tools/tray/webserver/server.py index d9b3b857f5..70d1fc8c0f 100644 --- a/client/ayon_core/tools/tray/webserver/server.py +++ b/client/ayon_core/tools/tray/webserver/server.py @@ -28,7 +28,7 @@ def find_free_port( exclude_ports (list, tuple, set): List of ports that won't be checked form entered range. host (str): Host where will check for free ports. Set to - "0.0.0.0" by default. + "127.0.0.1" by default. """ if port_from is None: port_from = 8079 @@ -42,7 +42,7 @@ def find_free_port( # Default host is localhost but it is possible to look for other hosts if host is None: - host = "0.0.0.0" + host = "127.0.0.1" found_port = None while True: @@ -78,7 +78,7 @@ class WebServerManager: self._log = None self.port = port or 8079 - self.host = host or "0.0.0.0" + self.host = host or "127.0.0.1" self.on_stop_callbacks = [] diff --git a/client/ayon_core/version.py b/client/ayon_core/version.py index 2775cb606a..fabbb4f0e9 100644 --- a/client/ayon_core/version.py +++ b/client/ayon_core/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring AYON addon 'core' version.""" -__version__ = "1.0.15-dev" +__version__ = "1.0.14-bk.1" diff --git a/package.py b/package.py index af3342f3f2..db5e0b80dc 100644 --- a/package.py +++ b/package.py @@ -1,6 +1,6 @@ name = "core" title = "Core" -version = "1.0.15-dev" +version = "1.0.14-bk.1" client_dir = "ayon_core" diff --git a/pyproject.toml b/pyproject.toml index e040ce986f..dc550a1eda 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ [tool.poetry] name = "ayon-core" -version = "1.0.15-dev" +version = "1.0.14-bk.1" description = "" authors = ["Ynput Team "] readme = "README.md" From 6708abaa43c1e22ea2435f450c03ca5c2e6cd500 Mon Sep 17 00:00:00 2001 From: Liam Hoflay Date: Thu, 23 Jan 2025 11:19:48 +0000 Subject: [PATCH 057/103] version revert --- client/ayon_core/version.py | 2 +- package.py | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/version.py b/client/ayon_core/version.py index fabbb4f0e9..2775cb606a 100644 --- a/client/ayon_core/version.py +++ b/client/ayon_core/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring AYON addon 'core' version.""" -__version__ = "1.0.14-bk.1" +__version__ = "1.0.15-dev" diff --git a/package.py b/package.py index db5e0b80dc..af3342f3f2 100644 --- a/package.py +++ b/package.py @@ -1,6 +1,6 @@ name = "core" title = "Core" -version = "1.0.14-bk.1" +version = "1.0.15-dev" client_dir = "ayon_core" diff --git a/pyproject.toml b/pyproject.toml index dc550a1eda..e040ce986f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ [tool.poetry] name = "ayon-core" -version = "1.0.14-bk.1" +version = "1.0.15-dev" description = "" authors = ["Ynput Team "] readme = "README.md" From ffec35f69f140a426ee3198ef9de6d01a7c11840 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 23 Jan 2025 17:26:32 +0100 Subject: [PATCH 058/103] Add Silhouette host to defaults of few validators --- server/settings/publish_plugins.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/server/settings/publish_plugins.py b/server/settings/publish_plugins.py index 1bf2e853cf..2cca9cd66e 100644 --- a/server/settings/publish_plugins.py +++ b/server/settings/publish_plugins.py @@ -1033,7 +1033,8 @@ DEFAULT_PUBLISH_VALUES = { "maya", "nuke", "photoshop", - "substancepainter" + "substancepainter", + "silhouette" ], "enabled": True, "optional": False, @@ -1053,7 +1054,8 @@ DEFAULT_PUBLISH_VALUES = { "harmony", "photoshop", "aftereffects", - "fusion" + "fusion", + "silhouette" ], "enabled": True, "optional": True, From 375378e74cc7cddb1d4e83bbc90c1bfd14d52693 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 24 Jan 2025 14:18:27 +0100 Subject: [PATCH 059/103] auto-fill project entity if is not passed in --- client/ayon_core/pipeline/create/creator_plugins.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/ayon_core/pipeline/create/creator_plugins.py b/client/ayon_core/pipeline/create/creator_plugins.py index 445b41cb4b..ce109e3a37 100644 --- a/client/ayon_core/pipeline/create/creator_plugins.py +++ b/client/ayon_core/pipeline/create/creator_plugins.py @@ -562,6 +562,9 @@ class BaseCreator(ABC): instance ) + if not project_entity: + project_entity = self.create_context.get_current_project_entity() + return get_product_name( project_name, task_name, From 8b4f5ec42a210c08f22bba61abc09d1d0f9dd047 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 24 Jan 2025 14:21:22 +0100 Subject: [PATCH 060/103] added one more check if current project is the project name passed in --- client/ayon_core/pipeline/create/creator_plugins.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/create/creator_plugins.py b/client/ayon_core/pipeline/create/creator_plugins.py index ce109e3a37..fca671d546 100644 --- a/client/ayon_core/pipeline/create/creator_plugins.py +++ b/client/ayon_core/pipeline/create/creator_plugins.py @@ -562,7 +562,8 @@ class BaseCreator(ABC): instance ) - if not project_entity: + cur_project_name = self.create_context.get_current_project_name() + if not project_entity and project_name == cur_project_name: project_entity = self.create_context.get_current_project_entity() return get_product_name( From 509c6a84d3004882cdff9b45e8cb31b18465db7e Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 24 Jan 2025 14:24:02 +0100 Subject: [PATCH 061/103] set minimum size of content widget to be able to stretch it's size --- client/ayon_core/tools/publisher/widgets/report_page.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/ayon_core/tools/publisher/widgets/report_page.py b/client/ayon_core/tools/publisher/widgets/report_page.py index b7afcf470a..2684f85356 100644 --- a/client/ayon_core/tools/publisher/widgets/report_page.py +++ b/client/ayon_core/tools/publisher/widgets/report_page.py @@ -1332,6 +1332,8 @@ class InstancesLogsView(QtWidgets.QFrame): content_widget = QtWidgets.QWidget(content_wrap_widget) content_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + content_widget.setMinimumSize(80, 80) + content_layout = QtWidgets.QVBoxLayout(content_widget) content_layout.setContentsMargins(8, 8, 8, 8) content_layout.setSpacing(15) From c2f3d8b114ecd93871029144bc81a77ad841758e Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 24 Jan 2025 14:31:06 +0100 Subject: [PATCH 062/103] use 'AYON_INSTANCE_ID' by default instead of 'AVALON_' --- client/ayon_core/pipeline/create/legacy_create.py | 4 ++-- client/ayon_core/pipeline/create/structures.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/pipeline/create/legacy_create.py b/client/ayon_core/pipeline/create/legacy_create.py index ec9b23ac62..f6427d9bd1 100644 --- a/client/ayon_core/pipeline/create/legacy_create.py +++ b/client/ayon_core/pipeline/create/legacy_create.py @@ -9,7 +9,7 @@ import os import logging import collections -from ayon_core.pipeline.constants import AVALON_INSTANCE_ID +from ayon_core.pipeline.constants import AYON_INSTANCE_ID from .product_name import get_product_name @@ -34,7 +34,7 @@ class LegacyCreator: # Default data self.data = collections.OrderedDict() # TODO use 'AYON_INSTANCE_ID' when all hosts support it - self.data["id"] = AVALON_INSTANCE_ID + self.data["id"] = AYON_INSTANCE_ID self.data["productType"] = self.product_type self.data["folderPath"] = folder_path self.data["productName"] = name diff --git a/client/ayon_core/pipeline/create/structures.py b/client/ayon_core/pipeline/create/structures.py index a1a4d5f8ef..a45e053cca 100644 --- a/client/ayon_core/pipeline/create/structures.py +++ b/client/ayon_core/pipeline/create/structures.py @@ -492,7 +492,7 @@ class CreatedInstance: item_id = data.get("id") # TODO use only 'AYON_INSTANCE_ID' when all hosts support it if item_id not in {AYON_INSTANCE_ID, AVALON_INSTANCE_ID}: - item_id = AVALON_INSTANCE_ID + item_id = AYON_INSTANCE_ID self._data["id"] = item_id self._data["productType"] = product_type self._data["productName"] = product_name From c380ebfedf03e77e4744df42db75eb527ae7663f Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 24 Jan 2025 17:09:03 +0100 Subject: [PATCH 063/103] use 'QTextEdit' for log message --- client/ayon_core/style/style.css | 2 + .../tools/publisher/widgets/report_page.py | 58 ++++++++++++++++--- 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/client/ayon_core/style/style.css b/client/ayon_core/style/style.css index a5e54453cc..0e19702d53 100644 --- a/client/ayon_core/style/style.css +++ b/client/ayon_core/style/style.css @@ -1171,6 +1171,8 @@ ValidationArtistMessage QLabel { #PublishLogMessage { font-family: "Noto Sans Mono"; + border: none; + padding: 0; } #PublishInstanceLogsLabel { diff --git a/client/ayon_core/tools/publisher/widgets/report_page.py b/client/ayon_core/tools/publisher/widgets/report_page.py index 2684f85356..58c78c0b06 100644 --- a/client/ayon_core/tools/publisher/widgets/report_page.py +++ b/client/ayon_core/tools/publisher/widgets/report_page.py @@ -1117,6 +1117,54 @@ class LogIconFrame(QtWidgets.QFrame): painter.end() +class LogItemMessage(QtWidgets.QTextEdit): + def __init__(self, msg, parent): + super().__init__(msg, parent) + + self.setObjectName("PublishLogMessage") + self.setReadOnly(True) + self.setFrameStyle(QtWidgets.QFrame.NoFrame) + self.setLineWidth(0) + self.setMidLineWidth(0) + pal = self.palette() + pal.setColor(QtGui.QPalette.Base, QtCore.Qt.transparent) + self.setPalette(pal) + self.setContentsMargins(0, 0, 0, 0) + viewport = self.viewport() + viewport.setContentsMargins(0, 0, 0, 0) + + self.setTextInteractionFlags( + QtCore.Qt.TextBrowserInteraction) + self.setCursor(QtGui.QCursor(QtCore.Qt.IBeamCursor)) + self.setLineWrapMode(QtWidgets.QTextEdit.WidgetWidth) + self.setWordWrapMode( + QtGui.QTextOption.WrapMode.WrapAtWordBoundaryOrAnywhere + ) + self.setSizePolicy( + QtWidgets.QSizePolicy.Preferred, + QtWidgets.QSizePolicy.Maximum + ) + document = self.document() + document.documentLayout().documentSizeChanged.connect( + self._adjust_minimum_size + ) + document.setDocumentMargin(0.0) + self._height = None + + def _adjust_minimum_size(self, size): + self._height = size.height() + (2 * self.frameWidth()) + self.updateGeometry() + + def sizeHint(self): + size = super().sizeHint() + if self._height is not None: + size.setHeight(self._height) + return size + + def minimumSizeHint(self): + return self.sizeHint() + + class LogItemWidget(QtWidgets.QWidget): log_level_to_flag = { 10: LOG_DEBUG_VISIBLE, @@ -1132,12 +1180,7 @@ class LogItemWidget(QtWidgets.QWidget): type_flag, level_n = self._get_log_info(log) icon_label = LogIconFrame( self, log["type"], level_n, log.get("is_validation_error")) - message_label = QtWidgets.QLabel(log["msg"].rstrip(), self) - message_label.setObjectName("PublishLogMessage") - message_label.setTextInteractionFlags( - QtCore.Qt.TextBrowserInteraction) - message_label.setCursor(QtGui.QCursor(QtCore.Qt.IBeamCursor)) - message_label.setWordWrap(True) + message_label = LogItemMessage(log["msg"].rstrip(), self) main_layout = QtWidgets.QHBoxLayout(self) main_layout.setContentsMargins(0, 0, 0, 0) @@ -1290,6 +1333,7 @@ class InstanceLogsWidget(QtWidgets.QWidget): label_widget = QtWidgets.QLabel(instance.label, self) label_widget.setObjectName("PublishInstanceLogsLabel") + label_widget.setWordWrap(True) logs_grid = LogsWithIconsView(instance.logs, self) layout = QtWidgets.QVBoxLayout(self) @@ -1329,10 +1373,10 @@ class InstancesLogsView(QtWidgets.QFrame): content_wrap_widget = QtWidgets.QWidget(scroll_area) content_wrap_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + content_wrap_widget.setMinimumWidth(80) content_widget = QtWidgets.QWidget(content_wrap_widget) content_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) - content_widget.setMinimumSize(80, 80) content_layout = QtWidgets.QVBoxLayout(content_widget) content_layout.setContentsMargins(8, 8, 8, 8) From 22546fd9c695a5e02a61252414a153b76ee9921a Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 24 Jan 2025 17:10:33 +0100 Subject: [PATCH 064/103] remove whitespaces --- client/ayon_core/tools/publisher/widgets/report_page.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/tools/publisher/widgets/report_page.py b/client/ayon_core/tools/publisher/widgets/report_page.py index 58c78c0b06..c0c1120fc9 100644 --- a/client/ayon_core/tools/publisher/widgets/report_page.py +++ b/client/ayon_core/tools/publisher/widgets/report_page.py @@ -1164,7 +1164,7 @@ class LogItemMessage(QtWidgets.QTextEdit): def minimumSizeHint(self): return self.sizeHint() - + class LogItemWidget(QtWidgets.QWidget): log_level_to_flag = { 10: LOG_DEBUG_VISIBLE, From aadd107975137ae4fcc611e93057bc2532850bb5 Mon Sep 17 00:00:00 2001 From: "robin@ynput.io" Date: Mon, 27 Jan 2025 13:04:00 +0100 Subject: [PATCH 065/103] Remove unecessary offsets. --- client/ayon_core/pipeline/editorial.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/client/ayon_core/pipeline/editorial.py b/client/ayon_core/pipeline/editorial.py index 6e5e2ec67a..e6e6294a81 100644 --- a/client/ayon_core/pipeline/editorial.py +++ b/client/ayon_core/pipeline/editorial.py @@ -344,8 +344,6 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end): # modifiers time_scalar = 1. - offset_in = 0 - offset_out = 0 time_warp_nodes = [] # Check for speed effects and adjust playback speed accordingly @@ -379,17 +377,12 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end): # add to timewarp nodes time_warp_nodes.append(tw_node) - # multiply by time scalar - offset_in *= time_scalar - offset_out *= time_scalar - # scale handles handle_start *= abs(time_scalar) handle_end *= abs(time_scalar) # flip offset and handles if reversed speed if time_scalar < 0: - offset_in, offset_out = offset_out, offset_in handle_start, handle_end = handle_end, handle_start # If media source is an image sequence, returned @@ -423,7 +416,7 @@ 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_in_trimmed = conformed_source_range.start_time.value offset_duration = ( conformed_source_range.duration.value From 521e50619ecda3e8001b1280c2e46952d8c3a2de Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 27 Jan 2025 13:55:52 +0100 Subject: [PATCH 066/103] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jakub Ježek --- server/settings/publish_plugins.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/settings/publish_plugins.py b/server/settings/publish_plugins.py index 2cca9cd66e..18e7d67f90 100644 --- a/server/settings/publish_plugins.py +++ b/server/settings/publish_plugins.py @@ -1034,7 +1034,7 @@ DEFAULT_PUBLISH_VALUES = { "nuke", "photoshop", "substancepainter", - "silhouette" + "silhouette", ], "enabled": True, "optional": False, @@ -1055,7 +1055,7 @@ DEFAULT_PUBLISH_VALUES = { "photoshop", "aftereffects", "fusion", - "silhouette" + "silhouette", ], "enabled": True, "optional": True, From 83b7c3d4429244126d984dc20bc7bcf5a663ab3e Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 27 Jan 2025 14:00:54 +0100 Subject: [PATCH 067/103] fix new-line character --- client/ayon_core/tools/publisher/widgets/report_page.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/tools/publisher/widgets/report_page.py b/client/ayon_core/tools/publisher/widgets/report_page.py index c0c1120fc9..1e46e7e52c 100644 --- a/client/ayon_core/tools/publisher/widgets/report_page.py +++ b/client/ayon_core/tools/publisher/widgets/report_page.py @@ -1119,7 +1119,10 @@ class LogIconFrame(QtWidgets.QFrame): class LogItemMessage(QtWidgets.QTextEdit): def __init__(self, msg, parent): - super().__init__(msg, parent) + super().__init__(parent) + + # Set as plain text to propagate new line characters + self.setPlainText(msg) self.setObjectName("PublishLogMessage") self.setReadOnly(True) From 5684c941deb0f2f62cf71f69667f786217ded612 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 28 Jan 2025 11:51:17 +0100 Subject: [PATCH 068/103] 'CreatedInstance' allows to pass in transient data --- .../ayon_core/pipeline/create/structures.py | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/client/ayon_core/pipeline/create/structures.py b/client/ayon_core/pipeline/create/structures.py index a45e053cca..17bb85b720 100644 --- a/client/ayon_core/pipeline/create/structures.py +++ b/client/ayon_core/pipeline/create/structures.py @@ -1,6 +1,7 @@ import copy import collections from uuid import uuid4 +import typing from typing import Optional, Dict, List, Any from ayon_core.lib.attribute_definitions import ( @@ -17,6 +18,9 @@ from ayon_core.pipeline import ( from .exceptions import ImmutableKeyError from .changes import TrackChangesItem +if typing.TYPE_CHECKING: + from .creator_plugins import BaseCreator + class ConvertorItem: """Item representing convertor plugin. @@ -444,10 +448,11 @@ class CreatedInstance: def __init__( self, - product_type, - product_name, - data, - creator, + product_type: str, + product_name: str, + data: Dict[str, Any], + creator: "BaseCreator", + transient_data: Optional[Dict[str, Any]] = None, ): self._creator = creator creator_identifier = creator.identifier @@ -462,7 +467,9 @@ class CreatedInstance: self._members = [] # Data that can be used for lifetime of object - self._transient_data = {} + if transient_data is None: + transient_data = {} + self._transient_data = transient_data # Create a copy of passed data to avoid changing them on the fly data = copy.deepcopy(data or {}) @@ -787,16 +794,26 @@ class CreatedInstance: self._create_context.instance_create_attr_defs_changed(self.id) @classmethod - def from_existing(cls, instance_data, creator): + def from_existing( + cls, + instance_data: Dict[str, Any], + creator: "BaseCreator", + transient_data: Optional[Dict[str, Any]] = None, + ) -> "CreatedInstance": """Convert instance data from workfile to CreatedInstance. Args: instance_data (Dict[str, Any]): Data in a structure ready for 'CreatedInstance' object. creator (BaseCreator): Creator plugin which is creating the - instance of for which the instance belong. - """ + instance of for which the instance belongs. + transient_data (Optional[dict[str, Any]]): Instance transient + data. + Returns: + CreatedInstance: Instance object. + + """ instance_data = copy.deepcopy(instance_data) product_type = instance_data.get("productType") @@ -809,7 +826,11 @@ class CreatedInstance: product_name = instance_data.get("subset") return cls( - product_type, product_name, instance_data, creator + product_type, + product_name, + instance_data, + creator, + transient_data=transient_data, ) def attribute_value_changed(self, key, changes): From f5bd7a9172f7a69aab66cecddb73ba973e03b50b Mon Sep 17 00:00:00 2001 From: "robin@ynput.io" Date: Tue, 28 Jan 2025 15:22:06 +0100 Subject: [PATCH 069/103] Apply frame offset to timewarp to handle source frame offset. --- client/ayon_core/pipeline/editorial.py | 32 +++++++ .../test_media_range_with_retimes.py | 88 +++++++++---------- 2 files changed, 76 insertions(+), 44 deletions(-) diff --git a/client/ayon_core/pipeline/editorial.py b/client/ayon_core/pipeline/editorial.py index e6e6294a81..8b6cfc52f1 100644 --- a/client/ayon_core/pipeline/editorial.py +++ b/client/ayon_core/pipeline/editorial.py @@ -441,6 +441,15 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end): in_frame += time_scalar frame_range.append(in_frame) + # Different editorial DCC might have different TimeWarp logic. + # The following logic assumes that the "lookup" list values are + # frame offsets relative to the current source frame number. + # + # media_source_range |______1_____|______2______|______3______| + # + # media_retimed_range |______2_____|______2______|______3______| + # + # TimeWarp lookup +1 0 0 for tw_idx, tw in enumerate(time_warp_nodes): for idx, frame_number in enumerate(frame_range): # First timewarp, apply on media range @@ -467,9 +476,32 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end): ) # adjust range if needed + media_in_trimmed_before_tw = media_in_trimmed media_in_trimmed = max(min(frame_range), media_in) media_out_trimmed = min(max(frame_range), media_out) + # If TimeWarp changes the first frame of the soure range, + # we need to offset the first TimeWarp values accordingly. + # + # expected_range |______2_____|______2______|______3______| + # + # EDITORIAL + # media_source_range |______1_____|______2______|______3______| + # + # TimeWarp lookup +1 0 0 + # + # EXTRACTED PLATE + # plate_range |______2_____|______3______|_ _ _ _ _ _ _| + # + # expected TimeWarp 0 -1 -1 + if media_in_trimmed != media_in_trimmed_before_tw: + offset = media_in_trimmed_before_tw - media_in_trimmed + offset *= 1.0 / time_scalar + time_warp_nodes[0]["lookup"] = [ + value + offset + for value in time_warp_nodes[0]["lookup"] + ] + # adjust available handles if needed if (media_in_trimmed - media_in) < handle_start: handle_start = max(0, media_in_trimmed - media_in) diff --git a/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py b/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py index fbab60623f..112d00b3e4 100644 --- a/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py +++ b/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py @@ -231,17 +231,17 @@ def test_movie_timewarp(): 'Class': 'TimeWarp', 'length': 4.0, 'lookup': [ - 2.0, - 1.8959999809265136, - 1.767999971389771, - 1.59199997138977, - 1.3439999809265135, - 1.0, - 0.5440000181198119, - -0.007999974250793684, - -0.6319999756813051, - -1.3039999847412114, - -2.0 + 0.0, + -0.10400001907348644, + -0.23200002861022906, + -0.4080000286102301, + -0.6560000190734865, + -1.0, + -1.455999981880188, + -2.0079999742507937, + -2.631999975681305, + -3.3039999847412114, + -4.0 ], 'name': 'TimeWarp2' } @@ -581,17 +581,17 @@ def test_img_sequence_timewarp_beyond_range(): 'Class': 'TimeWarp', 'length': 1.0, 'lookup': [ - -5.0, - -3.9440000305175777, - -2.852000034332275, - -1.6880000228881844, - -0.4160000076293944, - 1.0, - 2.5839999923706056, - 4.311999977111817, - 6.147999965667726, - 8.055999969482421, - 10.0 + 0.0, + 1.0559999694824223, + 2.147999965667725, + 3.3119999771118156, + 4.583999992370606, + 6.0, + 7.583999992370606, + 9.311999977111817, + 11.147999965667726, + 13.055999969482421, + 15.0 ], 'name': 'TimeWarp3' } @@ -632,17 +632,17 @@ def test_img_sequence_2X_speed_timewarp(): 'Class': 'TimeWarp', 'length': 4.0, 'lookup': [ - 2.0, - 1.7039999923706055, - 1.431999991416931, - 1.2079999942779531, - 1.055999998092652, - 1.0, - 1.056000007629395, - 1.208000022888184, - 1.432000034332276, - 1.7040000305175766, - 2.0 + 0.0, + -0.2960000076293945, + -0.568000008583069, + -0.7920000057220469, + -0.944000001907348, + -1.0, + -0.9439999923706051, + -0.791999977111816, + -0.5679999656677239, + -0.29599996948242335, + 0.0 ], 'name': 'TimeWarp6' } @@ -682,17 +682,17 @@ def test_img_sequence_multiple_timewarps(): 'Class': 'TimeWarp', 'length': 1.0, 'lookup': [ - -5.0, - -3.9440000305175777, - -2.852000034332275, - -1.6880000228881844, - -0.4160000076293944, - 1.0, - 2.5839999923706056, - 4.311999977111817, - 6.147999965667726, - 8.055999969482421, - 10.0 + 0.0, + 1.0559999694824223, + 2.147999965667725, + 3.3119999771118156, + 4.583999992370606, + 6.0, + 7.583999992370606, + 9.311999977111817, + 11.147999965667726, + 13.055999969482421, + 15.0 ], 'name': 'TimeWarp3' }, From d25c4701d1ec09f1aa81b9a80b356e79e3215456 Mon Sep 17 00:00:00 2001 From: "robin@ynput.io" Date: Wed, 29 Jan 2025 12:04:38 +0100 Subject: [PATCH 070/103] Fix Version follow up from Workfile set by hosts. --- .../ayon_core/pipeline/create/creator_plugins.py | 15 +++++++++++++-- .../plugins/publish/collect_scene_version.py | 7 +++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/pipeline/create/creator_plugins.py b/client/ayon_core/pipeline/create/creator_plugins.py index 445b41cb4b..f29d67f263 100644 --- a/client/ayon_core/pipeline/create/creator_plugins.py +++ b/client/ayon_core/pipeline/create/creator_plugins.py @@ -858,13 +858,24 @@ class Creator(BaseCreator): ["CollectAnatomyInstanceData"] ["follow_workfile_version"] ) + follow_version_hosts = ( + publish_settings + ["CollectSceneVersion"] + ["hosts"] + ) + + current_host = create_ctx.host.name + follow_workfile_version = ( + follow_workfile_version and + current_host in follow_version_hosts + ) # Gather version number provided from the instance. + current_workfile = create_ctx.get_current_workfile_path() version = instance.get("version") # If follow workfile, gather version from workfile path. - if version is None and follow_workfile_version: - current_workfile = self.create_context.get_current_workfile_path() + if version is None and follow_workfile_version and current_workfile: workfile_version = get_version_from_path(current_workfile) version = int(workfile_version) diff --git a/client/ayon_core/plugins/publish/collect_scene_version.py b/client/ayon_core/plugins/publish/collect_scene_version.py index 8d643062bc..8758bc2181 100644 --- a/client/ayon_core/plugins/publish/collect_scene_version.py +++ b/client/ayon_core/plugins/publish/collect_scene_version.py @@ -46,6 +46,13 @@ class CollectSceneVersion(pyblish.api.ContextPlugin): self.log.debug("Skipping for headless publishing") return + if context.data["hostName"] not in self.hosts: + self.log.debug( + f"Host {context.data['hostName']} is" + " not setup for following version." + ) + return + if not context.data.get('currentFile'): self.log.error("Cannot get current workfile path. " "Make sure your scene is saved.") From 3731e1226a840996f68dd61579f8bb0a5ba38063 Mon Sep 17 00:00:00 2001 From: "robin@ynput.io" Date: Wed, 29 Jan 2025 12:36:00 +0100 Subject: [PATCH 071/103] Fix lint + log message. --- client/ayon_core/pipeline/create/creator_plugins.py | 2 +- client/ayon_core/plugins/publish/collect_scene_version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/pipeline/create/creator_plugins.py b/client/ayon_core/pipeline/create/creator_plugins.py index f29d67f263..a7f191bc44 100644 --- a/client/ayon_core/pipeline/create/creator_plugins.py +++ b/client/ayon_core/pipeline/create/creator_plugins.py @@ -866,7 +866,7 @@ class Creator(BaseCreator): current_host = create_ctx.host.name follow_workfile_version = ( - follow_workfile_version and + follow_workfile_version and current_host in follow_version_hosts ) diff --git a/client/ayon_core/plugins/publish/collect_scene_version.py b/client/ayon_core/plugins/publish/collect_scene_version.py index 8758bc2181..fcd57f4110 100644 --- a/client/ayon_core/plugins/publish/collect_scene_version.py +++ b/client/ayon_core/plugins/publish/collect_scene_version.py @@ -49,7 +49,7 @@ class CollectSceneVersion(pyblish.api.ContextPlugin): if context.data["hostName"] not in self.hosts: self.log.debug( f"Host {context.data['hostName']} is" - " not setup for following version." + " not setup for collecting scene version." ) return From 1b109d761feb890a732b0995ead852c0a54db0e5 Mon Sep 17 00:00:00 2001 From: "robin@ynput.io" Date: Wed, 29 Jan 2025 14:03:23 +0100 Subject: [PATCH 072/103] Fix host refresh in publish/lib.py --- client/ayon_core/pipeline/publish/lib.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/publish/lib.py b/client/ayon_core/pipeline/publish/lib.py index 25495ed38b..62a68bc841 100644 --- a/client/ayon_core/pipeline/publish/lib.py +++ b/client/ayon_core/pipeline/publish/lib.py @@ -460,8 +460,19 @@ def filter_pyblish_plugins(plugins): ) apply_plugin_settings_automatically(plugin, plugin_settins, log) + # Pyblish already operated a filter based on host. + # But applying settings might have changed "hosts" + # value in plugin so re-filter. + plugin_hosts = getattr(plugin, "hosts", None) + if ( + plugin_hosts + and "*" not in plugin_hosts + and host_name not in plugin_hosts + ): + plugins.remove(plugin) + # Remove disabled plugins - if getattr(plugin, "enabled", True) is False: + elif getattr(plugin, "enabled", True) is False: plugins.remove(plugin) From 6763c37fb68c6c9bb7a3c01715b9c8e341f5e25e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 29 Jan 2025 14:16:21 +0100 Subject: [PATCH 073/103] :recycle: fix some style issues --- client/ayon_core/pipeline/traits/content.py | 8 ++- .../pipeline/traits/representation.py | 55 +++++++++++-------- .../pipeline/traits/three_dimensional.py | 3 + client/ayon_core/pipeline/traits/time.py | 10 ++-- 4 files changed, 45 insertions(+), 31 deletions(-) diff --git a/client/ayon_core/pipeline/traits/content.py b/client/ayon_core/pipeline/traits/content.py index b89537a35a..42893fc86e 100644 --- a/client/ayon_core/pipeline/traits/content.py +++ b/client/ayon_core/pipeline/traits/content.py @@ -5,7 +5,7 @@ import contextlib import re # TC003 is there because Path in TYPECHECKING will fail in tests -from pathlib import Path # noqa: TC003 +from pathlib import Path # noqa: TCH003 from typing import ClassVar, Generator, Optional from pydantic import Field @@ -43,6 +43,7 @@ class MimeType(TraitBase): id: ClassVar[str] = "ayon.content.MimeType.v1" mime_type: str = Field(..., title="Mime Type") + class LocatableContent(TraitBase): """LocatableContent trait model. @@ -66,6 +67,7 @@ class LocatableContent(TraitBase): location: str = Field(..., title="Location") is_templated: Optional[bool] = Field(default=None, title="Is Templated") + class FileLocation(TraitBase): """FileLocation trait model. @@ -89,6 +91,7 @@ class FileLocation(TraitBase): file_size: Optional[int] = Field(default=None, title="File Size") file_hash: Optional[str] = Field(default=None, title="File Hash") + class FileLocations(TraitBase): """FileLocation trait model. @@ -114,7 +117,7 @@ class FileLocations(TraitBase): This method will return all file paths from the trait. - Yeilds: + Yields: Path: List of file paths. """ @@ -432,7 +435,6 @@ class Bundle(TraitBase): """Convert bundle to representations.""" for idx, item in enumerate(self.items): yield Representation(name=f"{self.name} {idx}", traits=item) - class Fragment(TraitBase): diff --git a/client/ayon_core/pipeline/traits/representation.py b/client/ayon_core/pipeline/traits/representation.py index 498690dafd..f47a9a9607 100644 --- a/client/ayon_core/pipeline/traits/representation.py +++ b/client/ayon_core/pipeline/traits/representation.py @@ -8,18 +8,25 @@ import sys import uuid from functools import lru_cache from types import GenericAlias -from typing import ClassVar, Generic, ItemsView, Optional, Type, TypeVar, Union +from typing import ( + ClassVar, + Generic, + ItemsView, + Optional, + Type, + TypeVar, + Union, +) from .trait import ( IncompatibleTraitVersionError, LooseMatchingTraitError, MissingTraitError, TraitBase, - UpgradableTraitError, TraitValidationError, + UpgradableTraitError, ) - T = TypeVar("T", bound="TraitBase") @@ -333,14 +340,14 @@ class Representation(Generic[T]): ), None, ) - if not result: + if result is None: msg = f"Trait with ID {trait_id} not found." raise MissingTraitError(msg) return result def get_traits(self, traits: Optional[list[Type[T]]]=None - ) -> dict[str, T]: + ) -> dict[str, T]: """Get a list of traits from the representation. If no trait IDs or traits are provided, all traits will be returned. @@ -407,6 +414,7 @@ class Representation(Generic[T]): name (str): Representation name. Must be unique within instance. representation_id (str, optional): Representation ID. traits (list[TraitBase], optional): List of traits. + """ self.name = name self.representation_id = representation_id or uuid.uuid4().hex @@ -607,24 +615,23 @@ class Representation(Generic[T]): ) raise IncompatibleTraitVersionError(msg) from e - else: - if requested_version > found_version: - error_msg = ( - f"Requested trait version {requested_version} is " - f"higher than the found trait version {found_version}." - ) - raise IncompatibleTraitVersionError(error_msg) from e + if requested_version > found_version: + error_msg = ( + f"Requested trait version {requested_version} is " + f"higher than the found trait version {found_version}." + ) + raise IncompatibleTraitVersionError(error_msg) from e - if requested_version < found_version and hasattr( - e.found_trait, "upgrade"): - error_msg = ( - "Requested trait version " - f"{requested_version} is lower " - f"than the found trait version {found_version}." - ) - error: UpgradableTraitError = UpgradableTraitError(error_msg) - error.trait = e.found_trait - raise error from e + if requested_version < found_version and hasattr( + e.found_trait, "upgrade"): + error_msg = ( + "Requested trait version " + f"{requested_version} is lower " + f"than the found trait version {found_version}." + ) + error: UpgradableTraitError = UpgradableTraitError(error_msg) + error.trait = e.found_trait + raise error from e return trait_class @classmethod @@ -706,5 +713,5 @@ class Representation(Generic[T]): except TraitValidationError as e: errors.append(str(e)) if errors: - raise TraitValidationError( - f"representation {self.name}", "\n".join(errors)) + msg = f"representation {self.name}", "\n".join(errors) + raise TraitValidationError(msg) diff --git a/client/ayon_core/pipeline/traits/three_dimensional.py b/client/ayon_core/pipeline/traits/three_dimensional.py index eb27797ed2..e9eb0246c1 100644 --- a/client/ayon_core/pipeline/traits/three_dimensional.py +++ b/client/ayon_core/pipeline/traits/three_dimensional.py @@ -46,6 +46,7 @@ class Geometry(TraitBase): name: ClassVar[str] = "Geometry" description: ClassVar[str] = "Geometry trait model." + class Shader(TraitBase): """Shader trait model. @@ -58,6 +59,7 @@ class Shader(TraitBase): name: ClassVar[str] = "Shader" description: ClassVar[str] = "Shader trait model." + class Lighting(TraitBase): """Lighting trait model. @@ -70,6 +72,7 @@ class Lighting(TraitBase): name: ClassVar[str] = "Lighting" description: ClassVar[str] = "Lighting trait model." + class IESProfile(TraitBase): """IES profile (IES-LM-64) type trait model. diff --git a/client/ayon_core/pipeline/traits/time.py b/client/ayon_core/pipeline/traits/time.py index 46771b78fd..7f16013927 100644 --- a/client/ayon_core/pipeline/traits/time.py +++ b/client/ayon_core/pipeline/traits/time.py @@ -2,13 +2,13 @@ from __future__ import annotations import contextlib +import re from enum import Enum, auto +from re import Pattern from typing import TYPE_CHECKING, ClassVar, Optional, Union import clique from pydantic import Field, field_validator -import re -from re import Pattern from .trait import MissingTraitError, TraitBase, TraitValidationError @@ -16,6 +16,7 @@ if TYPE_CHECKING: from pathlib import Path + from .content import FileLocations from .representation import Representation @@ -391,10 +392,10 @@ class Sequence(TraitBase): """ src_collection = Sequence._get_collection(file_locations, regex) return list(src_collection.indexes) - + def get_frame_pattern(self) -> Pattern: """Get frame regex as pattern. - + If the regex is string, it will compile it to the pattern. """ @@ -405,6 +406,7 @@ class Sequence(TraitBase): return re.compile( r"\.(?P(?P0*)\d+)\.\D+\d?$") + # Do we need one for drop and non-drop frame? class SMPTETimecode(TraitBase): """SMPTE Timecode trait model.""" From 3f691607e54274370747c84abeb358a837f079aa Mon Sep 17 00:00:00 2001 From: "robin@ynput.io" Date: Wed, 29 Jan 2025 19:33:43 +0100 Subject: [PATCH 074/103] Fix [clip_media] reviewable. --- .../plugins/publish/collect_otio_review.py | 19 ++++++++++++++----- .../publish/collect_otio_subset_resources.py | 17 ----------------- .../plugins/publish/extract_otio_review.py | 3 +++ 3 files changed, 17 insertions(+), 22 deletions(-) diff --git a/client/ayon_core/plugins/publish/collect_otio_review.py b/client/ayon_core/plugins/publish/collect_otio_review.py index 4708b0a97c..36ef8f46d8 100644 --- a/client/ayon_core/plugins/publish/collect_otio_review.py +++ b/client/ayon_core/plugins/publish/collect_otio_review.py @@ -36,6 +36,16 @@ class CollectOtioReview(pyblish.api.InstancePlugin): # optionally get `reviewTrack` review_track_name = instance.data.get("reviewTrack") + # [clip_media] setting: + # Extract current clip source range as reviewable. + # Flag review content from otio_clip. + if not review_track_name and "review" in instance.data["families"]: + otio_review_clips = [otio_clip] + + # skip if no review track available + elif not review_track_name: + return + # generate range in parent otio_tl_range = otio_clip.range_in_parent() @@ -43,13 +53,12 @@ class CollectOtioReview(pyblish.api.InstancePlugin): clip_frame_end = int( otio_tl_range.start_time.value + otio_tl_range.duration.value) - # skip if no review track available - if not review_track_name: - return - # loop all tracks and match with name in `reviewTrack` for track in otio_timeline.tracks: - if review_track_name != track.name: + if ( + review_track_name is None + or review_track_name != track.name + ): continue # process correct track diff --git a/client/ayon_core/plugins/publish/collect_otio_subset_resources.py b/client/ayon_core/plugins/publish/collect_otio_subset_resources.py index deb51f62a5..d07c956856 100644 --- a/client/ayon_core/plugins/publish/collect_otio_subset_resources.py +++ b/client/ayon_core/plugins/publish/collect_otio_subset_resources.py @@ -195,13 +195,6 @@ class CollectOtioSubsetResources( 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) else: _trim = False @@ -217,13 +210,6 @@ class CollectOtioSubsetResources( repre = self._create_representation( frame_start, frame_end, file=filename, trim=_trim) - if ( - not instance.data.get("otioReviewClips") - and "review" in instance.data["families"] - ): - review_repre = self._create_representation( - frame_start, frame_end, - file=filename, delete=True, review=True) instance.data["originalDirname"] = self.staging_dir @@ -236,9 +222,6 @@ class CollectOtioSubsetResources( instance.data["representations"].append(repre) - # add review representation to instance data - if review_repre: - instance.data["representations"].append(review_repre) self.log.debug(instance.data) diff --git a/client/ayon_core/plugins/publish/extract_otio_review.py b/client/ayon_core/plugins/publish/extract_otio_review.py index 275c3e7c58..c2788af77c 100644 --- a/client/ayon_core/plugins/publish/extract_otio_review.py +++ b/client/ayon_core/plugins/publish/extract_otio_review.py @@ -320,6 +320,9 @@ class ExtractOTIOReview( end = max(collection.indexes) files = [f for f in collection] + # single frame sequence + if len(files) == 1: + files = files[0] ext = collection.format("{tail}") representation_data.update({ "name": ext[1:], From fd63c97f4e9baf75027895c9460b548ed453ed8a Mon Sep 17 00:00:00 2001 From: "robin@ynput.io" Date: Wed, 29 Jan 2025 19:56:34 +0100 Subject: [PATCH 075/103] Address feedback from PR. --- client/ayon_core/pipeline/publish/lib.py | 7 +----- .../plugins/publish/collect_scene_version.py | 25 +------------------ 2 files changed, 2 insertions(+), 30 deletions(-) diff --git a/client/ayon_core/pipeline/publish/lib.py b/client/ayon_core/pipeline/publish/lib.py index 62a68bc841..9adfc5e9e2 100644 --- a/client/ayon_core/pipeline/publish/lib.py +++ b/client/ayon_core/pipeline/publish/lib.py @@ -463,12 +463,7 @@ def filter_pyblish_plugins(plugins): # Pyblish already operated a filter based on host. # But applying settings might have changed "hosts" # value in plugin so re-filter. - plugin_hosts = getattr(plugin, "hosts", None) - if ( - plugin_hosts - and "*" not in plugin_hosts - and host_name not in plugin_hosts - ): + if not pyblish.plugin.host_is_compatible(plugin): plugins.remove(plugin) # Remove disabled plugins diff --git a/client/ayon_core/plugins/publish/collect_scene_version.py b/client/ayon_core/plugins/publish/collect_scene_version.py index fcd57f4110..7979b66abe 100644 --- a/client/ayon_core/plugins/publish/collect_scene_version.py +++ b/client/ayon_core/plugins/publish/collect_scene_version.py @@ -14,23 +14,7 @@ class CollectSceneVersion(pyblish.api.ContextPlugin): order = pyblish.api.CollectorOrder label = 'Collect Scene Version' # configurable in Settings - hosts = [ - "aftereffects", - "blender", - "celaction", - "fusion", - "harmony", - "hiero", - "houdini", - "maya", - "max", - "nuke", - "photoshop", - "resolve", - "tvpaint", - "motionbuilder", - "substancepainter" - ] + hosts = ["*"] # in some cases of headless publishing (for example webpublisher using PS) # you want to ignore version from name and let integrate use next version @@ -46,13 +30,6 @@ class CollectSceneVersion(pyblish.api.ContextPlugin): self.log.debug("Skipping for headless publishing") return - if context.data["hostName"] not in self.hosts: - self.log.debug( - f"Host {context.data['hostName']} is" - " not setup for collecting scene version." - ) - return - if not context.data.get('currentFile'): self.log.error("Cannot get current workfile path. " "Make sure your scene is saved.") From 920f917f943a50fca8c8b9a02d4b2e479d6d19ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 29 Jan 2025 22:22:05 +0100 Subject: [PATCH 076/103] :recycle: rename time to temporal to avoid conflict with std and some linter changes --- client/ayon_core/addon/__init__.py | 27 +++++++++---------- client/ayon_core/addon/interfaces.py | 23 +++++++++------- client/ayon_core/pipeline/traits/__init__.py | 2 +- client/ayon_core/pipeline/traits/content.py | 2 +- .../pipeline/traits/representation.py | 17 +++++++----- .../pipeline/traits/{time.py => temporal.py} | 7 +++-- client/ayon_core/pipeline/traits/utils.py | 2 +- pyproject.toml | 4 +-- 8 files changed, 44 insertions(+), 40 deletions(-) rename client/ayon_core/pipeline/traits/{time.py => temporal.py} (99%) diff --git a/client/ayon_core/addon/__init__.py b/client/ayon_core/addon/__init__.py index fd25dcb9d9..f78cea0b54 100644 --- a/client/ayon_core/addon/__init__.py +++ b/client/ayon_core/addon/__init__.py @@ -1,28 +1,25 @@ -# -*- coding: utf-8 -*- +"""Addons for AYON.""" from . import click_wrap -from .interfaces import ( - IPluginPaths, - ITrayAddon, - ITrayAction, - ITrayService, - IHostAddon, - ITraits, -) - from .base import ( - ProcessPreparationError, - ProcessContext, - AYONAddon, AddonsManager, + AYONAddon, + ProcessContext, + ProcessPreparationError, load_addons, ) - +from .interfaces import ( + IHostAddon, + IPluginPaths, + ITraits, + ITrayAction, + ITrayAddon, + ITrayService, +) from .utils import ( ensure_addons_are_process_context_ready, ensure_addons_are_process_ready, ) - __all__ = ( "click_wrap", diff --git a/client/ayon_core/addon/interfaces.py b/client/ayon_core/addon/interfaces.py index ccfd42d246..1070828d5c 100644 --- a/client/ayon_core/addon/interfaces.py +++ b/client/ayon_core/addon/interfaces.py @@ -1,3 +1,4 @@ +"""Addon interfaces for AYON.""" from __future__ import annotations import logging @@ -17,11 +18,11 @@ if TYPE_CHECKING: class _AYONInterfaceMeta(ABCMeta): """AYONInterface metaclass to print proper string.""" - def __str__(self): - return f"<'AYONInterface.{self.__name__}'>" + def __str__(cls): + return f"<'AYONInterface.{cls.__name__}'>" - def __repr__(self): - return str(self) + def __repr__(cls): + return str(cls) class AYONInterface(metaclass=_AYONInterfaceMeta): @@ -84,7 +85,7 @@ class IPluginPaths(AYONInterface): paths = [paths] return paths - def get_launcher_action_paths(self): + def get_launcher_action_paths(self) -> list[str]: """Receive launcher actions paths. Give addons ability to add launcher actions paths. @@ -208,7 +209,8 @@ class ITrayAddon(AYONInterface): """ if not self.tray_initialized: - # TODO: Called without initialized tray, still main thread needed + # TODO (Illicit): Called without initialized tray, still + # main thread needed. try: callback() @@ -245,7 +247,8 @@ class ITrayAddon(AYONInterface): self.manager.add_doubleclick_callback(self, callback) @staticmethod - def admin_submenu(tray_menu): + def admin_submenu(tray_menu: QtWidgets.QMenu) -> QtWidgets.QMenu: + """Get or create admin submenu.""" if ITrayAddon._admin_submenu is None: from qtpy import QtWidgets @@ -255,7 +258,9 @@ class ITrayAddon(AYONInterface): return ITrayAddon._admin_submenu @staticmethod - def add_action_to_admin_submenu(label, tray_menu): + def add_action_to_admin_submenu( + label: str, tray_menu: QtWidgets.QMenu) -> QtWidgets.QAction: + """Add action to admin submenu.""" from qtpy import QtWidgets menu = ITrayAddon.admin_submenu(tray_menu) @@ -330,7 +335,7 @@ class ITrayService(ITrayAddon): """Service label showed in menu.""" raise NotImplementedError - # TODO be able to get any sort of information to show/print + # TODO (Illicit): be able to get any sort of information to show/print # @abstractmethod # def get_service_info(self): # pass diff --git a/client/ayon_core/pipeline/traits/__init__.py b/client/ayon_core/pipeline/traits/__init__.py index e3ca610df1..6603990b53 100644 --- a/client/ayon_core/pipeline/traits/__init__.py +++ b/client/ayon_core/pipeline/traits/__init__.py @@ -15,7 +15,7 @@ from .lifecycle import Persistent, Transient from .meta import Tagged, TemplatePath, Variant from .representation import Representation from .three_dimensional import Geometry, IESProfile, Lighting, Shader, Spatial -from .time import ( +from .temporal import ( FrameRanged, GapPolicy, Handles, diff --git a/client/ayon_core/pipeline/traits/content.py b/client/ayon_core/pipeline/traits/content.py index 42893fc86e..16c3b47920 100644 --- a/client/ayon_core/pipeline/traits/content.py +++ b/client/ayon_core/pipeline/traits/content.py @@ -11,7 +11,7 @@ from typing import ClassVar, Generator, Optional from pydantic import Field from .representation import Representation -from .time import FrameRanged, Handles, Sequence +from .temporal import FrameRanged, Handles, Sequence from .trait import ( MissingTraitError, TraitBase, diff --git a/client/ayon_core/pipeline/traits/representation.py b/client/ayon_core/pipeline/traits/representation.py index f47a9a9607..8015d75d74 100644 --- a/client/ayon_core/pipeline/traits/representation.py +++ b/client/ayon_core/pipeline/traits/representation.py @@ -501,7 +501,7 @@ class Representation(Generic[T]): klass = getattr(module, attr_name) if not inspect.isclass(klass): continue - # this needs to be done because of the bug? in + # this needs to be done because of the bug? in # python ABCMeta, where ``issubclass`` is not working # if it hits the GenericAlias (that is in fact # tuple[int, int]). This is added to the scope by @@ -511,7 +511,8 @@ class Representation(Generic[T]): if issubclass(klass, TraitBase) \ and str(klass.id).startswith(trait_id): trait_candidates.add(klass) - return trait_candidates # type: ignore + # I + return trait_candidates # type: ignore[return-value] @classmethod @lru_cache(maxsize=64) @@ -632,11 +633,11 @@ class Representation(Generic[T]): error: UpgradableTraitError = UpgradableTraitError(error_msg) error.trait = e.found_trait raise error from e - return trait_class + return trait_class # type: ignore[return-value] @classmethod def from_dict( - cls, + cls: Type[Representation], name: str, representation_id: Optional[str]=None, trait_data: Optional[dict] = None) -> Representation: @@ -708,10 +709,12 @@ class Representation(Generic[T]): """ errors = [] for trait in self._data.values(): + # we do this in the loop to catch all the errors try: trait.validate_trait(self) - except TraitValidationError as e: + except TraitValidationError as e: # noqa: PERF203 errors.append(str(e)) if errors: - msg = f"representation {self.name}", "\n".join(errors) - raise TraitValidationError(msg) + msg = "\n".join(errors) + scope = self.name + raise TraitValidationError(scope, msg) diff --git a/client/ayon_core/pipeline/traits/time.py b/client/ayon_core/pipeline/traits/temporal.py similarity index 99% rename from client/ayon_core/pipeline/traits/time.py rename to client/ayon_core/pipeline/traits/temporal.py index 7f16013927..7c45eef9b9 100644 --- a/client/ayon_core/pipeline/traits/time.py +++ b/client/ayon_core/pipeline/traits/temporal.py @@ -13,7 +13,6 @@ from pydantic import Field, field_validator from .trait import MissingTraitError, TraitBase, TraitValidationError if TYPE_CHECKING: - from pathlib import Path @@ -205,9 +204,9 @@ class Sequence(TraitBase): handles_frame_end) self.validate_frame_padding(file_locs) - - def validate_frame_list( + + def validate_frame_list( # noqa: C901 self, file_locations: FileLocations, frame_start: Optional[int] = None, @@ -246,7 +245,7 @@ class Sequence(TraitBase): frame_regex = re.compile(self.frame_regex) elif isinstance(self.frame_regex, Pattern): frame_regex = self.frame_regex - + frames = self.get_frame_list( file_locations, frame_regex) else: diff --git a/client/ayon_core/pipeline/traits/utils.py b/client/ayon_core/pipeline/traits/utils.py index 54386fe8ca..2aa6173464 100644 --- a/client/ayon_core/pipeline/traits/utils.py +++ b/client/ayon_core/pipeline/traits/utils.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING from clique import assemble -from ayon_core.pipeline.traits.time import FrameRanged +from ayon_core.pipeline.traits.temporal import FrameRanged if TYPE_CHECKING: from pathlib import Path diff --git a/pyproject.toml b/pyproject.toml index 6e01a3efd7..975e141842 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,8 +79,6 @@ pydocstyle.convention = "google" select = ["ALL"] ignore = [ "PTH", - "ANN101", - "ANN102", "ANN204", "COM812", "S603", @@ -91,6 +89,8 @@ ignore = [ "UP035", # .. "ARG002", "INP001", # add `__init__.py` to namespaced package + "FIX002", # FIX all TODOs + "TD003", # missing issue link ] # Allow fix for all enabled rules (when `--fix`) is provided. From 423277580c81e895bc4fdd927f80387ce013718d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 29 Jan 2025 23:02:04 +0100 Subject: [PATCH 077/103] :sparkles: add IntendedUse trait --- client/ayon_core/pipeline/traits/meta.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/traits/meta.py b/client/ayon_core/pipeline/traits/meta.py index 2dc8ea5a27..00b5013f72 100644 --- a/client/ayon_core/pipeline/traits/meta.py +++ b/client/ayon_core/pipeline/traits/meta.py @@ -93,8 +93,8 @@ class KeepOriginalName(TraitBase): Note: This is not a persistent trait. - """ + name: ClassVar[str] = "KeepOriginalName" description: ClassVar[str] = "Keep Original Name Trait Model" id: ClassVar[str] = "ayon.meta.KeepOriginalName.v1" @@ -111,3 +111,18 @@ class SourceApplication(TraitBase): variant: str = Field(..., title="Application Variant (e.g. Pro)") version: str = Field(..., title="Application Version") platform: str = Field(..., title="Platform Name") + + +class IntendedUse(TraitBase): + """Intended use of the representation. + + This trait describes the intended use of the representation. It + can be used in cases, where the other traits are not enough to + describe the intended use. For example txt file with tracking + points can be used as corner pin in After Effect but not in Nuke. + """ + + name: ClassVar[str] = "IntendedUse" + description: ClassVar[str] = "Intended Use Trait Model" + id: ClassVar[str] = "ayon.meta.IntendedUse.v1" + use: str = Field(..., title="Intended Use") From 5eead5f66ac991514c42fb5dfa56ed108dcec9f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 29 Jan 2025 23:02:50 +0100 Subject: [PATCH 078/103] :package: update ruff to get rid of that annoying ANN101 and ANN102 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 975e141842..8d4a501651 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ pytest = "^8.0" pytest-print = "^1.0" ayon-python-api = "^1.0" # linting dependencies -ruff = "^0.3.3" +ruff = "^0.9.3" pre-commit = "^4" codespell = "^2.2.6" semver = "^3.0.2" From fef36a7cd9964f592777838336fa3fdc5d38a680 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 29 Jan 2025 23:03:20 +0100 Subject: [PATCH 079/103] :recycle: unify docstring style and signatures --- client/ayon_core/pipeline/traits/color.py | 1 + client/ayon_core/pipeline/traits/content.py | 10 ++-------- client/ayon_core/pipeline/traits/cryptography.py | 2 ++ client/ayon_core/pipeline/traits/representation.py | 14 +++++++------- client/ayon_core/pipeline/traits/temporal.py | 12 ++++++------ .../ayon_core/pipeline/traits/three_dimensional.py | 2 +- .../ayon_core/pipeline/traits/two_dimensional.py | 7 +------ 7 files changed, 20 insertions(+), 28 deletions(-) diff --git a/client/ayon_core/pipeline/traits/color.py b/client/ayon_core/pipeline/traits/color.py index 26802d51f8..b816593624 100644 --- a/client/ayon_core/pipeline/traits/color.py +++ b/client/ayon_core/pipeline/traits/color.py @@ -21,6 +21,7 @@ class ColorManaged(TraitBase): in the "current" OCIO context. config (str): An OCIO config name defining color space. """ + id: ClassVar[str] = "ayon.color.ColorManaged.v1" name: ClassVar[str] = "ColorManaged" description: ClassVar[str] = "Color Managed trait." diff --git a/client/ayon_core/pipeline/traits/content.py b/client/ayon_core/pipeline/traits/content.py index 16c3b47920..75b1263ea7 100644 --- a/client/ayon_core/pipeline/traits/content.py +++ b/client/ayon_core/pipeline/traits/content.py @@ -5,7 +5,7 @@ import contextlib import re # TC003 is there because Path in TYPECHECKING will fail in tests -from pathlib import Path # noqa: TCH003 +from pathlib import Path # noqa: TC003 from typing import ClassVar, Generator, Optional from pydantic import Field @@ -35,7 +35,6 @@ class MimeType(TraitBase): description (str): Trait description. id (str): id should be namespaced trait name with version mime_type (str): Mime type like image/jpeg. - """ name: ClassVar[str] = "MimeType" @@ -58,7 +57,6 @@ class LocatableContent(TraitBase): description (str): Trait description. id (str): id should be namespaced trait name with version location (str): Location. - """ name: ClassVar[str] = "LocatableContent" @@ -82,8 +80,8 @@ class FileLocation(TraitBase): file_path (str): File path. file_size (int): File size in bytes. file_hash (str): File hash. - """ + name: ClassVar[str] = "FileLocation" description: ClassVar[str] = "FileLocation Trait Model" id: ClassVar[str] = "ayon.content.FileLocation.v1" @@ -359,7 +357,6 @@ class RootlessLocation(TraitBase): description (str): Trait description. id (str): id should be namespaced trait name with version rootless_path (str): Rootless path. - """ name: ClassVar[str] = "RootlessLocation" @@ -383,7 +380,6 @@ class Compressed(TraitBase): description (str): Trait description. id (str): id should be namespaced trait name with version compression_type (str): Compression type. - """ name: ClassVar[str] = "Compressed" @@ -422,7 +418,6 @@ class Bundle(TraitBase): description (str): Trait description. id (str): id should be namespaced trait name with version items (list[list[TraitBase]]): List of representations. - """ name: ClassVar[str] = "Bundle" @@ -460,7 +455,6 @@ class Fragment(TraitBase): description (str): Trait description. id (str): id should be namespaced trait name with version parent (str): Parent representation id. - """ name: ClassVar[str] = "Fragment" diff --git a/client/ayon_core/pipeline/traits/cryptography.py b/client/ayon_core/pipeline/traits/cryptography.py index 42abee779c..3719f3dbbf 100644 --- a/client/ayon_core/pipeline/traits/cryptography.py +++ b/client/ayon_core/pipeline/traits/cryptography.py @@ -16,6 +16,7 @@ class DigitallySigned(TraitBase): Attributes: signature (str): Digital signature. """ + id: ClassVar[str] = "ayon.cryptography.DigitallySigned.v1" name: ClassVar[str] = "DigitallySigned" description: ClassVar[str] = "Digitally signed trait." @@ -29,6 +30,7 @@ class PGPSigned(DigitallySigned): Attributes: signature (str): PGP signature. """ + id: ClassVar[str] = "ayon.cryptography.PGPSigned.v1" name: ClassVar[str] = "PGPSigned" description: ClassVar[str] = "PGP signed trait." diff --git a/client/ayon_core/pipeline/traits/representation.py b/client/ayon_core/pipeline/traits/representation.py index 8015d75d74..d185c0466c 100644 --- a/client/ayon_core/pipeline/traits/representation.py +++ b/client/ayon_core/pipeline/traits/representation.py @@ -57,8 +57,8 @@ class Representation(Generic[T]): Arguments: name (str): Representation name. Must be unique within instance. representation_id (str): Representation ID. - """ + _data: dict[str, T] _module_blacklist: ClassVar[list[str]] = [ "_", "builtins", "pydantic", @@ -134,7 +134,7 @@ class Representation(Generic[T]): """Return the traits as items.""" return ItemsView(self._data) - def add_trait(self, trait: T, *, exists_ok: bool=False) -> None: + def add_trait(self, trait: T, *, exists_ok: bool = False) -> None: """Add a trait to the Representation. Args: @@ -156,7 +156,7 @@ class Representation(Generic[T]): self._data[trait.id] = trait def add_traits( - self, traits: list[T], *, exists_ok: bool=False) -> None: + self, traits: list[T], *, exists_ok: bool = False) -> None: """Add a list of traits to the Representation. Args: @@ -346,7 +346,7 @@ class Representation(Generic[T]): return result def get_traits(self, - traits: Optional[list[Type[T]]]=None + traits: Optional[list[Type[T]]] = None ) -> dict[str, T]: """Get a list of traits from the representation. @@ -406,8 +406,8 @@ class Representation(Generic[T]): def __init__( self, name: str, - representation_id: Optional[str]=None, - traits: Optional[list[T]]=None): + representation_id: Optional[str] = None, + traits: Optional[list[T]] = None): """Initialize the data. Args: @@ -639,7 +639,7 @@ class Representation(Generic[T]): def from_dict( cls: Type[Representation], name: str, - representation_id: Optional[str]=None, + representation_id: Optional[str] = None, trait_data: Optional[dict] = None) -> Representation: """Create a representation from a dictionary. diff --git a/client/ayon_core/pipeline/traits/temporal.py b/client/ayon_core/pipeline/traits/temporal.py index 7c45eef9b9..f924a8d1e1 100644 --- a/client/ayon_core/pipeline/traits/temporal.py +++ b/client/ayon_core/pipeline/traits/temporal.py @@ -14,8 +14,6 @@ from .trait import MissingTraitError, TraitBase, TraitValidationError if TYPE_CHECKING: - from pathlib import Path - from .content import FileLocations from .representation import Representation @@ -132,13 +130,16 @@ class Sequence(TraitBase): gaps_policy: Optional[GapPolicy] = Field( default=GapPolicy.forbidden, title="Gaps Policy") frame_padding: int = Field(..., title="Frame Padding") - frame_regex: Optional[Union[Pattern, str]] = Field(default=None, title="Frame Regex") - frame_spec: Optional[str] = Field(default=None, title="Frame Specification") + frame_regex: Optional[Union[Pattern, str]] = Field( + default=None, title="Frame Regex") + frame_spec: Optional[str] = Field(default=None, + title="Frame Specification") @field_validator("frame_regex") @classmethod def validate_frame_regex( - cls, v: Optional[Union[Pattern, str]]) -> Optional[Union[Pattern, str]]: + cls, v: Optional[Union[Pattern, str]] + ) -> Optional[Union[Pattern, str]]: """Validate frame regex.""" _v = v if v and isinstance(v, Pattern): @@ -419,7 +420,6 @@ class Static(TraitBase): """Static time trait. Used to define static time (single frame). - """ name: ClassVar[str] = "Static" description: ClassVar[str] = "Static Time Trait" diff --git a/client/ayon_core/pipeline/traits/three_dimensional.py b/client/ayon_core/pipeline/traits/three_dimensional.py index e9eb0246c1..67f4415f73 100644 --- a/client/ayon_core/pipeline/traits/three_dimensional.py +++ b/client/ayon_core/pipeline/traits/three_dimensional.py @@ -24,8 +24,8 @@ class Spatial(TraitBase): up_axis (str): Up axis. handedness (str): Handedness. meters_per_unit (float): Meters per unit. - """ + id: ClassVar[str] = "ayon.3d.Spatial.v1" name: ClassVar[str] = "Spatial" description: ClassVar[str] = "Spatial trait model." diff --git a/client/ayon_core/pipeline/traits/two_dimensional.py b/client/ayon_core/pipeline/traits/two_dimensional.py index 9095061bf2..62d6693336 100644 --- a/client/ayon_core/pipeline/traits/two_dimensional.py +++ b/client/ayon_core/pipeline/traits/two_dimensional.py @@ -11,6 +11,7 @@ from .trait import TraitBase if TYPE_CHECKING: from .content import FileLocation, FileLocations + class Image(TraitBase): """Image trait model. @@ -20,7 +21,6 @@ class Image(TraitBase): name (str): Trait name. description (str): Trait description. id (str): id should be namespaced trait name with version - """ name: ClassVar[str] = "Image" @@ -40,7 +40,6 @@ class PixelBased(TraitBase): display_window_width (int): Width of the image display window. display_window_height (int): Height of the image display window. pixel_aspect_ratio (float): Pixel aspect ratio. - """ name: ClassVar[str] = "PixelBased" @@ -66,7 +65,6 @@ class Planar(TraitBase): description (str): Trait description. id (str): id should be namespaced trait name with version planar_configuration (str): Planar configuration. - """ name: ClassVar[str] = "Planar" @@ -84,7 +82,6 @@ class Deep(TraitBase): name (str): Trait name. description (str): Trait description. id (str): id should be namespaced trait name with version - """ name: ClassVar[str] = "Deep" @@ -106,7 +103,6 @@ class Overscan(TraitBase): right (int): Right overscan/underscan. top (int): Top overscan/underscan. bottom (int): Bottom overscan/underscan. - """ name: ClassVar[str] = "Overscan" @@ -128,7 +124,6 @@ class UDIM(TraitBase): description (str): Trait description. id (str): id should be namespaced trait name with version udim (int): UDIM value. - """ name: ClassVar[str] = "UDIM" From ebf67890368873212fa9fb7255ad6e6be54186bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 29 Jan 2025 23:17:32 +0100 Subject: [PATCH 080/103] :recycle: organize imports --- client/ayon_core/addon/__init__.py | 23 ++++---- client/ayon_core/pipeline/traits/__init__.py | 56 +++++++++++-------- .../pipeline/traits/test_time_traits.py | 1 - 3 files changed, 43 insertions(+), 37 deletions(-) diff --git a/client/ayon_core/addon/__init__.py b/client/ayon_core/addon/__init__.py index f78cea0b54..a8cf51ae25 100644 --- a/client/ayon_core/addon/__init__.py +++ b/client/ayon_core/addon/__init__.py @@ -21,21 +21,18 @@ from .utils import ( ) __all__ = ( - "click_wrap", - - "IPluginPaths", - "ITrayAddon", - "ITrayAction", - "ITrayService", - "IHostAddon", - "ITraits", - - "ProcessPreparationError", - "ProcessContext", "AYONAddon", "AddonsManager", - "load_addons", - + "IHostAddon", + "IPluginPaths", + "ITraits", + "ITrayAction", + "ITrayAddon", + "ITrayService", + "ProcessContext", + "ProcessPreparationError", + "click_wrap", "ensure_addons_are_process_context_ready", "ensure_addons_are_process_ready", + "load_addons", ) diff --git a/client/ayon_core/pipeline/traits/__init__.py b/client/ayon_core/pipeline/traits/__init__.py index 6603990b53..645064d59f 100644 --- a/client/ayon_core/pipeline/traits/__init__.py +++ b/client/ayon_core/pipeline/traits/__init__.py @@ -12,9 +12,15 @@ from .content import ( ) from .cryptography import DigitallySigned, PGPSigned from .lifecycle import Persistent, Transient -from .meta import Tagged, TemplatePath, Variant +from .meta import ( + IntendedUse, + KeepOriginalLocation, + SourceApplication, + Tagged, + TemplatePath, + Variant, +) from .representation import Representation -from .three_dimensional import Geometry, IESProfile, Lighting, Shader, Spatial from .temporal import ( FrameRanged, GapPolicy, @@ -23,6 +29,7 @@ from .temporal import ( SMPTETimecode, Static, ) +from .three_dimensional import Geometry, IESProfile, Lighting, Shader, Spatial from .trait import ( MissingTraitError, TraitBase, @@ -40,25 +47,25 @@ from .utils import ( get_sequence_from_files, ) -__all__ = [ +__all__ = [ # noqa: RUF022 # base "Representation", "TraitBase", "MissingTraitError", "TraitValidationError", + # color + "ColorManaged", + # content "Bundle", "Compressed", "FileLocation", "FileLocations", - "MimeType", - "RootlessLocation", "Fragment", "LocatableContent", - - # color - "ColorManaged", + "MimeType", + "RootlessLocation", # cryptography "DigitallySigned", @@ -69,10 +76,28 @@ __all__ = [ "Transient", # meta + "IntendedUse", + "KeepOriginalLocation", + "SourceApplication", "Tagged", "TemplatePath", "Variant", + # temporal + "FrameRanged", + "GapPolicy", + "Handles", + "Sequence", + "SMPTETimecode", + "Static", + + # three-dimensional + "Geometry", + "IESProfile", + "Lighting", + "Shader", + "Spatial", + # two-dimensional "Compressed", "Deep", @@ -82,21 +107,6 @@ __all__ = [ "Planar", "UDIM", - # three-dimensional - "Geometry", - "IESProfile", - "Lighting", - "Shader", - "Spatial", - - # time - "FrameRanged", - "Static", - "Handles", - "GapPolicy", - "Sequence", - "SMPTETimecode", - # utils "get_sequence_from_files", ] diff --git a/tests/client/ayon_core/pipeline/traits/test_time_traits.py b/tests/client/ayon_core/pipeline/traits/test_time_traits.py index c716266fd7..100e4ed2b5 100644 --- a/tests/client/ayon_core/pipeline/traits/test_time_traits.py +++ b/tests/client/ayon_core/pipeline/traits/test_time_traits.py @@ -15,7 +15,6 @@ from ayon_core.pipeline.traits import ( from ayon_core.pipeline.traits.trait import TraitValidationError - def test_sequence_validations() -> None: """Test Sequence trait validation.""" file_locations_list = [ From ba67c436f943e19abd81f61eb9e56fd2d240c61e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 29 Jan 2025 23:25:43 +0100 Subject: [PATCH 081/103] :recycle: revert back some changes --- client/ayon_core/addon/interfaces.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/client/ayon_core/addon/interfaces.py b/client/ayon_core/addon/interfaces.py index 1070828d5c..bf844e2d8b 100644 --- a/client/ayon_core/addon/interfaces.py +++ b/client/ayon_core/addon/interfaces.py @@ -1,7 +1,6 @@ """Addon interfaces for AYON.""" from __future__ import annotations -import logging from abc import ABCMeta, abstractmethod from typing import TYPE_CHECKING, Callable, Optional, Type @@ -10,7 +9,6 @@ from ayon_core import resources if TYPE_CHECKING: from qtpy import QtWidgets - from ayon_core.addon import AddonsManager from ayon_core.pipeline.traits import TraitBase from ayon_core.tools.tray import TrayManager @@ -38,10 +36,6 @@ class AYONInterface(metaclass=_AYONInterfaceMeta): log = None - def __init__(self): - """Initialize interface.""" - self.log = logging.getLogger(self.__class__.__name__) - class IPluginPaths(AYONInterface): """Addon has plugin paths to return. @@ -162,7 +156,6 @@ class ITrayAddon(AYONInterface): """ tray_initialized = False - manager: AddonsManager = None _tray_manager: TrayManager = None _admin_submenu = None @@ -431,7 +424,6 @@ class IHostAddon(AYONInterface): @abstractmethod def host_name(self) -> str: """Name of host which addon represents.""" - raise NotImplementedError def get_workfile_extensions(self) -> list[str]: """Define workfile extensions for host. From 0fbee7da5ca9d7ddeed6fc484312f2df65769aef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 30 Jan 2025 10:31:42 +0100 Subject: [PATCH 082/103] :recycle: add `IntendedUse` to readme --- client/ayon_core/pipeline/traits/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/traits/README.md b/client/ayon_core/pipeline/traits/README.md index 515c6d1272..aa38d30a8e 100644 --- a/client/ayon_core/pipeline/traits/README.md +++ b/client/ayon_core/pipeline/traits/README.md @@ -166,6 +166,7 @@ to different packages based on their use: | | KeepOriginalLocation | Marks the representation to keep the original location of the file | | KeepOriginalName | Marks the representation to keep the original name of the file | | SourceApplication | Holds information about producing application, about it's version, variant and platform. +| | IntendedUse | For specifying the intended use of the representation if it cannot be easily determined by other traits. | three dimensional | Spatial | Spatial information like up-axis, units and handedness. | | Geometry | Type trait to mark the representation as a geometry. | | Shader | Type trait to mark the representation as a Shader. @@ -318,4 +319,4 @@ class MyAddon(AYONAddon, ITraits): If you want to use MyPy linter, you need to make sure that optional fields typed as `Optional[Type]` needs to set default value using `default` or `default_factory` parameter. Otherwise MyPy will -complain about missing named arguments. \ No newline at end of file +complain about missing named arguments. From ca03c4d86d2c08b347f4363030ec32b0a96b7721 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 30 Jan 2025 12:05:39 +0100 Subject: [PATCH 083/103] Add support for `optional_tooltip` attribute on `OptionalPyblishPluginMixin` --- client/ayon_core/pipeline/publish/publish_plugins.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/publish/publish_plugins.py b/client/ayon_core/pipeline/publish/publish_plugins.py index 57215eff68..94c0307ca0 100644 --- a/client/ayon_core/pipeline/publish/publish_plugins.py +++ b/client/ayon_core/pipeline/publish/publish_plugins.py @@ -304,8 +304,11 @@ class OptionalPyblishPluginMixin(AYONPyblishPluginMixin): active = getattr(cls, "active", True) # Return boolean stored under 'active' key with label of the class name label = cls.label or cls.__name__ + # Allow exposing tooltip from class with `optional_tooltip` attribute + tooltip = getattr(cls, "optional_tooltip", None) + return [ - BoolDef("active", default=active, label=label) + BoolDef("active", default=active, label=label, tooltip=tooltip) ] def is_active(self, data): From 64b6729eec537862bf05a515837936c5dff6b9a5 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 30 Jan 2025 12:40:53 +0100 Subject: [PATCH 084/103] Expose `optional_tooltip` directly as attribute on the `OptionalPyblishPluginMixin` for better auto-complete in IDEs --- client/ayon_core/pipeline/publish/publish_plugins.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/pipeline/publish/publish_plugins.py b/client/ayon_core/pipeline/publish/publish_plugins.py index 94c0307ca0..65a5a474ea 100644 --- a/client/ayon_core/pipeline/publish/publish_plugins.py +++ b/client/ayon_core/pipeline/publish/publish_plugins.py @@ -292,6 +292,9 @@ class OptionalPyblishPluginMixin(AYONPyblishPluginMixin): ``` """ + # Allow exposing tooltip from class with `optional_tooltip` attribute + optional_tooltip: Optional[str] = None + @classmethod def get_attribute_defs(cls): """Attribute definitions based on plugin's optional attribute.""" @@ -304,11 +307,12 @@ class OptionalPyblishPluginMixin(AYONPyblishPluginMixin): active = getattr(cls, "active", True) # Return boolean stored under 'active' key with label of the class name label = cls.label or cls.__name__ - # Allow exposing tooltip from class with `optional_tooltip` attribute - tooltip = getattr(cls, "optional_tooltip", None) return [ - BoolDef("active", default=active, label=label, tooltip=tooltip) + BoolDef("active", + default=active, + label=label, + tooltip=cls.optional_tooltip) ] def is_active(self, data): From e1438ed597550ee5b55e46efab3354838a42088a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 31 Jan 2025 23:30:04 +0100 Subject: [PATCH 085/103] Update client/ayon_core/pipeline/publish/publish_plugins.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/pipeline/publish/publish_plugins.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/pipeline/publish/publish_plugins.py b/client/ayon_core/pipeline/publish/publish_plugins.py index 65a5a474ea..cc6887e762 100644 --- a/client/ayon_core/pipeline/publish/publish_plugins.py +++ b/client/ayon_core/pipeline/publish/publish_plugins.py @@ -309,10 +309,12 @@ class OptionalPyblishPluginMixin(AYONPyblishPluginMixin): label = cls.label or cls.__name__ return [ - BoolDef("active", - default=active, - label=label, - tooltip=cls.optional_tooltip) + BoolDef( + "active", + default=active, + label=label, + tooltip=cls.optional_tooltip, + ) ] def is_active(self, data): From 5c53d201244b1bf5d5914a23c936472b594fb6d9 Mon Sep 17 00:00:00 2001 From: "robin@ynput.io" Date: Mon, 3 Feb 2025 10:51:59 +0100 Subject: [PATCH 086/103] Address feedback from PR. --- client/ayon_core/pipeline/create/creator_plugins.py | 5 +++-- client/ayon_core/pipeline/publish/lib.py | 10 +++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/client/ayon_core/pipeline/create/creator_plugins.py b/client/ayon_core/pipeline/create/creator_plugins.py index a7f191bc44..69cd3894af 100644 --- a/client/ayon_core/pipeline/create/creator_plugins.py +++ b/client/ayon_core/pipeline/create/creator_plugins.py @@ -877,10 +877,11 @@ class Creator(BaseCreator): # If follow workfile, gather version from workfile path. if version is None and follow_workfile_version and current_workfile: workfile_version = get_version_from_path(current_workfile) - version = int(workfile_version) + if workfile_version is not None: + version = int(workfile_version) # Fill-up version with next version available. - elif version is None: + if version is None: versions = self.get_next_versions_for_instances( [instance] ) diff --git a/client/ayon_core/pipeline/publish/lib.py b/client/ayon_core/pipeline/publish/lib.py index 9adfc5e9e2..cc5f67c74b 100644 --- a/client/ayon_core/pipeline/publish/lib.py +++ b/client/ayon_core/pipeline/publish/lib.py @@ -460,14 +460,14 @@ def filter_pyblish_plugins(plugins): ) apply_plugin_settings_automatically(plugin, plugin_settins, log) + # Remove disabled plugins + if getattr(plugin, "enabled", True) is False: + plugins.remove(plugin) + # Pyblish already operated a filter based on host. # But applying settings might have changed "hosts" # value in plugin so re-filter. - if not pyblish.plugin.host_is_compatible(plugin): - plugins.remove(plugin) - - # Remove disabled plugins - elif getattr(plugin, "enabled", True) is False: + elif not pyblish.plugin.host_is_compatible(plugin): plugins.remove(plugin) From 0293d74618c1a266215ca3051cb7d56da58f741c Mon Sep 17 00:00:00 2001 From: Robin De Lillo Date: Mon, 3 Feb 2025 11:25:34 +0100 Subject: [PATCH 087/103] Update client/ayon_core/plugins/publish/collect_otio_review.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/plugins/publish/collect_otio_review.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/plugins/publish/collect_otio_review.py b/client/ayon_core/plugins/publish/collect_otio_review.py index 36ef8f46d8..24f2f6c3e6 100644 --- a/client/ayon_core/plugins/publish/collect_otio_review.py +++ b/client/ayon_core/plugins/publish/collect_otio_review.py @@ -55,10 +55,11 @@ class CollectOtioReview(pyblish.api.InstancePlugin): # loop all tracks and match with name in `reviewTrack` for track in otio_timeline.tracks: - if ( - review_track_name is None - or review_track_name != track.name - ): + # Skip the loop + if review_track_name is None: + break + + if review_track_name != track.name: continue # process correct track From e12fa847c959cd4af1145810dcf74a38433232c2 Mon Sep 17 00:00:00 2001 From: "robin@ynput.io" Date: Mon, 3 Feb 2025 11:29:34 +0100 Subject: [PATCH 088/103] Address feedback from PR. --- client/ayon_core/plugins/publish/collect_otio_review.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/collect_otio_review.py b/client/ayon_core/plugins/publish/collect_otio_review.py index 24f2f6c3e6..064d4e3f3b 100644 --- a/client/ayon_core/plugins/publish/collect_otio_review.py +++ b/client/ayon_core/plugins/publish/collect_otio_review.py @@ -55,10 +55,12 @@ class CollectOtioReview(pyblish.api.InstancePlugin): # loop all tracks and match with name in `reviewTrack` for track in otio_timeline.tracks: - # Skip the loop + + # No review track defined, skip the loop if review_track_name is None: break + # Not current review track, skip it. if review_track_name != track.name: continue From 07ccade3a4062f10ae5a038a614b0f3260e406d6 Mon Sep 17 00:00:00 2001 From: "robin@ynput.io" Date: Mon, 3 Feb 2025 14:54:29 +0100 Subject: [PATCH 089/103] Fix lint. --- .../ayon_core/plugins/publish/collect_otio_subset_resources.py | 1 - client/ayon_core/plugins/publish/extract_otio_review.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/client/ayon_core/plugins/publish/collect_otio_subset_resources.py b/client/ayon_core/plugins/publish/collect_otio_subset_resources.py index d07c956856..f1fa6a817d 100644 --- a/client/ayon_core/plugins/publish/collect_otio_subset_resources.py +++ b/client/ayon_core/plugins/publish/collect_otio_subset_resources.py @@ -158,7 +158,6 @@ class CollectOtioSubsetResources( self.log.info( "frame_start-frame_end: {}-{}".format(frame_start, frame_end)) - review_repre = None if is_sequence: # file sequence way diff --git a/client/ayon_core/plugins/publish/extract_otio_review.py b/client/ayon_core/plugins/publish/extract_otio_review.py index c2788af77c..2461195b27 100644 --- a/client/ayon_core/plugins/publish/extract_otio_review.py +++ b/client/ayon_core/plugins/publish/extract_otio_review.py @@ -322,7 +322,7 @@ class ExtractOTIOReview( files = [f for f in collection] # single frame sequence if len(files) == 1: - files = files[0] + files = files[0] ext = collection.format("{tail}") representation_data.update({ "name": ext[1:], From b048f0aa3bae1e6c3b963ab89b88b804e0208c2a Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 4 Feb 2025 10:29:01 +0100 Subject: [PATCH 090/103] added helper method 'get_template_data' to create context --- client/ayon_core/pipeline/create/context.py | 31 +++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/client/ayon_core/pipeline/create/context.py b/client/ayon_core/pipeline/create/context.py index e29971415d..36a05725a6 100644 --- a/client/ayon_core/pipeline/create/context.py +++ b/client/ayon_core/pipeline/create/context.py @@ -29,6 +29,7 @@ from ayon_core.lib.events import QueuedEventSystem from ayon_core.lib.attribute_definitions import get_default_values from ayon_core.host import IPublishHost, IWorkfileHost from ayon_core.pipeline import Anatomy +from ayon_core.pipeline.template_data import get_template_data from ayon_core.pipeline.plugin_discover import DiscoverResult from .exceptions import ( @@ -480,6 +481,36 @@ class CreateContext: self.get_current_project_name()) return self._current_project_settings + def get_template_data( + self, folder_path: Optional[str], task_name: Optional[str] + ) -> dict[str, Any]: + """Prepare template data for given context. + + Method is using cached entities and settings to prepare template data. + + Args: + folder_path (Optional[str]): Folder path. + task_name (Optional[str]): Task name. + + Returns: + dict[str, Any]: Template data. + + """ + project_entity = self.get_current_project_entity() + folder_entity = task_entity = None + if folder_path: + folder_entity = self.get_folder_entity(folder_path) + if task_name and folder_entity: + task_entity = self.get_task_entity(folder_path, task_name) + + return get_template_data( + project_entity, + folder_entity, + task_entity, + host_name=self.host_name, + settings=self.get_current_project_settings(), + ) + @property def context_has_changed(self): """Host context has changed. From cc1ec148656a7afbf0546d35cae93350742c886f Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 4 Feb 2025 10:46:52 +0100 Subject: [PATCH 091/103] fix index validation --- client/ayon_core/lib/path_templates.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/lib/path_templates.py b/client/ayon_core/lib/path_templates.py index 057889403c..9e3e455a6c 100644 --- a/client/ayon_core/lib/path_templates.py +++ b/client/ayon_core/lib/path_templates.py @@ -587,8 +587,8 @@ class FormattingPart: if sub_key < 0: sub_key = len(value) + sub_key - invalid = 0 > sub_key < len(data) - if invalid: + valid = 0 <= sub_key < len(value) + if not valid: used_keys.append(sub_key) missing_key = True break From 43b18910bac326c00d0784b764641398738aca6b Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 4 Feb 2025 10:51:08 +0100 Subject: [PATCH 092/103] fix typehint --- client/ayon_core/pipeline/create/context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/create/context.py b/client/ayon_core/pipeline/create/context.py index 36a05725a6..c169df67df 100644 --- a/client/ayon_core/pipeline/create/context.py +++ b/client/ayon_core/pipeline/create/context.py @@ -483,7 +483,7 @@ class CreateContext: def get_template_data( self, folder_path: Optional[str], task_name: Optional[str] - ) -> dict[str, Any]: + ) -> Dict[str, Any]: """Prepare template data for given context. Method is using cached entities and settings to prepare template data. From c21b95679f72099c93f02e3e55a7af3bafad402f Mon Sep 17 00:00:00 2001 From: Ynbot Date: Tue, 4 Feb 2025 11:51:01 +0000 Subject: [PATCH 093/103] [Automated] Add generated package files from main --- client/ayon_core/version.py | 2 +- package.py | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/version.py b/client/ayon_core/version.py index 2775cb606a..da29c02004 100644 --- a/client/ayon_core/version.py +++ b/client/ayon_core/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring AYON addon 'core' version.""" -__version__ = "1.0.15-dev" +__version__ = "1.1.0" diff --git a/package.py b/package.py index af3342f3f2..fe0c7dbd18 100644 --- a/package.py +++ b/package.py @@ -1,6 +1,6 @@ name = "core" title = "Core" -version = "1.0.15-dev" +version = "1.1.0" client_dir = "ayon_core" diff --git a/pyproject.toml b/pyproject.toml index e040ce986f..32d101cc22 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ [tool.poetry] name = "ayon-core" -version = "1.0.15-dev" +version = "1.1.0" description = "" authors = ["Ynput Team "] readme = "README.md" From c7899fb3be31d215207c5e0cccf01a43e73408ff Mon Sep 17 00:00:00 2001 From: Ynbot Date: Tue, 4 Feb 2025 11:51:45 +0000 Subject: [PATCH 094/103] [Automated] Update version in package.py for develop --- client/ayon_core/version.py | 2 +- package.py | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/version.py b/client/ayon_core/version.py index da29c02004..909ecd7a3c 100644 --- a/client/ayon_core/version.py +++ b/client/ayon_core/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring AYON addon 'core' version.""" -__version__ = "1.1.0" +__version__ = "1.1.0+dev" diff --git a/package.py b/package.py index fe0c7dbd18..0b888f5c33 100644 --- a/package.py +++ b/package.py @@ -1,6 +1,6 @@ name = "core" title = "Core" -version = "1.1.0" +version = "1.1.0+dev" client_dir = "ayon_core" diff --git a/pyproject.toml b/pyproject.toml index 32d101cc22..32822391c8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ [tool.poetry] name = "ayon-core" -version = "1.1.0" +version = "1.1.0+dev" description = "" authors = ["Ynput Team "] readme = "README.md" From 516dd2d7cedfe1309aa033ce335d4c7130f3c693 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 4 Feb 2025 15:07:05 +0100 Subject: [PATCH 095/103] Escape & on Windows in shell using ^& in `run_subprocess` --- client/ayon_core/lib/execute.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/client/ayon_core/lib/execute.py b/client/ayon_core/lib/execute.py index 95696fd272..35e931a5fc 100644 --- a/client/ayon_core/lib/execute.py +++ b/client/ayon_core/lib/execute.py @@ -122,6 +122,16 @@ def run_subprocess(*args, **kwargs): ) args = (new_arg, ) + # Escape & on Windows in shell using ^& + if ( + kwargs.get("shell") is True + and len(args) == 1 + and isinstance(args[0], str) + and platform.system().lower() == "windows" + ): + new_arg = args[0].replace("&", "^&") + args = (new_arg, ) + # Get environents from kwarg or use current process environments if were # not passed. env = kwargs.get("env") or os.environ From ed0f5c8d7f300a984c139ad7849779c4e781b456 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 4 Feb 2025 15:20:28 +0100 Subject: [PATCH 096/103] Merge escape if checks + include `COMSPEC` check on Windows --- client/ayon_core/lib/execute.py | 36 ++++++++++++++++----------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/client/ayon_core/lib/execute.py b/client/ayon_core/lib/execute.py index 35e931a5fc..516ea958f5 100644 --- a/client/ayon_core/lib/execute.py +++ b/client/ayon_core/lib/execute.py @@ -108,31 +108,29 @@ def run_subprocess(*args, **kwargs): | getattr(subprocess, "CREATE_NO_WINDOW", 0) ) - # Escape parentheses for bash + # Escape special characters in certain shells if ( kwargs.get("shell") is True and len(args) == 1 and isinstance(args[0], str) - and os.getenv("SHELL") in ("/bin/bash", "/bin/sh") ): - new_arg = ( - args[0] - .replace("(", "\\(") - .replace(")", "\\)") - ) - args = (new_arg, ) + # Escape parentheses for bash + if os.getenv("SHELL") in ("/bin/bash", "/bin/sh"): + new_arg = ( + args[0] + .replace("(", "\\(") + .replace(")", "\\)") + ) + args = (new_arg,) + # Escape & on Windows in shell with `cmd.exe` using ^& + elif ( + platform.system().lower() == "windows" + and os.getenv("COMSPEC").endswith("cmd.exe") + ): + new_arg = args[0].replace("&", "^&") + args = (new_arg, ) - # Escape & on Windows in shell using ^& - if ( - kwargs.get("shell") is True - and len(args) == 1 - and isinstance(args[0], str) - and platform.system().lower() == "windows" - ): - new_arg = args[0].replace("&", "^&") - args = (new_arg, ) - - # Get environents from kwarg or use current process environments if were + # Get environments from kwarg or use current process environments if were # not passed. env = kwargs.get("env") or os.environ # Make sure environment contains only strings From bfcfa264d97d2527905f2dc00df73c75b1c50a90 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 5 Feb 2025 16:57:59 +0800 Subject: [PATCH 097/103] add substance designer as being part of the hosts in extract thumbnail --- client/ayon_core/plugins/publish/extract_thumbnail.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_core/plugins/publish/extract_thumbnail.py b/client/ayon_core/plugins/publish/extract_thumbnail.py index 8ae18f4abf..bd2f7eb0ae 100644 --- a/client/ayon_core/plugins/publish/extract_thumbnail.py +++ b/client/ayon_core/plugins/publish/extract_thumbnail.py @@ -35,6 +35,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): "resolve", "traypublisher", "substancepainter", + "substancedesigner", "nuke", "aftereffects", "unreal", From 4da77e745505ce3fe92302e3a508afeed611798a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 5 Feb 2025 16:50:40 +0100 Subject: [PATCH 098/103] :recycle: remove unnecessary `NotImplementedError` --- client/ayon_core/addon/interfaces.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/client/ayon_core/addon/interfaces.py b/client/ayon_core/addon/interfaces.py index bf844e2d8b..303cdf3941 100644 --- a/client/ayon_core/addon/interfaces.py +++ b/client/ayon_core/addon/interfaces.py @@ -169,18 +169,14 @@ class ITrayAddon(AYONInterface): prepared """ - raise NotImplementedError @abstractmethod def tray_menu(self, tray_menu: QtWidgets.QMenu) -> None: """Add addon's action to tray menu.""" - raise NotImplementedError - @abstractmethod def tray_start(self) -> None: """Start procedure in tray tool.""" - raise NotImplementedError @abstractmethod def tray_exit(self) -> None: @@ -189,7 +185,6 @@ class ITrayAddon(AYONInterface): This is place where all threads should be shut. """ - raise NotImplementedError def execute_in_main_thread(self, callback: Callable) -> None: """Pushes callback to the queue or process 'callback' on a main thread. @@ -282,12 +277,10 @@ class ITrayAction(ITrayAddon): @abstractmethod def label(self) -> str: """Service label showed in menu.""" - raise NotImplementedError @abstractmethod def on_action_trigger(self) -> None: """What happens on actions click.""" - raise NotImplementedError def tray_menu(self, tray_menu: QtWidgets.QMenu) -> None: """Add action to tray menu.""" @@ -326,7 +319,6 @@ class ITrayService(ITrayAddon): @abstractmethod def label(self) -> str: """Service label showed in menu.""" - raise NotImplementedError # TODO (Illicit): be able to get any sort of information to show/print # @abstractmethod @@ -448,4 +440,3 @@ class ITraits(AYONInterface): list[Type[TraitBase]]: Traits for the addon. """ - raise NotImplementedError From 5e5a27c7a953205c821d406d67a660dbdc7ddd8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 5 Feb 2025 16:59:11 +0100 Subject: [PATCH 099/103] :recycle: make fields optional --- client/ayon_core/pipeline/traits/meta.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/pipeline/traits/meta.py b/client/ayon_core/pipeline/traits/meta.py index 00b5013f72..2f5d3eb212 100644 --- a/client/ayon_core/pipeline/traits/meta.py +++ b/client/ayon_core/pipeline/traits/meta.py @@ -1,5 +1,7 @@ """Metadata traits.""" -from typing import ClassVar, List +from __future__ import annotations + +from typing import ClassVar, List, Optional from pydantic import Field @@ -102,15 +104,26 @@ class KeepOriginalName(TraitBase): class SourceApplication(TraitBase): - """Metadata about the source (producing) application.""" + """Metadata about the source (producing) application. + + This can be useful in cases, where this information is + needed but it cannot be determined from other means - like + .txt files used for various motion tracking applications that + must be interpreted by the loader. + + Note that this is not really connected to any logic in + ayon-applications addon. + + """ name: ClassVar[str] = "SourceApplication" description: ClassVar[str] = "Source Application Trait Model" id: ClassVar[str] = "ayon.meta.SourceApplication.v1" application: str = Field(..., title="Application Name") - variant: str = Field(..., title="Application Variant (e.g. Pro)") - version: str = Field(..., title="Application Version") - platform: str = Field(..., title="Platform Name") + variant: Optional[str] = Field(None, title="Application Variant (e.g. Pro)") + version: Optional[str] = Field(None, title="Application Version") + platform: Optional[str] = Field(None, title="Platform Name (e.g. Windows)") + host_name: Optional[str] = Field(None, title="AYON host Name if applicable") class IntendedUse(TraitBase): From 67bf458d1c92842062c97f65ee717ab7bea5ce15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 5 Feb 2025 16:59:29 +0100 Subject: [PATCH 100/103] :dog: style changes --- client/ayon_core/pipeline/traits/temporal.py | 13 ++++++++----- client/ayon_core/pipeline/traits/trait.py | 3 ++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/pipeline/traits/temporal.py b/client/ayon_core/pipeline/traits/temporal.py index f924a8d1e1..d50b9e8eb8 100644 --- a/client/ayon_core/pipeline/traits/temporal.py +++ b/client/ayon_core/pipeline/traits/temporal.py @@ -29,11 +29,13 @@ class GapPolicy(Enum): hold (int): Gaps are interpreted as hold frames (last existing frames). black (int): Gaps are interpreted as black frames. """ + forbidden = auto() missing = auto() hold = auto() black = auto() + class FrameRanged(TraitBase): """Frame ranged trait model. @@ -63,8 +65,8 @@ class FrameRanged(TraitBase): frame_out (int): Frame out. frames_per_second (str): Frames per second. step (int): Step. - """ + name: ClassVar[str] = "FrameRanged" description: ClassVar[str] = "Frame Ranged Trait" id: ClassVar[str] = "ayon.time.FrameRanged.v1" @@ -91,8 +93,8 @@ class Handles(TraitBase): inclusive (bool): Handles are inclusive. frame_start_handle (int): Frame start handle. frame_end_handle (int): Frame end handle. - """ + name: ClassVar[str] = "Handles" description: ClassVar[str] = "Handles Trait" id: ClassVar[str] = "ayon.time.Handles.v1" @@ -103,6 +105,7 @@ class Handles(TraitBase): frame_end_handle: Optional[int] = Field( 0, title="Frame End Handle") + class Sequence(TraitBase): """Sequence trait model. @@ -122,8 +125,8 @@ class Sequence(TraitBase): named group. frame_spec (str): Frame list specification of frames. This takes string like "1-10,20-30,40-50" etc. - """ + name: ClassVar[str] = "Sequence" description: ClassVar[str] = "Sequence Trait Model" id: ClassVar[str] = "ayon.time.Sequence.v1" @@ -206,7 +209,6 @@ class Sequence(TraitBase): self.validate_frame_padding(file_locs) - def validate_frame_list( # noqa: C901 self, file_locations: FileLocations, @@ -330,7 +332,6 @@ class Sequence(TraitBase): frames.extend(range(int(start), int(end) + 1)) return frames - @staticmethod def _get_collection( file_locations: FileLocations, @@ -410,6 +411,7 @@ class Sequence(TraitBase): # Do we need one for drop and non-drop frame? class SMPTETimecode(TraitBase): """SMPTE Timecode trait model.""" + name: ClassVar[str] = "Timecode" description: ClassVar[str] = "SMPTE Timecode Trait" id: ClassVar[str] = "ayon.time.SMPTETimecode.v1" @@ -421,6 +423,7 @@ class Static(TraitBase): Used to define static time (single frame). """ + name: ClassVar[str] = "Static" description: ClassVar[str] = "Static Time Trait" id: ClassVar[str] = "ayon.time.Static.v1" diff --git a/client/ayon_core/pipeline/traits/trait.py b/client/ayon_core/pipeline/traits/trait.py index 3997451f44..695ebb54c8 100644 --- a/client/ayon_core/pipeline/traits/trait.py +++ b/client/ayon_core/pipeline/traits/trait.py @@ -27,7 +27,6 @@ class TraitBase(ABC, BaseModel): It is using Pydantic BaseModel for serialization and validation. ``id``, ``name``, and ``description`` are abstract attributes that must be implemented in the derived classes. - """ model_config = ConfigDict( @@ -118,6 +117,7 @@ class UpgradableTraitError(Generic[T], Exception): trait: T old_data: dict + class LooseMatchingTraitError(Generic[T], Exception): """Loose matching trait exception. @@ -128,6 +128,7 @@ class LooseMatchingTraitError(Generic[T], Exception): found_trait: T expected_id: str + class TraitValidationError(Exception): """Trait validation error exception. From bdb0a10890df2c684dcbff8657d8db44f8f76ddb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 5 Feb 2025 16:59:51 +0100 Subject: [PATCH 101/103] :recycle: improve comment and error handling --- client/ayon_core/pipeline/traits/utils.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/traits/utils.py b/client/ayon_core/pipeline/traits/utils.py index 2aa6173464..54cc99a261 100644 --- a/client/ayon_core/pipeline/traits/utils.py +++ b/client/ayon_core/pipeline/traits/utils.py @@ -15,6 +15,8 @@ def get_sequence_from_files(paths: list[Path]) -> FrameRanged: """Get original frame range from files. Note that this cannot guess frame rate, so it's set to 25. + This will also fail on paths that cannot be assembled into + one collection without any reminders. Args: paths (list[Path]): List of file paths. @@ -23,7 +25,15 @@ def get_sequence_from_files(paths: list[Path]) -> FrameRanged: FrameRanged: FrameRanged trait. """ - col = assemble([path.as_posix() for path in paths])[0][0] + cols, rems = assemble([path.as_posix() for path in paths]) + if rems: + msg = "Cannot assemble paths into one collection" + raise ValueError(msg) + if len(cols) != 1: + msg = "More than one collection found" + raise ValueError(msg) + col = cols[0] + sorted_frames = sorted(col.indexes) # First frame used for end value first_frame = sorted_frames[0] From 681301bf8cec23dd9797464ed86d02f21b6c2f63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 5 Feb 2025 20:28:31 +0100 Subject: [PATCH 102/103] :dog: fix line length --- client/ayon_core/pipeline/traits/meta.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/pipeline/traits/meta.py b/client/ayon_core/pipeline/traits/meta.py index 2f5d3eb212..e21f3eb7fd 100644 --- a/client/ayon_core/pipeline/traits/meta.py +++ b/client/ayon_core/pipeline/traits/meta.py @@ -120,10 +120,14 @@ class SourceApplication(TraitBase): description: ClassVar[str] = "Source Application Trait Model" id: ClassVar[str] = "ayon.meta.SourceApplication.v1" application: str = Field(..., title="Application Name") - variant: Optional[str] = Field(None, title="Application Variant (e.g. Pro)") - version: Optional[str] = Field(None, title="Application Version") - platform: Optional[str] = Field(None, title="Platform Name (e.g. Windows)") - host_name: Optional[str] = Field(None, title="AYON host Name if applicable") + variant: Optional[str] = Field( + None, title="Application Variant (e.g. Pro)") + version: Optional[str] = Field( + None, title="Application Version") + platform: Optional[str] = Field( + None, title="Platform Name (e.g. Windows)") + host_name: Optional[str] = Field( + None, title="AYON host Name if applicable") class IntendedUse(TraitBase): From ece0631c680891800a98afd391b1b9f129af3721 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 6 Feb 2025 14:29:05 +0100 Subject: [PATCH 103/103] :dog: update ruff action --- .github/workflows/pr_linting.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr_linting.yml b/.github/workflows/pr_linting.yml index 3d2431b69a..896d5b7f4d 100644 --- a/.github/workflows/pr_linting.yml +++ b/.github/workflows/pr_linting.yml @@ -21,4 +21,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: chartboost/ruff-action@v1 + - uses: astral-sh/ruff-action@v1 + with: + changed-files: "true"