From 0e1ac30e2c06e0a40f96ba15b2fb98761ff011e4 Mon Sep 17 00:00:00 2001 From: vade Date: Tue, 1 Jul 2025 18:46:31 -0400 Subject: [PATCH 01/67] First step to marker support - some test data borrowed from OTIO Core project Signed-off-by: vade --- .../Assets/premiere_example.otio | 2369 +++++++++++++++++ 1 file changed, 2369 insertions(+) create mode 100644 Tests/OpenTimelineIO-AVFoundationTests/Assets/premiere_example.otio diff --git a/Tests/OpenTimelineIO-AVFoundationTests/Assets/premiere_example.otio b/Tests/OpenTimelineIO-AVFoundationTests/Assets/premiere_example.otio new file mode 100644 index 0000000..17f96ee --- /dev/null +++ b/Tests/OpenTimelineIO-AVFoundationTests/Assets/premiere_example.otio @@ -0,0 +1,2369 @@ +{ + "OTIO_SCHEMA": "Timeline.1", + "metadata": { + "fcp_xml": { + "@MZ.EditLine": "0", + "@MZ.Sequence.AudioTimeDisplayFormat": "200", + "@MZ.Sequence.EditingModeGUID": "9678af98-a7b7-4bdb-b477-7ac9c8df4a4e", + "@MZ.Sequence.PreviewFrameSizeHeight": "720", + "@MZ.Sequence.PreviewFrameSizeWidth": "1280", + "@MZ.Sequence.PreviewRenderingClassID": "1297106761", + "@MZ.Sequence.PreviewRenderingPresetCodec": "1297107278", + "@MZ.Sequence.PreviewRenderingPresetPath": "EncoderPresets\\SequencePreview\\9678af98-a7b7-4bdb-b477-7ac9c8df4a4e\\I-Frame Only MPEG.epr", + "@MZ.Sequence.PreviewUseMaxBitDepth": "false", + "@MZ.Sequence.PreviewUseMaxRenderQuality": "false", + "@MZ.Sequence.VideoTimeDisplayFormat": "104", + "@MZ.WorkInPoint": "0", + "@MZ.WorkOutPoint": "10550131200000", + "@Monitor.ProgramZoomIn": "0", + "@Monitor.ProgramZoomOut": "10550131200000", + "@TL.SQAVDividerPosition": "0.5", + "@TL.SQAudioVisibleBase": "0", + "@TL.SQHeaderWidth": "184", + "@TL.SQHideShyTracks": "0", + "@TL.SQTimePerPixel": "0.033806825568230996", + "@TL.SQVideoVisibleBase": "0", + "@TL.SQVisibleBaseTime": "0", + "@explodedTracks": "true", + "@id": "sequence-1", + "labels": { + "label2": "Forest" + }, + "media": { + "audio": { + "format": { + "samplecharacteristics": { + "depth": "16", + "samplerate": "48000" + } + }, + "numOutputChannels": "2", + "outputs": { + "group": [ + { + "channel": { + "index": "1" + }, + "downmix": "0", + "index": "1", + "numchannels": "1" + }, + { + "channel": { + "index": "2" + }, + "downmix": "0", + "index": "2", + "numchannels": "1" + } + ] + } + }, + "video": { + "format": { + "samplecharacteristics": { + "anamorphic": "FALSE", + "codec": { + "appspecificdata": { + "appmanufacturer": "Apple Inc.", + "appname": "Final Cut Pro", + "appversion": "7.0", + "data": { + "qtcodec": { + "codecname": "Apple ProRes 422", + "codectypecode": "apcn", + "codectypename": "Apple ProRes 422", + "codecvendorcode": "appl", + "datarate": "0", + "keyframerate": "0", + "spatialquality": "1024", + "temporalquality": "0" + } + } + }, + "name": "Apple ProRes 422" + }, + "colordepth": "24", + "fielddominance": "none", + "height": "720", + "pixelaspectratio": "square", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "width": "1280" + } + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "NDF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + } + }, + "uuid": "5ea30a6b-552f-4722-be92-6dfdb66c97e6" + } + }, + "name": "sc01_sh010_layerA", + "global_start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + }, + "tracks": { + "OTIO_SCHEMA": "Stack.1", + "metadata": {}, + "name": "sc01_sh010_layerA", + "source_range": null, + "effects": [], + "markers": [ + { + "OTIO_SCHEMA": "Marker.2", + "metadata": { + "fcp_xml": { + "comment": "so, this happened" + } + }, + "name": "My MArker 1", + "color": "RED", + "marked_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 113.0 + } + } + }, + { + "OTIO_SCHEMA": "Marker.2", + "metadata": { + "fcp_xml": { + "comment": "fsfsfs" + } + }, + "name": "dsf", + "color": "RED", + "marked_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 492.0 + } + } + }, + { + "OTIO_SCHEMA": "Marker.2", + "metadata": { + "fcp_xml": { + "comment": null + } + }, + "name": "", + "color": "RED", + "marked_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 298.0 + } + } + } + ], + "enabled": true, + "children": [ + { + "OTIO_SCHEMA": "Track.1", + "metadata": { + "fcp_xml": { + "@MZ.TrackTargeted": "1", + "@TL.SQTrackExpanded": "0", + "@TL.SQTrackExpandedHeight": "25", + "@TL.SQTrackShy": "0", + "enabled": "TRUE", + "locked": "FALSE" + } + }, + "name": "", + "source_range": null, + "effects": [], + "markers": [], + "enabled": true, + "children": [ + { + "OTIO_SCHEMA": "Gap.1", + "metadata": {}, + "name": "", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 536.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true + }, + { + "OTIO_SCHEMA": "Clip.2", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-1", + "alphatype": "none", + "anamorphic": "FALSE", + "enabled": "TRUE", + "labels": { + "label2": "Iris" + }, + "link": [ + { + "clipindex": "1", + "linkclipref": "clipitem-1", + "mediatype": "video", + "trackindex": "1" + }, + { + "clipindex": "2", + "groupindex": "1", + "linkclipref": "clipitem-14", + "mediatype": "audio", + "trackindex": "1" + }, + { + "clipindex": "2", + "groupindex": "1", + "linkclipref": "clipitem-16", + "mediatype": "audio", + "trackindex": "2" + } + ], + "logginginfo": { + "description": null, + "lognote": null, + "scene": null, + "shottake": null + }, + "masterclipid": "masterclip-1", + "pixelaspectratio": "square", + "pproTicksIn": "0", + "pproTicksOut": "846720000000" + }, + "my_hook_function_was_here": true + }, + "name": "sc01_sh010_anim.mov", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 15.0, + "value": 50.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 15.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ExternalReference.1", + "metadata": { + "fcp_xml": { + "@id": "file-1", + "media": { + "audio": { + "channelcount": "2", + "samplecharacteristics": { + "depth": "16", + "samplerate": "48000" + } + }, + "video": { + "samplecharacteristics": { + "anamorphic": "FALSE", + "fielddominance": "none", + "height": "720", + "pixelaspectratio": "square", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "width": "1280" + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "NDF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "reel": { + "name": null + } + } + } + }, + "name": "sc01_sh010_anim.mov", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 100.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "available_image_bounds": null, + "target_url": "file://localhost/D%3a/media/sc01_sh010_anim.mov" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" + } + ], + "kind": "Video" + }, + { + "OTIO_SCHEMA": "Track.1", + "metadata": { + "fcp_xml": { + "@MZ.TrackTargeted": "0", + "@TL.SQTrackExpanded": "0", + "@TL.SQTrackExpandedHeight": "25", + "@TL.SQTrackShy": "0", + "enabled": "TRUE", + "locked": "FALSE" + } + }, + "name": "", + "source_range": null, + "effects": [], + "markers": [], + "enabled": true, + "children": [ + { + "OTIO_SCHEMA": "Gap.1", + "metadata": {}, + "name": "", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 13.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true + }, + { + "OTIO_SCHEMA": "Clip.2", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-2", + "alphatype": "none", + "enabled": "TRUE", + "labels": { + "label2": "Iris" + }, + "link": [ + { + "clipindex": "1", + "linkclipref": "clipitem-2", + "mediatype": "video", + "trackindex": "2" + }, + { + "clipindex": "1", + "groupindex": "1", + "linkclipref": "clipitem-13", + "mediatype": "audio", + "trackindex": "1" + }, + { + "clipindex": "1", + "groupindex": "1", + "linkclipref": "clipitem-15", + "mediatype": "audio", + "trackindex": "2" + } + ], + "logginginfo": { + "description": null, + "lognote": null, + "scene": null, + "shottake": null + }, + "masterclipid": "masterclip-1", + "pproTicksIn": "0", + "pproTicksOut": "846720000000" + }, + "my_hook_function_was_here": true + }, + "name": "sc01_sh010_anim.mov", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 100.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ExternalReference.1", + "metadata": { + "fcp_xml": { + "@id": "file-1", + "media": { + "audio": { + "channelcount": "2", + "samplecharacteristics": { + "depth": "16", + "samplerate": "48000" + } + }, + "video": { + "samplecharacteristics": { + "anamorphic": "FALSE", + "fielddominance": "none", + "height": "720", + "pixelaspectratio": "square", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "width": "1280" + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "NDF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "reel": { + "name": null + } + } + } + }, + "name": "sc01_sh010_anim.mov", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 100.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "available_image_bounds": null, + "target_url": "file://localhost/D%3a/media/sc01_sh010_anim.mov" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" + }, + { + "OTIO_SCHEMA": "Gap.1", + "metadata": {}, + "name": "", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 52.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true + }, + { + "OTIO_SCHEMA": "Clip.2", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-3", + "alphatype": "none", + "anamorphic": "FALSE", + "enabled": "TRUE", + "labels": { + "label2": "Iris" + }, + "logginginfo": { + "description": null, + "lognote": null, + "scene": null, + "shottake": null + }, + "masterclipid": "masterclip-2", + "pixelaspectratio": "square", + "pproTicksIn": "0", + "pproTicksOut": "1329350400000" + }, + "my_hook_function_was_here": true + }, + "name": "sc01_sh020_anim.mov", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 157.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ExternalReference.1", + "metadata": { + "fcp_xml": { + "@id": "file-2", + "media": { + "audio": { + "channelcount": "2", + "samplecharacteristics": { + "depth": "16", + "samplerate": "48000" + } + }, + "video": { + "samplecharacteristics": { + "anamorphic": "FALSE", + "fielddominance": "none", + "height": "720", + "pixelaspectratio": "square", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "width": "1280" + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "NDF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "reel": { + "name": null + } + } + } + }, + "name": "sc01_sh020_anim.mov", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 175.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "available_image_bounds": null, + "target_url": "file://localhost/D%3a/media/sc01_sh020_anim.mov" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" + }, + { + "OTIO_SCHEMA": "Clip.2", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-4", + "alphatype": "none", + "anamorphic": "FALSE", + "enabled": "TRUE", + "labels": { + "label2": "Iris" + }, + "logginginfo": { + "description": null, + "lognote": null, + "scene": null, + "shottake": null + }, + "masterclipid": "masterclip-3", + "pixelaspectratio": "square", + "pproTicksIn": "0", + "pproTicksOut": "1989792000000" + }, + "my_hook_function_was_here": true + }, + "name": "sc01_sh030_anim.mov", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 235.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [ + { + "OTIO_SCHEMA": "Marker.2", + "metadata": { + "fcp_xml": { + "comment": null + } + }, + "name": "", + "color": "RED", + "marked_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 73.0 + } + } + } + ], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ExternalReference.1", + "metadata": { + "fcp_xml": { + "@id": "file-3", + "media": { + "audio": { + "channelcount": "2", + "samplecharacteristics": { + "depth": "16", + "samplerate": "48000" + } + }, + "video": { + "samplecharacteristics": { + "anamorphic": "FALSE", + "fielddominance": "none", + "height": "720", + "pixelaspectratio": "square", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "width": "1280" + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "NDF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "reel": { + "name": null + } + } + } + }, + "name": "sc01_sh030_anim.mov", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 400.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "available_image_bounds": null, + "target_url": "file://localhost/D%3a/media/sc01_sh030_anim.mov" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" + }, + { + "OTIO_SCHEMA": "Transition.1", + "metadata": { + "fcp_xml": { + "alignment": "end-black", + "cutPointTicks": "160876800000" + } + }, + "name": "Cross Dissolve", + "in_offset": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 19.0 + }, + "out_offset": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + }, + "transition_type": "SMPTE_Dissolve" + }, + { + "OTIO_SCHEMA": "Gap.1", + "metadata": {}, + "name": "", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 79.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true + }, + { + "OTIO_SCHEMA": "Stack.1", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-5", + "enabled": "TRUE", + "labels": { + "label2": "Forest" + }, + "masterclipid": "masterclip-4", + "pproTicksIn": "0", + "pproTicksOut": "2709504000000" + } + }, + "name": "sc01_sh010_anim", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 320.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "children": [] + } + ], + "kind": "Video" + }, + { + "OTIO_SCHEMA": "Track.1", + "metadata": { + "fcp_xml": { + "@MZ.TrackTargeted": "0", + "@TL.SQTrackExpanded": "0", + "@TL.SQTrackExpandedHeight": "25", + "@TL.SQTrackShy": "0", + "enabled": "TRUE", + "locked": "FALSE" + } + }, + "name": "", + "source_range": null, + "effects": [], + "markers": [], + "enabled": true, + "children": [ + { + "OTIO_SCHEMA": "Gap.1", + "metadata": {}, + "name": "", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 15.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true + }, + { + "OTIO_SCHEMA": "Clip.2", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-10", + "alphatype": "straight", + "anamorphic": "FALSE", + "enabled": "TRUE", + "labels": { + "label2": "Lavender" + }, + "logginginfo": { + "description": null, + "lognote": null, + "scene": null, + "shottake": null + }, + "masterclipid": "masterclip-5", + "pixelaspectratio": "square", + "pproTicksIn": "914457600000000", + "pproTicksOut": "922425235200000" + }, + "my_hook_function_was_here": true + }, + "name": "test_title", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 941.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 108000.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "GeneratorReference.1", + "metadata": { + "fcp_xml": { + "@id": "file-4", + "media": { + "video": { + "samplecharacteristics": { + "anamorphic": "FALSE", + "fielddominance": "none", + "height": "720", + "pixelaspectratio": "square", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "width": "1280" + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "DF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "reel": { + "name": null + } + } + } + }, + "name": "test_title", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "available_image_bounds": null, + "generator_kind": "Slug", + "parameters": {} + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" + } + ], + "kind": "Video" + }, + { + "OTIO_SCHEMA": "Track.1", + "metadata": { + "fcp_xml": { + "@MZ.TrackTargeted": "0", + "@TL.SQTrackExpanded": "0", + "@TL.SQTrackExpandedHeight": "25", + "@TL.SQTrackShy": "0", + "enabled": "TRUE", + "locked": "FALSE" + } + }, + "name": "", + "source_range": null, + "effects": [], + "markers": [], + "enabled": true, + "children": [ + { + "OTIO_SCHEMA": "Gap.1", + "metadata": {}, + "name": "", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 956.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true + }, + { + "OTIO_SCHEMA": "Clip.2", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-11", + "alphatype": "none", + "anamorphic": "FALSE", + "enabled": "TRUE", + "labels": { + "label2": "Iris" + }, + "link": [ + { + "clipindex": "1", + "linkclipref": "clipitem-11", + "mediatype": "video", + "trackindex": "4" + }, + { + "clipindex": "1", + "groupindex": "1", + "linkclipref": "clipitem-23", + "mediatype": "audio", + "trackindex": "7" + }, + { + "clipindex": "1", + "groupindex": "1", + "linkclipref": "clipitem-25", + "mediatype": "audio", + "trackindex": "8" + } + ], + "logginginfo": { + "description": null, + "lognote": null, + "scene": null, + "shottake": null + }, + "masterclipid": "masterclip-6", + "pixelaspectratio": "square", + "pproTicksIn": "287884800000", + "pproTicksOut": "2159136000000" + }, + "my_hook_function_was_here": true + }, + "name": "sc01_master_layerA_sh030_temp.mov", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 208.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 133.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ExternalReference.1", + "metadata": { + "fcp_xml": { + "@id": "file-5", + "media": { + "audio": { + "channelcount": "2", + "samplecharacteristics": { + "depth": "16", + "samplerate": "48000" + } + }, + "video": { + "samplecharacteristics": { + "anamorphic": "FALSE", + "fielddominance": "none", + "height": "720", + "pixelaspectratio": "square", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "width": "1280" + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "NDF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "reel": { + "name": null + } + } + } + }, + "name": "sc01_master_layerA_sh030_temp.mov", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 400.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 99.0 + } + }, + "available_image_bounds": null, + "target_url": "file://localhost/D%3a/media/sc01_master_layerA_sh030_temp.mov" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" + }, + { + "OTIO_SCHEMA": "Transition.1", + "metadata": { + "fcp_xml": { + "alignment": "center", + "cutPointTicks": "101606400000" + } + }, + "name": "Cross Dissolve", + "in_offset": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 12.0 + }, + "out_offset": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 13.0 + }, + "transition_type": "SMPTE_Dissolve" + }, + { + "OTIO_SCHEMA": "Clip.2", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-12", + "alphatype": "none", + "enabled": "TRUE", + "labels": { + "label2": "Iris" + }, + "link": [ + { + "clipindex": "3", + "linkclipref": "clipitem-12", + "mediatype": "video", + "trackindex": "4" + }, + { + "clipindex": "2", + "groupindex": "1", + "linkclipref": "clipitem-24", + "mediatype": "audio", + "trackindex": "7" + }, + { + "clipindex": "2", + "groupindex": "1", + "linkclipref": "clipitem-26", + "mediatype": "audio", + "trackindex": "8" + } + ], + "logginginfo": { + "description": null, + "lognote": null, + "scene": null, + "shottake": null + }, + "masterclipid": "masterclip-1", + "pproTicksIn": "50803200000", + "pproTicksOut": "846720000000" + }, + "my_hook_function_was_here": true + }, + "name": "sc01_sh010_anim.mov", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 82.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 18.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ExternalReference.1", + "metadata": { + "fcp_xml": { + "@id": "file-1", + "media": { + "audio": { + "channelcount": "2", + "samplecharacteristics": { + "depth": "16", + "samplerate": "48000" + } + }, + "video": { + "samplecharacteristics": { + "anamorphic": "FALSE", + "fielddominance": "none", + "height": "720", + "pixelaspectratio": "square", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "width": "1280" + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "NDF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "reel": { + "name": null + } + } + } + }, + "name": "sc01_sh010_anim.mov", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 100.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "available_image_bounds": null, + "target_url": "file://localhost/D%3a/media/sc01_sh010_anim.mov" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" + } + ], + "kind": "Video" + }, + { + "OTIO_SCHEMA": "Track.1", + "metadata": { + "fcp_xml": { + "@MZ.TrackTargeted": "1", + "@PannerCurrentValue": "0.5", + "@PannerIsInverted": "true", + "@PannerName": "Balance", + "@PannerStartKeyframe": "-91445760000000000,0.5,0,0,0,0,0,0", + "@TL.SQTrackAudioKeyframeStyle": "0", + "@TL.SQTrackExpanded": "0", + "@TL.SQTrackExpandedHeight": "25", + "@TL.SQTrackShy": "0", + "@currentExplodedTrackIndex": "0", + "@premiereTrackType": "Stereo", + "@totalExplodedTrackCount": "2", + "enabled": "TRUE", + "locked": "FALSE", + "outputchannelindex": "1" + } + }, + "name": "", + "source_range": null, + "effects": [], + "markers": [], + "enabled": true, + "children": [ + { + "OTIO_SCHEMA": "Gap.1", + "metadata": {}, + "name": "", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 13.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true + }, + { + "OTIO_SCHEMA": "Clip.2", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-13", + "@premiereChannelType": "stereo", + "enabled": "TRUE", + "labels": { + "label2": "Iris" + }, + "link": [ + { + "clipindex": "1", + "linkclipref": "clipitem-2", + "mediatype": "video", + "trackindex": "2" + }, + { + "clipindex": "1", + "groupindex": "1", + "linkclipref": "clipitem-13", + "mediatype": "audio", + "trackindex": "1" + }, + { + "clipindex": "1", + "groupindex": "1", + "linkclipref": "clipitem-15", + "mediatype": "audio", + "trackindex": "2" + } + ], + "logginginfo": { + "description": null, + "lognote": null, + "scene": null, + "shottake": null + }, + "masterclipid": "masterclip-1", + "pproTicksIn": "0", + "pproTicksOut": "846720000000", + "sourcetrack": { + "mediatype": "audio", + "trackindex": "1" + } + }, + "my_hook_function_was_here": true + }, + "name": "sc01_sh010_anim.mov", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 100.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ExternalReference.1", + "metadata": { + "fcp_xml": { + "@id": "file-1", + "media": { + "audio": { + "channelcount": "2", + "samplecharacteristics": { + "depth": "16", + "samplerate": "48000" + } + }, + "video": { + "samplecharacteristics": { + "anamorphic": "FALSE", + "fielddominance": "none", + "height": "720", + "pixelaspectratio": "square", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "width": "1280" + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "NDF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "reel": { + "name": null + } + } + } + }, + "name": "sc01_sh010_anim.mov", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 100.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "available_image_bounds": null, + "target_url": "file://localhost/D%3a/media/sc01_sh010_anim.mov" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" + }, + { + "OTIO_SCHEMA": "Gap.1", + "metadata": {}, + "name": "", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 423.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true + }, + { + "OTIO_SCHEMA": "Clip.2", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-14", + "@premiereChannelType": "stereo", + "enabled": "TRUE", + "labels": { + "label2": "Iris" + }, + "link": [ + { + "clipindex": "1", + "linkclipref": "clipitem-1", + "mediatype": "video", + "trackindex": "1" + }, + { + "clipindex": "2", + "groupindex": "1", + "linkclipref": "clipitem-14", + "mediatype": "audio", + "trackindex": "1" + }, + { + "clipindex": "2", + "groupindex": "1", + "linkclipref": "clipitem-16", + "mediatype": "audio", + "trackindex": "2" + } + ], + "logginginfo": { + "description": null, + "lognote": null, + "scene": null, + "shottake": null + }, + "masterclipid": "masterclip-1", + "pproTicksIn": "0", + "pproTicksOut": "846720000000", + "sourcetrack": { + "mediatype": "audio", + "trackindex": "1" + } + }, + "my_hook_function_was_here": true + }, + "name": "sc01_sh010_anim.mov", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 100.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ExternalReference.1", + "metadata": { + "fcp_xml": { + "@id": "file-1", + "media": { + "audio": { + "channelcount": "2", + "samplecharacteristics": { + "depth": "16", + "samplerate": "48000" + } + }, + "video": { + "samplecharacteristics": { + "anamorphic": "FALSE", + "fielddominance": "none", + "height": "720", + "pixelaspectratio": "square", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "width": "1280" + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "NDF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "reel": { + "name": null + } + } + } + }, + "name": "sc01_sh010_anim.mov", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 100.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "available_image_bounds": null, + "target_url": "file://localhost/D%3a/media/sc01_sh010_anim.mov" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" + } + ], + "kind": "Audio" + }, + { + "OTIO_SCHEMA": "Track.1", + "metadata": { + "fcp_xml": { + "@MZ.TrackTargeted": "1", + "@PannerCurrentValue": "0.5", + "@PannerIsInverted": "true", + "@PannerName": "Balance", + "@PannerStartKeyframe": "-91445760000000000,0.5,0,0,0,0,0,0", + "@TL.SQTrackAudioKeyframeStyle": "0", + "@TL.SQTrackExpanded": "0", + "@TL.SQTrackExpandedHeight": "25", + "@TL.SQTrackShy": "0", + "@currentExplodedTrackIndex": "0", + "@premiereTrackType": "Stereo", + "@totalExplodedTrackCount": "2", + "enabled": "TRUE", + "locked": "FALSE", + "outputchannelindex": "1" + } + }, + "name": "", + "source_range": null, + "effects": [], + "markers": [], + "enabled": true, + "children": [ + { + "OTIO_SCHEMA": "Gap.1", + "metadata": {}, + "name": "", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 335.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true + }, + { + "OTIO_SCHEMA": "Clip.2", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-17", + "@premiereChannelType": "stereo", + "enabled": "TRUE", + "labels": { + "label2": "Caribbean" + }, + "link": [ + { + "clipindex": "1", + "groupindex": "1", + "linkclipref": "clipitem-17", + "mediatype": "audio", + "trackindex": "3" + }, + { + "clipindex": "1", + "groupindex": "1", + "linkclipref": "clipitem-19", + "mediatype": "audio", + "trackindex": "4" + } + ], + "logginginfo": { + "description": null, + "lognote": null, + "scene": null, + "shottake": null + }, + "masterclipid": "masterclip-7", + "pproTicksIn": "0", + "pproTicksOut": "1439424000000", + "sourcetrack": { + "mediatype": "audio", + "trackindex": "1" + } + }, + "my_hook_function_was_here": true + }, + "name": "sc01_placeholder.wav", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 170.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 8497.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ExternalReference.1", + "metadata": { + "fcp_xml": { + "@id": "file-6", + "media": { + "audio": { + "channelcount": "2", + "samplecharacteristics": { + "depth": "16", + "samplerate": "48000" + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "DF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "reel": { + "name": null + } + } + } + }, + "name": "sc01_placeholder.wav", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 170.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 8497.0 + } + }, + "available_image_bounds": null, + "target_url": "file://localhost/D%3a/media/sc01_placeholder.wav" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" + }, + { + "OTIO_SCHEMA": "Gap.1", + "metadata": {}, + "name": "", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 131.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true + }, + { + "OTIO_SCHEMA": "Stack.1", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-18", + "@premiereChannelType": "stereo", + "enabled": "TRUE", + "labels": { + "label2": "Forest" + }, + "link": [ + { + "clipindex": "2", + "groupindex": "1", + "linkclipref": "clipitem-18", + "mediatype": "audio", + "trackindex": "3" + }, + { + "clipindex": "2", + "groupindex": "1", + "linkclipref": "clipitem-20", + "mediatype": "audio", + "trackindex": "4" + } + ], + "masterclipid": "masterclip-4", + "pproTicksIn": "0", + "pproTicksOut": "2489356800000" + } + }, + "name": "sc01_sh010_anim", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 294.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "children": [] + } + ], + "kind": "Audio" + }, + { + "OTIO_SCHEMA": "Track.1", + "metadata": { + "fcp_xml": { + "@MZ.TrackTargeted": "1", + "@PannerCurrentValue": "0.5", + "@PannerIsInverted": "true", + "@PannerName": "Balance", + "@PannerStartKeyframe": "-91445760000000000,0.5,0,0,0,0,0,0", + "@TL.SQTrackAudioKeyframeStyle": "0", + "@TL.SQTrackExpanded": "0", + "@TL.SQTrackExpandedHeight": "25", + "@TL.SQTrackShy": "0", + "@currentExplodedTrackIndex": "0", + "@premiereTrackType": "Stereo", + "@totalExplodedTrackCount": "2", + "enabled": "TRUE", + "locked": "FALSE", + "outputchannelindex": "1" + } + }, + "name": "", + "source_range": null, + "effects": [], + "markers": [], + "enabled": true, + "children": [ + { + "OTIO_SCHEMA": "Gap.1", + "metadata": {}, + "name": "", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 153.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true + }, + { + "OTIO_SCHEMA": "Clip.2", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-21", + "@premiereChannelType": "stereo", + "enabled": "TRUE", + "labels": { + "label2": "Caribbean" + }, + "link": [ + { + "clipindex": "1", + "groupindex": "1", + "linkclipref": "clipitem-21", + "mediatype": "audio", + "trackindex": "5" + }, + { + "clipindex": "1", + "groupindex": "1", + "linkclipref": "clipitem-22", + "mediatype": "audio", + "trackindex": "6" + } + ], + "logginginfo": { + "description": null, + "lognote": null, + "scene": null, + "shottake": null + }, + "masterclipid": "masterclip-8", + "pproTicksIn": "0", + "pproTicksOut": "1676505600000", + "sourcetrack": { + "mediatype": "audio", + "trackindex": "1" + } + }, + "my_hook_function_was_here": true + }, + "name": "track_08.wav", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 198.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 6896.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ExternalReference.1", + "metadata": { + "fcp_xml": { + "@id": "file-7", + "media": { + "audio": { + "channelcount": "2", + "samplecharacteristics": { + "depth": "16", + "samplerate": "48000" + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "DF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "reel": { + "name": null + } + } + } + }, + "name": "track_08.wav", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 198.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 6896.0 + } + }, + "available_image_bounds": null, + "target_url": "file://localhost/D%3a/media/track_08.wav" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" + } + ], + "kind": "Audio" + }, + { + "OTIO_SCHEMA": "Track.1", + "metadata": { + "fcp_xml": { + "@MZ.TrackTargeted": "0", + "@PannerCurrentValue": "0.5", + "@PannerIsInverted": "true", + "@PannerName": "Balance", + "@PannerStartKeyframe": "-91445760000000000,0.5,0,0,0,0,0,0", + "@TL.SQTrackAudioKeyframeStyle": "0", + "@TL.SQTrackExpanded": "0", + "@TL.SQTrackExpandedHeight": "25", + "@TL.SQTrackShy": "0", + "@currentExplodedTrackIndex": "0", + "@premiereTrackType": "Stereo", + "@totalExplodedTrackCount": "2", + "enabled": "TRUE", + "locked": "FALSE", + "outputchannelindex": "1" + } + }, + "name": "", + "source_range": null, + "effects": [], + "markers": [], + "enabled": true, + "children": [ + { + "OTIO_SCHEMA": "Gap.1", + "metadata": {}, + "name": "", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 956.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true + }, + { + "OTIO_SCHEMA": "Clip.2", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-23", + "@premiereChannelType": "stereo", + "enabled": "TRUE", + "labels": { + "label2": "Iris" + }, + "link": [ + { + "clipindex": "1", + "linkclipref": "clipitem-11", + "mediatype": "video", + "trackindex": "4" + }, + { + "clipindex": "1", + "groupindex": "1", + "linkclipref": "clipitem-23", + "mediatype": "audio", + "trackindex": "7" + }, + { + "clipindex": "1", + "groupindex": "1", + "linkclipref": "clipitem-25", + "mediatype": "audio", + "trackindex": "8" + } + ], + "logginginfo": { + "description": null, + "lognote": null, + "scene": null, + "shottake": null + }, + "masterclipid": "masterclip-6", + "pproTicksIn": "287884800000", + "pproTicksOut": "2049062400000", + "sourcetrack": { + "mediatype": "audio", + "trackindex": "1" + } + }, + "my_hook_function_was_here": true + }, + "name": "sc01_master_layerA_sh030_temp.mov", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 221.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 133.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ExternalReference.1", + "metadata": { + "fcp_xml": { + "@id": "file-5", + "media": { + "audio": { + "channelcount": "2", + "samplecharacteristics": { + "depth": "16", + "samplerate": "48000" + } + }, + "video": { + "samplecharacteristics": { + "anamorphic": "FALSE", + "fielddominance": "none", + "height": "720", + "pixelaspectratio": "square", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "width": "1280" + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "NDF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "reel": { + "name": null + } + } + } + }, + "name": "sc01_master_layerA_sh030_temp.mov", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 400.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 99.0 + } + }, + "available_image_bounds": null, + "target_url": "file://localhost/D%3a/media/sc01_master_layerA_sh030_temp.mov" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" + }, + { + "OTIO_SCHEMA": "Clip.2", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-24", + "@premiereChannelType": "stereo", + "enabled": "TRUE", + "labels": { + "label2": "Iris" + }, + "link": [ + { + "clipindex": "3", + "linkclipref": "clipitem-12", + "mediatype": "video", + "trackindex": "4" + }, + { + "clipindex": "2", + "groupindex": "1", + "linkclipref": "clipitem-24", + "mediatype": "audio", + "trackindex": "7" + }, + { + "clipindex": "2", + "groupindex": "1", + "linkclipref": "clipitem-26", + "mediatype": "audio", + "trackindex": "8" + } + ], + "logginginfo": { + "description": null, + "lognote": null, + "scene": null, + "shottake": null + }, + "masterclipid": "masterclip-1", + "pproTicksIn": "152409600000", + "pproTicksOut": "846720000000", + "sourcetrack": { + "mediatype": "audio", + "trackindex": "1" + } + }, + "my_hook_function_was_here": true + }, + "name": "sc01_sh010_anim.mov", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 94.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 6.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ExternalReference.1", + "metadata": { + "fcp_xml": { + "@id": "file-1", + "media": { + "audio": { + "channelcount": "2", + "samplecharacteristics": { + "depth": "16", + "samplerate": "48000" + } + }, + "video": { + "samplecharacteristics": { + "anamorphic": "FALSE", + "fielddominance": "none", + "height": "720", + "pixelaspectratio": "square", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "width": "1280" + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "NDF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "reel": { + "name": null + } + } + } + }, + "name": "sc01_sh010_anim.mov", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 100.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "available_image_bounds": null, + "target_url": "file://localhost/D%3a/media/sc01_sh010_anim.mov" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" + } + ], + "kind": "Audio" + } + ] + } +} \ No newline at end of file From b954b1b504637700690fcf6f794bf8a2568ee91c Mon Sep 17 00:00:00 2001 From: vade Date: Tue, 1 Jul 2025 19:15:37 -0400 Subject: [PATCH 02/67] Draft of marker view Signed-off-by: vade --- .../Views/ItemView.swift | 8 +++ .../Views/MarkerView.swift | 59 +++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift index 6d1da6f..69eef9e 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift @@ -53,6 +53,7 @@ struct ItemView : View { } } .frame(width: self.getSafeWidth()) + // .offset(x:self.getSafePositionX() )//, y:geometry.size.height * 0.5 ) } @@ -81,4 +82,11 @@ struct ItemView : View { { return self.getSafeRange().startTime.toSeconds() * self.secondsToPixels// + self.getSafeWidth()/2.0 } + +// @ViewBuilder func getMarkerView() -> some View +// { +// ForEach(self.item.markers) { marker in +// +// } +// } } diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift new file mode 100644 index 0000000..b1357f2 --- /dev/null +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift @@ -0,0 +1,59 @@ +// +// MarkerView.swift +// OpenTimelineIO-Reader +// +// Created by Anton Marini on 7/1/25. +// + +import OpenTimelineIO_AVFoundation +import OpenTimelineIO +import TimecodeKit +import SwiftUI + +struct MarkerView : View { + + let marker:OpenTimelineIO.Marker + + @Binding var secondsToPixels:Double + + var body: some View + { + self.colorForMarker() + .frame(width: self.getSafeWidth() ) + .offset(x:self.getSafePositionX() ) + } + + func getSafeWidth() -> CGFloat + { + return max(self.marker.markedRange.duration.toSeconds() * self.secondsToPixels, 3.0) + } + + func getSafePositionX() -> CGFloat + { + return self.marker.markedRange.startTime.toSeconds() * self.secondsToPixels// + self.getSafeWidth()/2.0 + } + + func colorForMarker() -> Color + { + if let color:Marker.Color = Marker.Color(rawValue: marker.color) + { + switch color + { + case .pink : return Color.pink + case .red : return Color.red + case .orange : return Color.orange + case .yellow : return Color.yellow + case .green : return Color.green + case .cyan : return Color.cyan + case .blue : return Color.blue + case .purple : return Color.purple + case .magenta : return Color(red: 1.0, green: 0, blue: 1.0) + case .black : return Color.black + case .white : return Color.white + + } + } + + return Color.red + } +} From 9341ab2143e9528f7e79217dc8614fc64d8f0610 Mon Sep 17 00:00:00 2001 From: vade Date: Tue, 1 Jul 2025 20:21:21 -0400 Subject: [PATCH 03/67] Wire basic marker view up - need to validate placement logic Signed-off-by: vade --- .../Views/ItemView.swift | 24 ++++++++++++------- .../Views/MarkerView.swift | 5 ++-- .../Views/TimeRulerView.swift | 9 +++---- .../Views/TimelineView.swift | 19 ++++++++++++--- .../Views/TrackView.swift | 15 ++++++++++++ 5 files changed, 53 insertions(+), 19 deletions(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift index 69eef9e..635569e 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift @@ -20,7 +20,6 @@ struct ItemView : View { var body: some View { - ZStack { if let _ = item as? Gap @@ -52,8 +51,10 @@ struct ItemView : View { } } } - .frame(width: self.getSafeWidth()) - + .frame(width: self.getSafeWidth(), alignment: .leading ) + .overlay { + self.getMarkerView() + } // .offset(x:self.getSafePositionX() )//, y:geometry.size.height * 0.5 ) } @@ -83,10 +84,15 @@ struct ItemView : View { return self.getSafeRange().startTime.toSeconds() * self.secondsToPixels// + self.getSafeWidth()/2.0 } -// @ViewBuilder func getMarkerView() -> some View -// { -// ForEach(self.item.markers) { marker in -// -// } -// } + @ViewBuilder func getMarkerView() -> some View + { + HStack(alignment: .top) + { + let markerRange = self.item.markers.startIndex ..< self.item.markers.endIndex + ForEach(markerRange, id:\.self) { markerIndex in + MarkerView(marker: self.item.markers[markerIndex], + secondsToPixels: self.$secondsToPixels) + } + } + } } diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift index b1357f2..426af0b 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift @@ -19,13 +19,13 @@ struct MarkerView : View { var body: some View { self.colorForMarker() - .frame(width: self.getSafeWidth() ) + .frame(width: self.getSafeWidth(), height: 7.0, alignment: .leading ) .offset(x:self.getSafePositionX() ) } func getSafeWidth() -> CGFloat { - return max(self.marker.markedRange.duration.toSeconds() * self.secondsToPixels, 3.0) + return max(self.marker.markedRange.duration.toSeconds() * self.secondsToPixels, 5.0) } func getSafePositionX() -> CGFloat @@ -50,7 +50,6 @@ struct MarkerView : View { case .magenta : return Color(red: 1.0, green: 0, blue: 1.0) case .black : return Color.black case .white : return Color.white - } } diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift index e972c36..f0c18f4 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift @@ -34,7 +34,7 @@ struct TimeRulerView: View } func drawTicks(context: GraphicsContext, startSeconds: Double, endSeconds: Double, secondsToPixels: Double, size: CGSize) { - if self.secondsToPixels > 100 + if self.secondsToPixels > 75 { self.drawFrameTicks(context: context, startSeconds: startSeconds, endSeconds: endSeconds, secondsToPixels: secondsToPixels, size: size) } @@ -66,7 +66,7 @@ struct TimeRulerView: View context.fill(Path(tickRect), with: .color(.white)) // Draw label if it's an hour or minute - if self.secondsToPixels > 50 + if self.secondsToPixels > 75 { context.draw(Text(label).font(.system(size: 10)), at: CGPoint(x: positionX + 2, y: size.height - tickHeight - 10)) } @@ -93,9 +93,10 @@ struct TimeRulerView: View let tickRect = CGRect(x: positionX, y: size.height - tickHeight, width: 1, height: tickHeight) context.fill(Path(tickRect), with: .color(.white)) - if self.secondsToPixels > 400 + if self.secondsToPixels > 500 { - context.draw(Text(String(frameNum)).font(.system(size: 10)), at: CGPoint(x: positionX, y: size.height - tickHeight - 5)) + context.draw(Text(String(frameNum)).font(.system(size: 10)), + at: CGPoint(x: positionX, y: size.height - tickHeight - 5)) } } } diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift index 6b9694f..7c6d599 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift @@ -51,17 +51,19 @@ struct TimelineView : View { } } - func timelineView() -> some View + @ViewBuilder func timelineView() -> some View { let videoTracks = timeline.videoTracks let audioTracks = timeline.audioTracks - return + VStack(alignment:.leading, spacing: 3) { TimeRulerView(timeline: self.timeline, secondsToPixels: self.$secondsToPixels, currentTime: self.$currentTime ) .frame(height: 40) .offset(x:100) + .overlay( self.getMarkerView( ) ) + // ForEach(0.. some View + { + HStack(alignment: .top) + { + let markerRange = (self.timeline.tracks?.markers.startIndex ?? 0) ..< (self.timeline.tracks?.markers.endIndex ?? 0) + ForEach(markerRange, id:\.self) { markerIndex in + MarkerView(marker: self.timeline.tracks!.markers[markerIndex], + secondsToPixels: self.$secondsToPixels) + } + } } } diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TrackView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TrackView.swift index 30e2caa..ec29d44 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TrackView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TrackView.swift @@ -45,6 +45,9 @@ struct TrackView : View } } .frame(width: self.getSafeWidth(), alignment: .leading ) + .overlay { + self.getMarkerView() + } // .position(x:self.getSafePositionX(), y:0 ) } @@ -94,4 +97,16 @@ struct TrackView : View { return self.getSafeRange().startTime.toSeconds() * self.secondsToPixels - self.getSafeWidth()/2.0 } + + @ViewBuilder func getMarkerView() -> some View + { + HStack(alignment: .top) + { + let markerRange = self.track.markers.startIndex ..< self.track.markers.endIndex + ForEach(markerRange, id:\.self) { markerIndex in + MarkerView(marker: self.track.markers[markerIndex], + secondsToPixels: self.$secondsToPixels) + } + } + } } From e5b5ccd91617c295f1c0543c10cd0173828dac19 Mon Sep 17 00:00:00 2001 From: vade Date: Wed, 2 Jul 2025 00:47:05 -0400 Subject: [PATCH 04/67] Fix marker position on main stack Signed-off-by: vade --- .../Views/ContentView.swift | 1 + .../Views/ItemView.swift | 3 + .../Views/MarkerView.swift | 2 +- .../Views/TimeRulerView.swift | 17 ++++ .../Views/TimelineView.swift | 83 ++++++++++--------- 5 files changed, 66 insertions(+), 40 deletions(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ContentView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ContentView.swift index 647a297..a882cff 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ContentView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ContentView.swift @@ -65,6 +65,7 @@ struct ContentView: View self.controlsViewStack() } } + } detail: { ItemInspectorView(selectedItem: self.$selectedItem) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift index 635569e..21df9dc 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift @@ -54,6 +54,7 @@ struct ItemView : View { .frame(width: self.getSafeWidth(), alignment: .leading ) .overlay { self.getMarkerView() + } // .offset(x:self.getSafePositionX() )//, y:geometry.size.height * 0.5 ) } @@ -94,5 +95,7 @@ struct ItemView : View { secondsToPixels: self.$secondsToPixels) } } + .frame(width: self.getSafeWidth(), alignment: .leading ) + } } diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift index 426af0b..348d3ef 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift @@ -19,7 +19,7 @@ struct MarkerView : View { var body: some View { self.colorForMarker() - .frame(width: self.getSafeWidth(), height: 7.0, alignment: .leading ) + .frame(width: self.getSafeWidth(), height: 7.0 ) .offset(x:self.getSafePositionX() ) } diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift index f0c18f4..c80d6c5 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift @@ -23,6 +23,23 @@ struct TimeRulerView: View let startSeconds = safeRange.startTime.toSeconds() let endSeconds = safeRange.endTimeInclusive().toSeconds() + if let tracks = self.timeline.tracks + { + let markerRange = tracks.markers.startIndex ..< tracks.markers.endIndex + for markerIndex in markerRange + { + let marker = tracks.markers[markerIndex] + let x = marker.markedRange.startTime.toSeconds() * self.secondsToPixels// + + + let path = Path(roundedRect: CGRect(origin: CGPoint(x: x, y: 0), + size: CGSize(width: 5.0, height: 9.0)), + cornerRadius: 3.0) + + context.fill(path, with: .color(.red)) + } + } + + // Draw playhead drawPlayhead(context: context, currentTime: currentTime, secondsToPixels: secondsToPixels, size: size) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift index 7c6d599..b172d96 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift @@ -55,53 +55,58 @@ struct TimelineView : View { { let videoTracks = timeline.videoTracks let audioTracks = timeline.audioTracks - - VStack(alignment:.leading, spacing: 3) - { + + VStack(alignment:.leading, spacing: 3) + { TimeRulerView(timeline: self.timeline, secondsToPixels: self.$secondsToPixels, currentTime: self.$currentTime ) .frame(height: 40) .offset(x:100) - .overlay( self.getMarkerView( ) ) - - // +// .overlay( +// self.getMarkerView( ) +// ) + // + + ForEach(0.. some View - { - HStack(alignment: .top) - { - let markerRange = (self.timeline.tracks?.markers.startIndex ?? 0) ..< (self.timeline.tracks?.markers.endIndex ?? 0) - ForEach(markerRange, id:\.self) { markerIndex in - MarkerView(marker: self.timeline.tracks!.markers[markerIndex], - secondsToPixels: self.$secondsToPixels) + + Divider() + + ForEach(0.. some View +// { +// HStack(alignment: .top, spacing: 0) +// { +// let markerRange = (self.timeline.tracks?.markers.startIndex ?? 0) ..< (self.timeline.tracks?.markers.endIndex ?? 0) +// ForEach(markerRange, id:\.self) { markerIndex in +// MarkerView(marker: self.timeline.tracks!.markers[markerIndex], +// secondsToPixels: self.$secondsToPixels) +// } +// } +//// .frame(width: self.getSafeWidth(), alignment: .leading ) +// .frame(alignment: .leading) +// .border(Color.purple, width: 2) +// } } From 5e1907cb0ae1cae916633b266010699fbfea9edf Mon Sep 17 00:00:00 2001 From: vade Date: Wed, 2 Jul 2025 01:08:44 -0400 Subject: [PATCH 05/67] Better marker drawing Signed-off-by: vade --- .../Views/TimeRulerView.swift | 74 ++++++++++++++----- 1 file changed, 56 insertions(+), 18 deletions(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift index c80d6c5..f7c3083 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift @@ -23,32 +23,70 @@ struct TimeRulerView: View let startSeconds = safeRange.startTime.toSeconds() let endSeconds = safeRange.endTimeInclusive().toSeconds() - if let tracks = self.timeline.tracks - { - let markerRange = tracks.markers.startIndex ..< tracks.markers.endIndex - for markerIndex in markerRange - { - let marker = tracks.markers[markerIndex] - let x = marker.markedRange.startTime.toSeconds() * self.secondsToPixels// + - - let path = Path(roundedRect: CGRect(origin: CGPoint(x: x, y: 0), - size: CGSize(width: 5.0, height: 9.0)), - cornerRadius: 3.0) - - context.fill(path, with: .color(.red)) - } - } + // Draw ticks (including frame-level ticks) + drawTicks(context: context, startSeconds: startSeconds, endSeconds: endSeconds, secondsToPixels: secondsToPixels, size: size) // Draw playhead drawPlayhead(context: context, currentTime: currentTime, secondsToPixels: secondsToPixels, size: size) - - // Draw ticks (including frame-level ticks) - drawTicks(context: context, startSeconds: startSeconds, endSeconds: endSeconds, secondsToPixels: secondsToPixels, size: size) + + drawMarkers(context: context, startSeconds: startSeconds, endSeconds: endSeconds, secondsToPixels: secondsToPixels, size: size) } .frame(width: self.getSafeWidth()) } + + func drawMarkers(context: GraphicsContext, startSeconds: Double, endSeconds: Double, secondsToPixels: Double, size: CGSize) + { + if let tracks = self.timeline.tracks + { + let markerRange = tracks.markers.startIndex ..< tracks.markers.endIndex + for markerIndex in markerRange + { + let marker = tracks.markers[markerIndex] + let x = marker.markedRange.startTime.toSeconds() * self.secondsToPixels// + + +// let path = Path(roundedRect: CGRect(origin: CGPoint(x: x, y: 13), +// size: CGSize(width: 3.0, height: 9.0)), +// cornerRadius: 1.0) +// +// context.fill(path, with: .color(.red)) + + let text = marker.name + + if #available(macOS 14.0, *) + { + context.draw( + Text("\(Image(systemName: "arrowtriangle.down.fill"))") + .font(.system(size: 10)) + .foregroundStyle(.red), + at: CGPoint(x: x + 0.5, y: 21)) + + + context.draw( + Text(text) + .font(.system(size: 10)) + .foregroundStyle(.white), + at: CGPoint(x: x , y: 12)) + } + else + { + context.draw( + Text("\(Image(systemName: "arrowtriangle.down.fill"))") + .font(.system(size: 10)) + .foregroundColor(.red), + at: CGPoint(x: x + 0.5, y: 21)) + + context.draw( + Text(text) + .font(.system(size: 10)) + .foregroundColor(.orange), + at: CGPoint(x: x , y: 12)) + } + } + } + } + func drawTicks(context: GraphicsContext, startSeconds: Double, endSeconds: Double, secondsToPixels: Double, size: CGSize) { if self.secondsToPixels > 75 From feaf3bc146ae51d8a8dbace10b3dfe4294990eb0 Mon Sep 17 00:00:00 2001 From: vade Date: Wed, 2 Jul 2025 01:10:39 -0400 Subject: [PATCH 06/67] Xleanup Signed-off-by: vade --- .../OpenTimelineIO-Reader/Views/TimeRulerView.swift | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift index f7c3083..eff5af2 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift @@ -44,13 +44,7 @@ struct TimeRulerView: View for markerIndex in markerRange { let marker = tracks.markers[markerIndex] - let x = marker.markedRange.startTime.toSeconds() * self.secondsToPixels// + - -// let path = Path(roundedRect: CGRect(origin: CGPoint(x: x, y: 13), -// size: CGSize(width: 3.0, height: 9.0)), -// cornerRadius: 1.0) -// -// context.fill(path, with: .color(.red)) + let x = marker.markedRange.startTime.toSeconds() * self.secondsToPixels let text = marker.name From 5ddfecfab6224cfe085bed5ee145670a75660921 Mon Sep 17 00:00:00 2001 From: vade Date: Wed, 2 Jul 2025 01:27:06 -0400 Subject: [PATCH 07/67] more tweaks Signed-off-by: vade --- .../Views/TimeRulerView.swift | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift index eff5af2..2f03379 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift @@ -38,6 +38,7 @@ struct TimeRulerView: View func drawMarkers(context: GraphicsContext, startSeconds: Double, endSeconds: Double, secondsToPixels: Double, size: CGSize) { + let y = 15.0 if let tracks = self.timeline.tracks { let markerRange = tracks.markers.startIndex ..< tracks.markers.endIndex @@ -54,14 +55,17 @@ struct TimeRulerView: View Text("\(Image(systemName: "arrowtriangle.down.fill"))") .font(.system(size: 10)) .foregroundStyle(.red), - at: CGPoint(x: x + 0.5, y: 21)) + at: CGPoint(x: x + 0.5, y: y), + ) context.draw( Text(text) .font(.system(size: 10)) - .foregroundStyle(.white), - at: CGPoint(x: x , y: 12)) + .foregroundStyle(.red), + at: CGPoint(x: x + 9 , y: y - 2.0), + anchor: UnitPoint(x: 0, y: 0.5) +) } else { @@ -69,14 +73,23 @@ struct TimeRulerView: View Text("\(Image(systemName: "arrowtriangle.down.fill"))") .font(.system(size: 10)) .foregroundColor(.red), - at: CGPoint(x: x + 0.5, y: 21)) + at: CGPoint(x: x + 0.5, y: y), + ) context.draw( Text(text) .font(.system(size: 10)) - .foregroundColor(.orange), - at: CGPoint(x: x , y: 12)) + .foregroundColor(.red), + at: CGPoint(x: x + 9 , y: y - 2.0), + anchor: UnitPoint(x: 0, y: 0.5) + ) } + + let tickHeight = 20.0 + + // Draw tick line + let tickRect = CGRect(x: x, y: size.height - tickHeight, width: 1, height: tickHeight) + context.fill(Path(tickRect), with: .color(.red)) } } } @@ -177,7 +190,7 @@ struct TimeRulerView: View context.draw( Text(currentTimeLabel) - .font(.system(size: 10)) + .font(.system(size: 10, weight: .bold)) .foregroundStyle(.orange), at: CGPoint(x: playheadPositionX, y: 5)) } @@ -191,7 +204,7 @@ struct TimeRulerView: View context.draw( Text(currentTimeLabel) - .font(.system(size: 10)) + .font(.system(size: 10, weight: .bold)) .foregroundColor(.orange), at: CGPoint(x: playheadPositionX, y: 5)) } From fb3f35e65e93b8545c998339b1624479782702ea Mon Sep 17 00:00:00 2001 From: vade Date: Wed, 2 Jul 2025 09:04:55 -0400 Subject: [PATCH 08/67] Nicer marker drawing Signed-off-by: vade --- .../Views/ContentView.swift | 4 +- .../Views/ItemView.swift | 66 ++++++++----------- .../Views/TimeRulerView.swift | 7 +- .../Views/TimelineView.swift | 35 +++------- .../Views/TrackView.swift | 11 ++-- 5 files changed, 48 insertions(+), 75 deletions(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ContentView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ContentView.swift index a882cff..66c887e 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ContentView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ContentView.swift @@ -33,9 +33,7 @@ struct ContentView: View self.fileURL = fileURL guard let fileURL = fileURL else { return } - - - + self.document.setupPlayerWithBaseDocumentURL(fileURL) } diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift index 21df9dc..cdca6fb 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift @@ -13,50 +13,42 @@ import SwiftUI struct ItemView : View { let item:OpenTimelineIO.Item - @State var backgroundColor:Color - @State var selected:Bool + let backgroundColor:Color + let selected:Bool + + private var isGap: Bool { + item.isKind(of: Gap.self) + } @Binding var secondsToPixels:Double var body: some View { - ZStack { - - if let _ = item as? Gap - { - RoundedRectangle(cornerRadius: 3) - .fill(Color("GapTrackBaseColor")) // Fill the RoundedRectangle with color - .overlay( - RoundedRectangle(cornerRadius: 3) - .stroke(self.selected ? .white : .clear, lineWidth: 1) // Add stroke/outline - ) - .frame(width: self.getSafeWidth() - 2) - } - else - { + ZStack + { + let fill:AnyShapeStyle = ( isGap ) ? AnyShapeStyle( Color("GapTrackBaseColor") ) : AnyShapeStyle(self.backgroundColor.gradient) + let textGapOpacity:Double = ( isGap ) ? 0.0 : 1.0 + + RoundedRectangle(cornerRadius: 3) + .fill(fill, style: FillStyle()) + .overlay( RoundedRectangle(cornerRadius: 3) - .fill(self.backgroundColor.gradient) // Fill the RoundedRectangle with color - .overlay( - RoundedRectangle(cornerRadius: 3) - .stroke(self.selected ? .white : .clear, lineWidth: 1) // Add stroke/outline - ) + .strokeBorder(self.selected ? .orange : .clear, lineWidth: 1) // Add stroke/outline .frame(width: self.getSafeWidth() - 2) - - if self.getSafeWidth() > 40 - { - Text(item.name) - .lineLimit(1) - .font(.system(size: 10)) - .frame(width: self.getSafeWidth()) - } - } - } - .frame(width: self.getSafeWidth(), alignment: .leading ) - .overlay { - self.getMarkerView() - - } -// .offset(x:self.getSafePositionX() )//, y:geometry.size.height * 0.5 ) + ) + .frame(width: self.getSafeWidth() - 2) + + Text(item.name) + .lineLimit(1) + .font(.system(size: 10)) + .frame(width: self.getSafeWidth()) + .opacity(self.getSafeWidth() > 40 ? textGapOpacity : 0.0) + } + .frame(width: self.getSafeWidth(), alignment: .leading ) + .overlay { + // TODO: Get item marker coordinate system working +// self.getMarkerView() + } } func getSafeRange() -> OpenTimelineIO.TimeRange diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift index 2f03379..3417277 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift @@ -23,7 +23,6 @@ struct TimeRulerView: View let startSeconds = safeRange.startTime.toSeconds() let endSeconds = safeRange.endTimeInclusive().toSeconds() - // Draw ticks (including frame-level ticks) drawTicks(context: context, startSeconds: startSeconds, endSeconds: endSeconds, secondsToPixels: secondsToPixels, size: size) @@ -38,7 +37,7 @@ struct TimeRulerView: View func drawMarkers(context: GraphicsContext, startSeconds: Double, endSeconds: Double, secondsToPixels: Double, size: CGSize) { - let y = 15.0 + let y = 20.0 if let tracks = self.timeline.tracks { let markerRange = tracks.markers.startIndex ..< tracks.markers.endIndex @@ -85,7 +84,7 @@ struct TimeRulerView: View ) } - let tickHeight = 20.0 + let tickHeight = 24.0 // Draw tick line let tickRect = CGRect(x: x, y: size.height - tickHeight, width: 1, height: tickHeight) @@ -177,7 +176,7 @@ struct TimeRulerView: View } let playheadPositionX = currentTime.toSeconds() * secondsToPixels - let playheadRect = CGRect(x: playheadPositionX, y: 20, width: 1, height: size.height-20) + let playheadRect = CGRect(x: playheadPositionX, y: 22, width: 1, height: size.height - 22) // context.fill(Path(playheadRect), with: .color(.orange)) if #available(macOS 14.0, *) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift index b172d96..d09055c 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift @@ -32,7 +32,7 @@ struct TimelineView : View { { self.timelineView() .allowsHitTesting(self.hitTestEnabled) - .drawingGroup(opaque: true) +// .drawingGroup(opaque: true) } .onScrollPhaseChange({ oldPhase, newPhase, context in @@ -40,6 +40,9 @@ struct TimelineView : View { self.hitTestEnabled = !newPhase.isScrolling }) + .frame(idealHeight: ( CGFloat((timeline.videoTracks.count + timeline.audioTracks.count)) * TrackView.trackHeight) + 100 ) + // .frame(maxHeight: CGFloat((videoTracks.count + audioTracks.count)) * 500) + } else { @@ -55,17 +58,12 @@ struct TimelineView : View { { let videoTracks = timeline.videoTracks let audioTracks = timeline.audioTracks - VStack(alignment:.leading, spacing: 3) { - TimeRulerView(timeline: self.timeline, secondsToPixels: self.$secondsToPixels, currentTime: self.$currentTime ) - .frame(height: 40) + TimeRulerView(timeline: self.timeline, secondsToPixels: self.$secondsToPixels, currentTime: self.$currentTime ) + .frame(height: 50) .offset(x:100) -// .overlay( -// self.getMarkerView( ) -// ) - // ForEach(0.. some View -// { -// HStack(alignment: .top, spacing: 0) -// { -// let markerRange = (self.timeline.tracks?.markers.startIndex ?? 0) ..< (self.timeline.tracks?.markers.endIndex ?? 0) -// ForEach(markerRange, id:\.self) { markerIndex in -// MarkerView(marker: self.timeline.tracks!.markers[markerIndex], -// secondsToPixels: self.$secondsToPixels) -// } -// } -//// .frame(width: self.getSafeWidth(), alignment: .leading ) -// .frame(alignment: .leading) -// .border(Color.purple, width: 2) -// } } diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TrackView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TrackView.swift index ec29d44..4336b1b 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TrackView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TrackView.swift @@ -14,8 +14,10 @@ import SwiftUI struct TrackView : View { + static let trackHeight:CGFloat = 35 + let track:OpenTimelineIO.Track - @State var backgroundColor:Color + let backgroundColor:Color @Binding var secondsToPixels:Double @Binding var selectedItem:Item? @@ -27,14 +29,13 @@ struct TrackView : View { Section(header: self.headerView() ) { - ForEach(0.. Date: Wed, 2 Jul 2025 17:30:14 -0400 Subject: [PATCH 09/67] Cleanup --- .../OpenTimelineIO-Reader/Views/ItemInspectorView.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemInspectorView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemInspectorView.swift index 7020708..633d253 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemInspectorView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemInspectorView.swift @@ -129,7 +129,6 @@ struct ItemInspectorView: View } .font(.system(size: 10)) .listRowSeparator(.hidden) - } func safeToJSON(item: Item) -> String From ec643602af7867393f5f81fc08c4da93362b3abb Mon Sep 17 00:00:00 2001 From: vade Date: Wed, 2 Jul 2025 17:36:18 -0400 Subject: [PATCH 10/67] Fix alignment of TimelineRuler canvas to allow for "overdraw" fixing #25 --- .../OpenTimelineIO-Reader/Views/TimeRulerView.swift | 7 ++++++- .../OpenTimelineIO-Reader/Views/TimelineView.swift | 6 +++--- .../OpenTimelineIO-Reader/Views/TrackView.swift | 5 +++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift index 3417277..440cd5f 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift @@ -11,6 +11,9 @@ import TimecodeKit import SwiftUI struct TimeRulerView: View { + + static let VerticalPadding:CGFloat = 100 + let timeline: OpenTimelineIO.Timeline @Binding var secondsToPixels: Double @Binding var currentTime: OpenTimelineIO.RationalTime @@ -19,6 +22,8 @@ struct TimeRulerView: View { Canvas { context, size in + context.translateBy(x: TrackView.trackHeaderWidth, y: 0) + let safeRange = getSafeRange() let startSeconds = safeRange.startTime.toSeconds() let endSeconds = safeRange.endTimeInclusive().toSeconds() @@ -32,7 +37,7 @@ struct TimeRulerView: View drawMarkers(context: context, startSeconds: startSeconds, endSeconds: endSeconds, secondsToPixels: secondsToPixels, size: size) } - .frame(width: self.getSafeWidth()) + .frame(width: self.getSafeWidth() + TrackView.trackHeaderWidth) } func drawMarkers(context: GraphicsContext, startSeconds: Double, endSeconds: Double, secondsToPixels: Double, size: CGSize) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift index d09055c..eb16a86 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift @@ -40,8 +40,7 @@ struct TimelineView : View { self.hitTestEnabled = !newPhase.isScrolling }) - .frame(idealHeight: ( CGFloat((timeline.videoTracks.count + timeline.audioTracks.count)) * TrackView.trackHeight) + 100 ) - // .frame(maxHeight: CGFloat((videoTracks.count + audioTracks.count)) * 500) + .frame(idealHeight: ( CGFloat((timeline.videoTracks.count + timeline.audioTracks.count)) * TrackView.trackHeight) + TimeRulerView.VerticalPadding ) } else @@ -51,6 +50,8 @@ struct TimelineView : View { { self.timelineView() } + .frame(idealHeight: ( CGFloat((timeline.videoTracks.count + timeline.audioTracks.count)) * TrackView.trackHeight) + TimeRulerView.VerticalPadding ) + } } @@ -63,7 +64,6 @@ struct TimelineView : View { { TimeRulerView(timeline: self.timeline, secondsToPixels: self.$secondsToPixels, currentTime: self.$currentTime ) .frame(height: 50) - .offset(x:100) ForEach(0.. CGFloat { - return self.getSafeRange().duration.toSeconds() * self.secondsToPixels + 100 + return self.getSafeRange().duration.toSeconds() * self.secondsToPixels + TrackView.trackHeaderWidth } func getSafePositionX() -> CGFloat From 54a979d4cf58434ec46a59f8c19344e19ab19284 Mon Sep 17 00:00:00 2001 From: vade Date: Tue, 1 Jul 2025 18:46:31 -0400 Subject: [PATCH 11/67] First step to marker support - some test data borrowed from OTIO Core project Signed-off-by: vade Signed-off-by: Anton Marini --- .../Assets/premiere_example.otio | 2369 +++++++++++++++++ 1 file changed, 2369 insertions(+) create mode 100644 Tests/OpenTimelineIO-AVFoundationTests/Assets/premiere_example.otio diff --git a/Tests/OpenTimelineIO-AVFoundationTests/Assets/premiere_example.otio b/Tests/OpenTimelineIO-AVFoundationTests/Assets/premiere_example.otio new file mode 100644 index 0000000..17f96ee --- /dev/null +++ b/Tests/OpenTimelineIO-AVFoundationTests/Assets/premiere_example.otio @@ -0,0 +1,2369 @@ +{ + "OTIO_SCHEMA": "Timeline.1", + "metadata": { + "fcp_xml": { + "@MZ.EditLine": "0", + "@MZ.Sequence.AudioTimeDisplayFormat": "200", + "@MZ.Sequence.EditingModeGUID": "9678af98-a7b7-4bdb-b477-7ac9c8df4a4e", + "@MZ.Sequence.PreviewFrameSizeHeight": "720", + "@MZ.Sequence.PreviewFrameSizeWidth": "1280", + "@MZ.Sequence.PreviewRenderingClassID": "1297106761", + "@MZ.Sequence.PreviewRenderingPresetCodec": "1297107278", + "@MZ.Sequence.PreviewRenderingPresetPath": "EncoderPresets\\SequencePreview\\9678af98-a7b7-4bdb-b477-7ac9c8df4a4e\\I-Frame Only MPEG.epr", + "@MZ.Sequence.PreviewUseMaxBitDepth": "false", + "@MZ.Sequence.PreviewUseMaxRenderQuality": "false", + "@MZ.Sequence.VideoTimeDisplayFormat": "104", + "@MZ.WorkInPoint": "0", + "@MZ.WorkOutPoint": "10550131200000", + "@Monitor.ProgramZoomIn": "0", + "@Monitor.ProgramZoomOut": "10550131200000", + "@TL.SQAVDividerPosition": "0.5", + "@TL.SQAudioVisibleBase": "0", + "@TL.SQHeaderWidth": "184", + "@TL.SQHideShyTracks": "0", + "@TL.SQTimePerPixel": "0.033806825568230996", + "@TL.SQVideoVisibleBase": "0", + "@TL.SQVisibleBaseTime": "0", + "@explodedTracks": "true", + "@id": "sequence-1", + "labels": { + "label2": "Forest" + }, + "media": { + "audio": { + "format": { + "samplecharacteristics": { + "depth": "16", + "samplerate": "48000" + } + }, + "numOutputChannels": "2", + "outputs": { + "group": [ + { + "channel": { + "index": "1" + }, + "downmix": "0", + "index": "1", + "numchannels": "1" + }, + { + "channel": { + "index": "2" + }, + "downmix": "0", + "index": "2", + "numchannels": "1" + } + ] + } + }, + "video": { + "format": { + "samplecharacteristics": { + "anamorphic": "FALSE", + "codec": { + "appspecificdata": { + "appmanufacturer": "Apple Inc.", + "appname": "Final Cut Pro", + "appversion": "7.0", + "data": { + "qtcodec": { + "codecname": "Apple ProRes 422", + "codectypecode": "apcn", + "codectypename": "Apple ProRes 422", + "codecvendorcode": "appl", + "datarate": "0", + "keyframerate": "0", + "spatialquality": "1024", + "temporalquality": "0" + } + } + }, + "name": "Apple ProRes 422" + }, + "colordepth": "24", + "fielddominance": "none", + "height": "720", + "pixelaspectratio": "square", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "width": "1280" + } + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "NDF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + } + }, + "uuid": "5ea30a6b-552f-4722-be92-6dfdb66c97e6" + } + }, + "name": "sc01_sh010_layerA", + "global_start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + }, + "tracks": { + "OTIO_SCHEMA": "Stack.1", + "metadata": {}, + "name": "sc01_sh010_layerA", + "source_range": null, + "effects": [], + "markers": [ + { + "OTIO_SCHEMA": "Marker.2", + "metadata": { + "fcp_xml": { + "comment": "so, this happened" + } + }, + "name": "My MArker 1", + "color": "RED", + "marked_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 113.0 + } + } + }, + { + "OTIO_SCHEMA": "Marker.2", + "metadata": { + "fcp_xml": { + "comment": "fsfsfs" + } + }, + "name": "dsf", + "color": "RED", + "marked_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 492.0 + } + } + }, + { + "OTIO_SCHEMA": "Marker.2", + "metadata": { + "fcp_xml": { + "comment": null + } + }, + "name": "", + "color": "RED", + "marked_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 298.0 + } + } + } + ], + "enabled": true, + "children": [ + { + "OTIO_SCHEMA": "Track.1", + "metadata": { + "fcp_xml": { + "@MZ.TrackTargeted": "1", + "@TL.SQTrackExpanded": "0", + "@TL.SQTrackExpandedHeight": "25", + "@TL.SQTrackShy": "0", + "enabled": "TRUE", + "locked": "FALSE" + } + }, + "name": "", + "source_range": null, + "effects": [], + "markers": [], + "enabled": true, + "children": [ + { + "OTIO_SCHEMA": "Gap.1", + "metadata": {}, + "name": "", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 536.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true + }, + { + "OTIO_SCHEMA": "Clip.2", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-1", + "alphatype": "none", + "anamorphic": "FALSE", + "enabled": "TRUE", + "labels": { + "label2": "Iris" + }, + "link": [ + { + "clipindex": "1", + "linkclipref": "clipitem-1", + "mediatype": "video", + "trackindex": "1" + }, + { + "clipindex": "2", + "groupindex": "1", + "linkclipref": "clipitem-14", + "mediatype": "audio", + "trackindex": "1" + }, + { + "clipindex": "2", + "groupindex": "1", + "linkclipref": "clipitem-16", + "mediatype": "audio", + "trackindex": "2" + } + ], + "logginginfo": { + "description": null, + "lognote": null, + "scene": null, + "shottake": null + }, + "masterclipid": "masterclip-1", + "pixelaspectratio": "square", + "pproTicksIn": "0", + "pproTicksOut": "846720000000" + }, + "my_hook_function_was_here": true + }, + "name": "sc01_sh010_anim.mov", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 15.0, + "value": 50.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 15.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ExternalReference.1", + "metadata": { + "fcp_xml": { + "@id": "file-1", + "media": { + "audio": { + "channelcount": "2", + "samplecharacteristics": { + "depth": "16", + "samplerate": "48000" + } + }, + "video": { + "samplecharacteristics": { + "anamorphic": "FALSE", + "fielddominance": "none", + "height": "720", + "pixelaspectratio": "square", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "width": "1280" + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "NDF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "reel": { + "name": null + } + } + } + }, + "name": "sc01_sh010_anim.mov", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 100.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "available_image_bounds": null, + "target_url": "file://localhost/D%3a/media/sc01_sh010_anim.mov" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" + } + ], + "kind": "Video" + }, + { + "OTIO_SCHEMA": "Track.1", + "metadata": { + "fcp_xml": { + "@MZ.TrackTargeted": "0", + "@TL.SQTrackExpanded": "0", + "@TL.SQTrackExpandedHeight": "25", + "@TL.SQTrackShy": "0", + "enabled": "TRUE", + "locked": "FALSE" + } + }, + "name": "", + "source_range": null, + "effects": [], + "markers": [], + "enabled": true, + "children": [ + { + "OTIO_SCHEMA": "Gap.1", + "metadata": {}, + "name": "", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 13.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true + }, + { + "OTIO_SCHEMA": "Clip.2", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-2", + "alphatype": "none", + "enabled": "TRUE", + "labels": { + "label2": "Iris" + }, + "link": [ + { + "clipindex": "1", + "linkclipref": "clipitem-2", + "mediatype": "video", + "trackindex": "2" + }, + { + "clipindex": "1", + "groupindex": "1", + "linkclipref": "clipitem-13", + "mediatype": "audio", + "trackindex": "1" + }, + { + "clipindex": "1", + "groupindex": "1", + "linkclipref": "clipitem-15", + "mediatype": "audio", + "trackindex": "2" + } + ], + "logginginfo": { + "description": null, + "lognote": null, + "scene": null, + "shottake": null + }, + "masterclipid": "masterclip-1", + "pproTicksIn": "0", + "pproTicksOut": "846720000000" + }, + "my_hook_function_was_here": true + }, + "name": "sc01_sh010_anim.mov", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 100.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ExternalReference.1", + "metadata": { + "fcp_xml": { + "@id": "file-1", + "media": { + "audio": { + "channelcount": "2", + "samplecharacteristics": { + "depth": "16", + "samplerate": "48000" + } + }, + "video": { + "samplecharacteristics": { + "anamorphic": "FALSE", + "fielddominance": "none", + "height": "720", + "pixelaspectratio": "square", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "width": "1280" + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "NDF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "reel": { + "name": null + } + } + } + }, + "name": "sc01_sh010_anim.mov", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 100.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "available_image_bounds": null, + "target_url": "file://localhost/D%3a/media/sc01_sh010_anim.mov" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" + }, + { + "OTIO_SCHEMA": "Gap.1", + "metadata": {}, + "name": "", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 52.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true + }, + { + "OTIO_SCHEMA": "Clip.2", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-3", + "alphatype": "none", + "anamorphic": "FALSE", + "enabled": "TRUE", + "labels": { + "label2": "Iris" + }, + "logginginfo": { + "description": null, + "lognote": null, + "scene": null, + "shottake": null + }, + "masterclipid": "masterclip-2", + "pixelaspectratio": "square", + "pproTicksIn": "0", + "pproTicksOut": "1329350400000" + }, + "my_hook_function_was_here": true + }, + "name": "sc01_sh020_anim.mov", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 157.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ExternalReference.1", + "metadata": { + "fcp_xml": { + "@id": "file-2", + "media": { + "audio": { + "channelcount": "2", + "samplecharacteristics": { + "depth": "16", + "samplerate": "48000" + } + }, + "video": { + "samplecharacteristics": { + "anamorphic": "FALSE", + "fielddominance": "none", + "height": "720", + "pixelaspectratio": "square", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "width": "1280" + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "NDF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "reel": { + "name": null + } + } + } + }, + "name": "sc01_sh020_anim.mov", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 175.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "available_image_bounds": null, + "target_url": "file://localhost/D%3a/media/sc01_sh020_anim.mov" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" + }, + { + "OTIO_SCHEMA": "Clip.2", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-4", + "alphatype": "none", + "anamorphic": "FALSE", + "enabled": "TRUE", + "labels": { + "label2": "Iris" + }, + "logginginfo": { + "description": null, + "lognote": null, + "scene": null, + "shottake": null + }, + "masterclipid": "masterclip-3", + "pixelaspectratio": "square", + "pproTicksIn": "0", + "pproTicksOut": "1989792000000" + }, + "my_hook_function_was_here": true + }, + "name": "sc01_sh030_anim.mov", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 235.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [ + { + "OTIO_SCHEMA": "Marker.2", + "metadata": { + "fcp_xml": { + "comment": null + } + }, + "name": "", + "color": "RED", + "marked_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 73.0 + } + } + } + ], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ExternalReference.1", + "metadata": { + "fcp_xml": { + "@id": "file-3", + "media": { + "audio": { + "channelcount": "2", + "samplecharacteristics": { + "depth": "16", + "samplerate": "48000" + } + }, + "video": { + "samplecharacteristics": { + "anamorphic": "FALSE", + "fielddominance": "none", + "height": "720", + "pixelaspectratio": "square", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "width": "1280" + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "NDF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "reel": { + "name": null + } + } + } + }, + "name": "sc01_sh030_anim.mov", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 400.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "available_image_bounds": null, + "target_url": "file://localhost/D%3a/media/sc01_sh030_anim.mov" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" + }, + { + "OTIO_SCHEMA": "Transition.1", + "metadata": { + "fcp_xml": { + "alignment": "end-black", + "cutPointTicks": "160876800000" + } + }, + "name": "Cross Dissolve", + "in_offset": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 19.0 + }, + "out_offset": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + }, + "transition_type": "SMPTE_Dissolve" + }, + { + "OTIO_SCHEMA": "Gap.1", + "metadata": {}, + "name": "", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 79.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true + }, + { + "OTIO_SCHEMA": "Stack.1", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-5", + "enabled": "TRUE", + "labels": { + "label2": "Forest" + }, + "masterclipid": "masterclip-4", + "pproTicksIn": "0", + "pproTicksOut": "2709504000000" + } + }, + "name": "sc01_sh010_anim", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 320.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "children": [] + } + ], + "kind": "Video" + }, + { + "OTIO_SCHEMA": "Track.1", + "metadata": { + "fcp_xml": { + "@MZ.TrackTargeted": "0", + "@TL.SQTrackExpanded": "0", + "@TL.SQTrackExpandedHeight": "25", + "@TL.SQTrackShy": "0", + "enabled": "TRUE", + "locked": "FALSE" + } + }, + "name": "", + "source_range": null, + "effects": [], + "markers": [], + "enabled": true, + "children": [ + { + "OTIO_SCHEMA": "Gap.1", + "metadata": {}, + "name": "", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 15.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true + }, + { + "OTIO_SCHEMA": "Clip.2", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-10", + "alphatype": "straight", + "anamorphic": "FALSE", + "enabled": "TRUE", + "labels": { + "label2": "Lavender" + }, + "logginginfo": { + "description": null, + "lognote": null, + "scene": null, + "shottake": null + }, + "masterclipid": "masterclip-5", + "pixelaspectratio": "square", + "pproTicksIn": "914457600000000", + "pproTicksOut": "922425235200000" + }, + "my_hook_function_was_here": true + }, + "name": "test_title", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 941.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 108000.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "GeneratorReference.1", + "metadata": { + "fcp_xml": { + "@id": "file-4", + "media": { + "video": { + "samplecharacteristics": { + "anamorphic": "FALSE", + "fielddominance": "none", + "height": "720", + "pixelaspectratio": "square", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "width": "1280" + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "DF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "reel": { + "name": null + } + } + } + }, + "name": "test_title", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "available_image_bounds": null, + "generator_kind": "Slug", + "parameters": {} + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" + } + ], + "kind": "Video" + }, + { + "OTIO_SCHEMA": "Track.1", + "metadata": { + "fcp_xml": { + "@MZ.TrackTargeted": "0", + "@TL.SQTrackExpanded": "0", + "@TL.SQTrackExpandedHeight": "25", + "@TL.SQTrackShy": "0", + "enabled": "TRUE", + "locked": "FALSE" + } + }, + "name": "", + "source_range": null, + "effects": [], + "markers": [], + "enabled": true, + "children": [ + { + "OTIO_SCHEMA": "Gap.1", + "metadata": {}, + "name": "", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 956.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true + }, + { + "OTIO_SCHEMA": "Clip.2", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-11", + "alphatype": "none", + "anamorphic": "FALSE", + "enabled": "TRUE", + "labels": { + "label2": "Iris" + }, + "link": [ + { + "clipindex": "1", + "linkclipref": "clipitem-11", + "mediatype": "video", + "trackindex": "4" + }, + { + "clipindex": "1", + "groupindex": "1", + "linkclipref": "clipitem-23", + "mediatype": "audio", + "trackindex": "7" + }, + { + "clipindex": "1", + "groupindex": "1", + "linkclipref": "clipitem-25", + "mediatype": "audio", + "trackindex": "8" + } + ], + "logginginfo": { + "description": null, + "lognote": null, + "scene": null, + "shottake": null + }, + "masterclipid": "masterclip-6", + "pixelaspectratio": "square", + "pproTicksIn": "287884800000", + "pproTicksOut": "2159136000000" + }, + "my_hook_function_was_here": true + }, + "name": "sc01_master_layerA_sh030_temp.mov", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 208.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 133.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ExternalReference.1", + "metadata": { + "fcp_xml": { + "@id": "file-5", + "media": { + "audio": { + "channelcount": "2", + "samplecharacteristics": { + "depth": "16", + "samplerate": "48000" + } + }, + "video": { + "samplecharacteristics": { + "anamorphic": "FALSE", + "fielddominance": "none", + "height": "720", + "pixelaspectratio": "square", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "width": "1280" + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "NDF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "reel": { + "name": null + } + } + } + }, + "name": "sc01_master_layerA_sh030_temp.mov", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 400.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 99.0 + } + }, + "available_image_bounds": null, + "target_url": "file://localhost/D%3a/media/sc01_master_layerA_sh030_temp.mov" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" + }, + { + "OTIO_SCHEMA": "Transition.1", + "metadata": { + "fcp_xml": { + "alignment": "center", + "cutPointTicks": "101606400000" + } + }, + "name": "Cross Dissolve", + "in_offset": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 12.0 + }, + "out_offset": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 13.0 + }, + "transition_type": "SMPTE_Dissolve" + }, + { + "OTIO_SCHEMA": "Clip.2", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-12", + "alphatype": "none", + "enabled": "TRUE", + "labels": { + "label2": "Iris" + }, + "link": [ + { + "clipindex": "3", + "linkclipref": "clipitem-12", + "mediatype": "video", + "trackindex": "4" + }, + { + "clipindex": "2", + "groupindex": "1", + "linkclipref": "clipitem-24", + "mediatype": "audio", + "trackindex": "7" + }, + { + "clipindex": "2", + "groupindex": "1", + "linkclipref": "clipitem-26", + "mediatype": "audio", + "trackindex": "8" + } + ], + "logginginfo": { + "description": null, + "lognote": null, + "scene": null, + "shottake": null + }, + "masterclipid": "masterclip-1", + "pproTicksIn": "50803200000", + "pproTicksOut": "846720000000" + }, + "my_hook_function_was_here": true + }, + "name": "sc01_sh010_anim.mov", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 82.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 18.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ExternalReference.1", + "metadata": { + "fcp_xml": { + "@id": "file-1", + "media": { + "audio": { + "channelcount": "2", + "samplecharacteristics": { + "depth": "16", + "samplerate": "48000" + } + }, + "video": { + "samplecharacteristics": { + "anamorphic": "FALSE", + "fielddominance": "none", + "height": "720", + "pixelaspectratio": "square", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "width": "1280" + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "NDF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "reel": { + "name": null + } + } + } + }, + "name": "sc01_sh010_anim.mov", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 100.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "available_image_bounds": null, + "target_url": "file://localhost/D%3a/media/sc01_sh010_anim.mov" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" + } + ], + "kind": "Video" + }, + { + "OTIO_SCHEMA": "Track.1", + "metadata": { + "fcp_xml": { + "@MZ.TrackTargeted": "1", + "@PannerCurrentValue": "0.5", + "@PannerIsInverted": "true", + "@PannerName": "Balance", + "@PannerStartKeyframe": "-91445760000000000,0.5,0,0,0,0,0,0", + "@TL.SQTrackAudioKeyframeStyle": "0", + "@TL.SQTrackExpanded": "0", + "@TL.SQTrackExpandedHeight": "25", + "@TL.SQTrackShy": "0", + "@currentExplodedTrackIndex": "0", + "@premiereTrackType": "Stereo", + "@totalExplodedTrackCount": "2", + "enabled": "TRUE", + "locked": "FALSE", + "outputchannelindex": "1" + } + }, + "name": "", + "source_range": null, + "effects": [], + "markers": [], + "enabled": true, + "children": [ + { + "OTIO_SCHEMA": "Gap.1", + "metadata": {}, + "name": "", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 13.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true + }, + { + "OTIO_SCHEMA": "Clip.2", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-13", + "@premiereChannelType": "stereo", + "enabled": "TRUE", + "labels": { + "label2": "Iris" + }, + "link": [ + { + "clipindex": "1", + "linkclipref": "clipitem-2", + "mediatype": "video", + "trackindex": "2" + }, + { + "clipindex": "1", + "groupindex": "1", + "linkclipref": "clipitem-13", + "mediatype": "audio", + "trackindex": "1" + }, + { + "clipindex": "1", + "groupindex": "1", + "linkclipref": "clipitem-15", + "mediatype": "audio", + "trackindex": "2" + } + ], + "logginginfo": { + "description": null, + "lognote": null, + "scene": null, + "shottake": null + }, + "masterclipid": "masterclip-1", + "pproTicksIn": "0", + "pproTicksOut": "846720000000", + "sourcetrack": { + "mediatype": "audio", + "trackindex": "1" + } + }, + "my_hook_function_was_here": true + }, + "name": "sc01_sh010_anim.mov", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 100.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ExternalReference.1", + "metadata": { + "fcp_xml": { + "@id": "file-1", + "media": { + "audio": { + "channelcount": "2", + "samplecharacteristics": { + "depth": "16", + "samplerate": "48000" + } + }, + "video": { + "samplecharacteristics": { + "anamorphic": "FALSE", + "fielddominance": "none", + "height": "720", + "pixelaspectratio": "square", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "width": "1280" + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "NDF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "reel": { + "name": null + } + } + } + }, + "name": "sc01_sh010_anim.mov", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 100.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "available_image_bounds": null, + "target_url": "file://localhost/D%3a/media/sc01_sh010_anim.mov" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" + }, + { + "OTIO_SCHEMA": "Gap.1", + "metadata": {}, + "name": "", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 423.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true + }, + { + "OTIO_SCHEMA": "Clip.2", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-14", + "@premiereChannelType": "stereo", + "enabled": "TRUE", + "labels": { + "label2": "Iris" + }, + "link": [ + { + "clipindex": "1", + "linkclipref": "clipitem-1", + "mediatype": "video", + "trackindex": "1" + }, + { + "clipindex": "2", + "groupindex": "1", + "linkclipref": "clipitem-14", + "mediatype": "audio", + "trackindex": "1" + }, + { + "clipindex": "2", + "groupindex": "1", + "linkclipref": "clipitem-16", + "mediatype": "audio", + "trackindex": "2" + } + ], + "logginginfo": { + "description": null, + "lognote": null, + "scene": null, + "shottake": null + }, + "masterclipid": "masterclip-1", + "pproTicksIn": "0", + "pproTicksOut": "846720000000", + "sourcetrack": { + "mediatype": "audio", + "trackindex": "1" + } + }, + "my_hook_function_was_here": true + }, + "name": "sc01_sh010_anim.mov", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 100.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ExternalReference.1", + "metadata": { + "fcp_xml": { + "@id": "file-1", + "media": { + "audio": { + "channelcount": "2", + "samplecharacteristics": { + "depth": "16", + "samplerate": "48000" + } + }, + "video": { + "samplecharacteristics": { + "anamorphic": "FALSE", + "fielddominance": "none", + "height": "720", + "pixelaspectratio": "square", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "width": "1280" + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "NDF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "reel": { + "name": null + } + } + } + }, + "name": "sc01_sh010_anim.mov", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 100.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "available_image_bounds": null, + "target_url": "file://localhost/D%3a/media/sc01_sh010_anim.mov" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" + } + ], + "kind": "Audio" + }, + { + "OTIO_SCHEMA": "Track.1", + "metadata": { + "fcp_xml": { + "@MZ.TrackTargeted": "1", + "@PannerCurrentValue": "0.5", + "@PannerIsInverted": "true", + "@PannerName": "Balance", + "@PannerStartKeyframe": "-91445760000000000,0.5,0,0,0,0,0,0", + "@TL.SQTrackAudioKeyframeStyle": "0", + "@TL.SQTrackExpanded": "0", + "@TL.SQTrackExpandedHeight": "25", + "@TL.SQTrackShy": "0", + "@currentExplodedTrackIndex": "0", + "@premiereTrackType": "Stereo", + "@totalExplodedTrackCount": "2", + "enabled": "TRUE", + "locked": "FALSE", + "outputchannelindex": "1" + } + }, + "name": "", + "source_range": null, + "effects": [], + "markers": [], + "enabled": true, + "children": [ + { + "OTIO_SCHEMA": "Gap.1", + "metadata": {}, + "name": "", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 335.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true + }, + { + "OTIO_SCHEMA": "Clip.2", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-17", + "@premiereChannelType": "stereo", + "enabled": "TRUE", + "labels": { + "label2": "Caribbean" + }, + "link": [ + { + "clipindex": "1", + "groupindex": "1", + "linkclipref": "clipitem-17", + "mediatype": "audio", + "trackindex": "3" + }, + { + "clipindex": "1", + "groupindex": "1", + "linkclipref": "clipitem-19", + "mediatype": "audio", + "trackindex": "4" + } + ], + "logginginfo": { + "description": null, + "lognote": null, + "scene": null, + "shottake": null + }, + "masterclipid": "masterclip-7", + "pproTicksIn": "0", + "pproTicksOut": "1439424000000", + "sourcetrack": { + "mediatype": "audio", + "trackindex": "1" + } + }, + "my_hook_function_was_here": true + }, + "name": "sc01_placeholder.wav", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 170.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 8497.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ExternalReference.1", + "metadata": { + "fcp_xml": { + "@id": "file-6", + "media": { + "audio": { + "channelcount": "2", + "samplecharacteristics": { + "depth": "16", + "samplerate": "48000" + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "DF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "reel": { + "name": null + } + } + } + }, + "name": "sc01_placeholder.wav", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 170.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 8497.0 + } + }, + "available_image_bounds": null, + "target_url": "file://localhost/D%3a/media/sc01_placeholder.wav" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" + }, + { + "OTIO_SCHEMA": "Gap.1", + "metadata": {}, + "name": "", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 131.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true + }, + { + "OTIO_SCHEMA": "Stack.1", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-18", + "@premiereChannelType": "stereo", + "enabled": "TRUE", + "labels": { + "label2": "Forest" + }, + "link": [ + { + "clipindex": "2", + "groupindex": "1", + "linkclipref": "clipitem-18", + "mediatype": "audio", + "trackindex": "3" + }, + { + "clipindex": "2", + "groupindex": "1", + "linkclipref": "clipitem-20", + "mediatype": "audio", + "trackindex": "4" + } + ], + "masterclipid": "masterclip-4", + "pproTicksIn": "0", + "pproTicksOut": "2489356800000" + } + }, + "name": "sc01_sh010_anim", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 294.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "children": [] + } + ], + "kind": "Audio" + }, + { + "OTIO_SCHEMA": "Track.1", + "metadata": { + "fcp_xml": { + "@MZ.TrackTargeted": "1", + "@PannerCurrentValue": "0.5", + "@PannerIsInverted": "true", + "@PannerName": "Balance", + "@PannerStartKeyframe": "-91445760000000000,0.5,0,0,0,0,0,0", + "@TL.SQTrackAudioKeyframeStyle": "0", + "@TL.SQTrackExpanded": "0", + "@TL.SQTrackExpandedHeight": "25", + "@TL.SQTrackShy": "0", + "@currentExplodedTrackIndex": "0", + "@premiereTrackType": "Stereo", + "@totalExplodedTrackCount": "2", + "enabled": "TRUE", + "locked": "FALSE", + "outputchannelindex": "1" + } + }, + "name": "", + "source_range": null, + "effects": [], + "markers": [], + "enabled": true, + "children": [ + { + "OTIO_SCHEMA": "Gap.1", + "metadata": {}, + "name": "", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 153.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true + }, + { + "OTIO_SCHEMA": "Clip.2", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-21", + "@premiereChannelType": "stereo", + "enabled": "TRUE", + "labels": { + "label2": "Caribbean" + }, + "link": [ + { + "clipindex": "1", + "groupindex": "1", + "linkclipref": "clipitem-21", + "mediatype": "audio", + "trackindex": "5" + }, + { + "clipindex": "1", + "groupindex": "1", + "linkclipref": "clipitem-22", + "mediatype": "audio", + "trackindex": "6" + } + ], + "logginginfo": { + "description": null, + "lognote": null, + "scene": null, + "shottake": null + }, + "masterclipid": "masterclip-8", + "pproTicksIn": "0", + "pproTicksOut": "1676505600000", + "sourcetrack": { + "mediatype": "audio", + "trackindex": "1" + } + }, + "my_hook_function_was_here": true + }, + "name": "track_08.wav", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 198.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 6896.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ExternalReference.1", + "metadata": { + "fcp_xml": { + "@id": "file-7", + "media": { + "audio": { + "channelcount": "2", + "samplecharacteristics": { + "depth": "16", + "samplerate": "48000" + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "DF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "reel": { + "name": null + } + } + } + }, + "name": "track_08.wav", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 198.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 6896.0 + } + }, + "available_image_bounds": null, + "target_url": "file://localhost/D%3a/media/track_08.wav" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" + } + ], + "kind": "Audio" + }, + { + "OTIO_SCHEMA": "Track.1", + "metadata": { + "fcp_xml": { + "@MZ.TrackTargeted": "0", + "@PannerCurrentValue": "0.5", + "@PannerIsInverted": "true", + "@PannerName": "Balance", + "@PannerStartKeyframe": "-91445760000000000,0.5,0,0,0,0,0,0", + "@TL.SQTrackAudioKeyframeStyle": "0", + "@TL.SQTrackExpanded": "0", + "@TL.SQTrackExpandedHeight": "25", + "@TL.SQTrackShy": "0", + "@currentExplodedTrackIndex": "0", + "@premiereTrackType": "Stereo", + "@totalExplodedTrackCount": "2", + "enabled": "TRUE", + "locked": "FALSE", + "outputchannelindex": "1" + } + }, + "name": "", + "source_range": null, + "effects": [], + "markers": [], + "enabled": true, + "children": [ + { + "OTIO_SCHEMA": "Gap.1", + "metadata": {}, + "name": "", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 956.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true + }, + { + "OTIO_SCHEMA": "Clip.2", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-23", + "@premiereChannelType": "stereo", + "enabled": "TRUE", + "labels": { + "label2": "Iris" + }, + "link": [ + { + "clipindex": "1", + "linkclipref": "clipitem-11", + "mediatype": "video", + "trackindex": "4" + }, + { + "clipindex": "1", + "groupindex": "1", + "linkclipref": "clipitem-23", + "mediatype": "audio", + "trackindex": "7" + }, + { + "clipindex": "1", + "groupindex": "1", + "linkclipref": "clipitem-25", + "mediatype": "audio", + "trackindex": "8" + } + ], + "logginginfo": { + "description": null, + "lognote": null, + "scene": null, + "shottake": null + }, + "masterclipid": "masterclip-6", + "pproTicksIn": "287884800000", + "pproTicksOut": "2049062400000", + "sourcetrack": { + "mediatype": "audio", + "trackindex": "1" + } + }, + "my_hook_function_was_here": true + }, + "name": "sc01_master_layerA_sh030_temp.mov", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 221.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 133.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ExternalReference.1", + "metadata": { + "fcp_xml": { + "@id": "file-5", + "media": { + "audio": { + "channelcount": "2", + "samplecharacteristics": { + "depth": "16", + "samplerate": "48000" + } + }, + "video": { + "samplecharacteristics": { + "anamorphic": "FALSE", + "fielddominance": "none", + "height": "720", + "pixelaspectratio": "square", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "width": "1280" + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "NDF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "reel": { + "name": null + } + } + } + }, + "name": "sc01_master_layerA_sh030_temp.mov", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 400.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 99.0 + } + }, + "available_image_bounds": null, + "target_url": "file://localhost/D%3a/media/sc01_master_layerA_sh030_temp.mov" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" + }, + { + "OTIO_SCHEMA": "Clip.2", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-24", + "@premiereChannelType": "stereo", + "enabled": "TRUE", + "labels": { + "label2": "Iris" + }, + "link": [ + { + "clipindex": "3", + "linkclipref": "clipitem-12", + "mediatype": "video", + "trackindex": "4" + }, + { + "clipindex": "2", + "groupindex": "1", + "linkclipref": "clipitem-24", + "mediatype": "audio", + "trackindex": "7" + }, + { + "clipindex": "2", + "groupindex": "1", + "linkclipref": "clipitem-26", + "mediatype": "audio", + "trackindex": "8" + } + ], + "logginginfo": { + "description": null, + "lognote": null, + "scene": null, + "shottake": null + }, + "masterclipid": "masterclip-1", + "pproTicksIn": "152409600000", + "pproTicksOut": "846720000000", + "sourcetrack": { + "mediatype": "audio", + "trackindex": "1" + } + }, + "my_hook_function_was_here": true + }, + "name": "sc01_sh010_anim.mov", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 94.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 6.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ExternalReference.1", + "metadata": { + "fcp_xml": { + "@id": "file-1", + "media": { + "audio": { + "channelcount": "2", + "samplecharacteristics": { + "depth": "16", + "samplerate": "48000" + } + }, + "video": { + "samplecharacteristics": { + "anamorphic": "FALSE", + "fielddominance": "none", + "height": "720", + "pixelaspectratio": "square", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "width": "1280" + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "NDF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "reel": { + "name": null + } + } + } + }, + "name": "sc01_sh010_anim.mov", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 100.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "available_image_bounds": null, + "target_url": "file://localhost/D%3a/media/sc01_sh010_anim.mov" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" + } + ], + "kind": "Audio" + } + ] + } +} \ No newline at end of file From f06acad106b38b221f65643fd87a8f0f8fbbddb0 Mon Sep 17 00:00:00 2001 From: vade Date: Tue, 1 Jul 2025 19:15:37 -0400 Subject: [PATCH 12/67] Draft of marker view Signed-off-by: vade Signed-off-by: Anton Marini --- .../Views/ItemView.swift | 8 +++ .../Views/MarkerView.swift | 59 +++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift index 6d1da6f..69eef9e 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift @@ -53,6 +53,7 @@ struct ItemView : View { } } .frame(width: self.getSafeWidth()) + // .offset(x:self.getSafePositionX() )//, y:geometry.size.height * 0.5 ) } @@ -81,4 +82,11 @@ struct ItemView : View { { return self.getSafeRange().startTime.toSeconds() * self.secondsToPixels// + self.getSafeWidth()/2.0 } + +// @ViewBuilder func getMarkerView() -> some View +// { +// ForEach(self.item.markers) { marker in +// +// } +// } } diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift new file mode 100644 index 0000000..b1357f2 --- /dev/null +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift @@ -0,0 +1,59 @@ +// +// MarkerView.swift +// OpenTimelineIO-Reader +// +// Created by Anton Marini on 7/1/25. +// + +import OpenTimelineIO_AVFoundation +import OpenTimelineIO +import TimecodeKit +import SwiftUI + +struct MarkerView : View { + + let marker:OpenTimelineIO.Marker + + @Binding var secondsToPixels:Double + + var body: some View + { + self.colorForMarker() + .frame(width: self.getSafeWidth() ) + .offset(x:self.getSafePositionX() ) + } + + func getSafeWidth() -> CGFloat + { + return max(self.marker.markedRange.duration.toSeconds() * self.secondsToPixels, 3.0) + } + + func getSafePositionX() -> CGFloat + { + return self.marker.markedRange.startTime.toSeconds() * self.secondsToPixels// + self.getSafeWidth()/2.0 + } + + func colorForMarker() -> Color + { + if let color:Marker.Color = Marker.Color(rawValue: marker.color) + { + switch color + { + case .pink : return Color.pink + case .red : return Color.red + case .orange : return Color.orange + case .yellow : return Color.yellow + case .green : return Color.green + case .cyan : return Color.cyan + case .blue : return Color.blue + case .purple : return Color.purple + case .magenta : return Color(red: 1.0, green: 0, blue: 1.0) + case .black : return Color.black + case .white : return Color.white + + } + } + + return Color.red + } +} From 883836b29e69d0d4b91278b5aa8f32b44057e0ac Mon Sep 17 00:00:00 2001 From: vade Date: Tue, 1 Jul 2025 20:21:21 -0400 Subject: [PATCH 13/67] Wire basic marker view up - need to validate placement logic Signed-off-by: vade Signed-off-by: Anton Marini --- .../Views/ItemView.swift | 24 ++++++++++++------- .../Views/MarkerView.swift | 5 ++-- .../Views/TimeRulerView.swift | 9 +++---- .../Views/TimelineView.swift | 19 ++++++++++++--- .../Views/TrackView.swift | 15 ++++++++++++ 5 files changed, 53 insertions(+), 19 deletions(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift index 69eef9e..635569e 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift @@ -20,7 +20,6 @@ struct ItemView : View { var body: some View { - ZStack { if let _ = item as? Gap @@ -52,8 +51,10 @@ struct ItemView : View { } } } - .frame(width: self.getSafeWidth()) - + .frame(width: self.getSafeWidth(), alignment: .leading ) + .overlay { + self.getMarkerView() + } // .offset(x:self.getSafePositionX() )//, y:geometry.size.height * 0.5 ) } @@ -83,10 +84,15 @@ struct ItemView : View { return self.getSafeRange().startTime.toSeconds() * self.secondsToPixels// + self.getSafeWidth()/2.0 } -// @ViewBuilder func getMarkerView() -> some View -// { -// ForEach(self.item.markers) { marker in -// -// } -// } + @ViewBuilder func getMarkerView() -> some View + { + HStack(alignment: .top) + { + let markerRange = self.item.markers.startIndex ..< self.item.markers.endIndex + ForEach(markerRange, id:\.self) { markerIndex in + MarkerView(marker: self.item.markers[markerIndex], + secondsToPixels: self.$secondsToPixels) + } + } + } } diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift index b1357f2..426af0b 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift @@ -19,13 +19,13 @@ struct MarkerView : View { var body: some View { self.colorForMarker() - .frame(width: self.getSafeWidth() ) + .frame(width: self.getSafeWidth(), height: 7.0, alignment: .leading ) .offset(x:self.getSafePositionX() ) } func getSafeWidth() -> CGFloat { - return max(self.marker.markedRange.duration.toSeconds() * self.secondsToPixels, 3.0) + return max(self.marker.markedRange.duration.toSeconds() * self.secondsToPixels, 5.0) } func getSafePositionX() -> CGFloat @@ -50,7 +50,6 @@ struct MarkerView : View { case .magenta : return Color(red: 1.0, green: 0, blue: 1.0) case .black : return Color.black case .white : return Color.white - } } diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift index e972c36..f0c18f4 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift @@ -34,7 +34,7 @@ struct TimeRulerView: View } func drawTicks(context: GraphicsContext, startSeconds: Double, endSeconds: Double, secondsToPixels: Double, size: CGSize) { - if self.secondsToPixels > 100 + if self.secondsToPixels > 75 { self.drawFrameTicks(context: context, startSeconds: startSeconds, endSeconds: endSeconds, secondsToPixels: secondsToPixels, size: size) } @@ -66,7 +66,7 @@ struct TimeRulerView: View context.fill(Path(tickRect), with: .color(.white)) // Draw label if it's an hour or minute - if self.secondsToPixels > 50 + if self.secondsToPixels > 75 { context.draw(Text(label).font(.system(size: 10)), at: CGPoint(x: positionX + 2, y: size.height - tickHeight - 10)) } @@ -93,9 +93,10 @@ struct TimeRulerView: View let tickRect = CGRect(x: positionX, y: size.height - tickHeight, width: 1, height: tickHeight) context.fill(Path(tickRect), with: .color(.white)) - if self.secondsToPixels > 400 + if self.secondsToPixels > 500 { - context.draw(Text(String(frameNum)).font(.system(size: 10)), at: CGPoint(x: positionX, y: size.height - tickHeight - 5)) + context.draw(Text(String(frameNum)).font(.system(size: 10)), + at: CGPoint(x: positionX, y: size.height - tickHeight - 5)) } } } diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift index 6b9694f..7c6d599 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift @@ -51,17 +51,19 @@ struct TimelineView : View { } } - func timelineView() -> some View + @ViewBuilder func timelineView() -> some View { let videoTracks = timeline.videoTracks let audioTracks = timeline.audioTracks - return + VStack(alignment:.leading, spacing: 3) { TimeRulerView(timeline: self.timeline, secondsToPixels: self.$secondsToPixels, currentTime: self.$currentTime ) .frame(height: 40) .offset(x:100) + .overlay( self.getMarkerView( ) ) + // ForEach(0.. some View + { + HStack(alignment: .top) + { + let markerRange = (self.timeline.tracks?.markers.startIndex ?? 0) ..< (self.timeline.tracks?.markers.endIndex ?? 0) + ForEach(markerRange, id:\.self) { markerIndex in + MarkerView(marker: self.timeline.tracks!.markers[markerIndex], + secondsToPixels: self.$secondsToPixels) + } + } } } diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TrackView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TrackView.swift index 30e2caa..ec29d44 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TrackView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TrackView.swift @@ -45,6 +45,9 @@ struct TrackView : View } } .frame(width: self.getSafeWidth(), alignment: .leading ) + .overlay { + self.getMarkerView() + } // .position(x:self.getSafePositionX(), y:0 ) } @@ -94,4 +97,16 @@ struct TrackView : View { return self.getSafeRange().startTime.toSeconds() * self.secondsToPixels - self.getSafeWidth()/2.0 } + + @ViewBuilder func getMarkerView() -> some View + { + HStack(alignment: .top) + { + let markerRange = self.track.markers.startIndex ..< self.track.markers.endIndex + ForEach(markerRange, id:\.self) { markerIndex in + MarkerView(marker: self.track.markers[markerIndex], + secondsToPixels: self.$secondsToPixels) + } + } + } } From 54f871fe3dfb21fcfdde7bf1fcded23776f1b52a Mon Sep 17 00:00:00 2001 From: vade Date: Wed, 2 Jul 2025 00:47:05 -0400 Subject: [PATCH 14/67] Fix marker position on main stack Signed-off-by: vade Signed-off-by: Anton Marini --- .../Views/ContentView.swift | 1 + .../Views/ItemView.swift | 3 + .../Views/MarkerView.swift | 2 +- .../Views/TimeRulerView.swift | 17 ++++ .../Views/TimelineView.swift | 83 ++++++++++--------- 5 files changed, 66 insertions(+), 40 deletions(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ContentView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ContentView.swift index 647a297..a882cff 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ContentView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ContentView.swift @@ -65,6 +65,7 @@ struct ContentView: View self.controlsViewStack() } } + } detail: { ItemInspectorView(selectedItem: self.$selectedItem) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift index 635569e..21df9dc 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift @@ -54,6 +54,7 @@ struct ItemView : View { .frame(width: self.getSafeWidth(), alignment: .leading ) .overlay { self.getMarkerView() + } // .offset(x:self.getSafePositionX() )//, y:geometry.size.height * 0.5 ) } @@ -94,5 +95,7 @@ struct ItemView : View { secondsToPixels: self.$secondsToPixels) } } + .frame(width: self.getSafeWidth(), alignment: .leading ) + } } diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift index 426af0b..348d3ef 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift @@ -19,7 +19,7 @@ struct MarkerView : View { var body: some View { self.colorForMarker() - .frame(width: self.getSafeWidth(), height: 7.0, alignment: .leading ) + .frame(width: self.getSafeWidth(), height: 7.0 ) .offset(x:self.getSafePositionX() ) } diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift index f0c18f4..c80d6c5 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift @@ -23,6 +23,23 @@ struct TimeRulerView: View let startSeconds = safeRange.startTime.toSeconds() let endSeconds = safeRange.endTimeInclusive().toSeconds() + if let tracks = self.timeline.tracks + { + let markerRange = tracks.markers.startIndex ..< tracks.markers.endIndex + for markerIndex in markerRange + { + let marker = tracks.markers[markerIndex] + let x = marker.markedRange.startTime.toSeconds() * self.secondsToPixels// + + + let path = Path(roundedRect: CGRect(origin: CGPoint(x: x, y: 0), + size: CGSize(width: 5.0, height: 9.0)), + cornerRadius: 3.0) + + context.fill(path, with: .color(.red)) + } + } + + // Draw playhead drawPlayhead(context: context, currentTime: currentTime, secondsToPixels: secondsToPixels, size: size) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift index 7c6d599..b172d96 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift @@ -55,53 +55,58 @@ struct TimelineView : View { { let videoTracks = timeline.videoTracks let audioTracks = timeline.audioTracks - - VStack(alignment:.leading, spacing: 3) - { + + VStack(alignment:.leading, spacing: 3) + { TimeRulerView(timeline: self.timeline, secondsToPixels: self.$secondsToPixels, currentTime: self.$currentTime ) .frame(height: 40) .offset(x:100) - .overlay( self.getMarkerView( ) ) - - // +// .overlay( +// self.getMarkerView( ) +// ) + // + + ForEach(0.. some View - { - HStack(alignment: .top) - { - let markerRange = (self.timeline.tracks?.markers.startIndex ?? 0) ..< (self.timeline.tracks?.markers.endIndex ?? 0) - ForEach(markerRange, id:\.self) { markerIndex in - MarkerView(marker: self.timeline.tracks!.markers[markerIndex], - secondsToPixels: self.$secondsToPixels) + + Divider() + + ForEach(0.. some View +// { +// HStack(alignment: .top, spacing: 0) +// { +// let markerRange = (self.timeline.tracks?.markers.startIndex ?? 0) ..< (self.timeline.tracks?.markers.endIndex ?? 0) +// ForEach(markerRange, id:\.self) { markerIndex in +// MarkerView(marker: self.timeline.tracks!.markers[markerIndex], +// secondsToPixels: self.$secondsToPixels) +// } +// } +//// .frame(width: self.getSafeWidth(), alignment: .leading ) +// .frame(alignment: .leading) +// .border(Color.purple, width: 2) +// } } From fca8c669c155d2f192b489eb2684f756e8dfb4eb Mon Sep 17 00:00:00 2001 From: vade Date: Wed, 2 Jul 2025 01:08:44 -0400 Subject: [PATCH 15/67] Better marker drawing Signed-off-by: vade Signed-off-by: Anton Marini --- .../Views/TimeRulerView.swift | 74 ++++++++++++++----- 1 file changed, 56 insertions(+), 18 deletions(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift index c80d6c5..f7c3083 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift @@ -23,32 +23,70 @@ struct TimeRulerView: View let startSeconds = safeRange.startTime.toSeconds() let endSeconds = safeRange.endTimeInclusive().toSeconds() - if let tracks = self.timeline.tracks - { - let markerRange = tracks.markers.startIndex ..< tracks.markers.endIndex - for markerIndex in markerRange - { - let marker = tracks.markers[markerIndex] - let x = marker.markedRange.startTime.toSeconds() * self.secondsToPixels// + - - let path = Path(roundedRect: CGRect(origin: CGPoint(x: x, y: 0), - size: CGSize(width: 5.0, height: 9.0)), - cornerRadius: 3.0) - - context.fill(path, with: .color(.red)) - } - } + // Draw ticks (including frame-level ticks) + drawTicks(context: context, startSeconds: startSeconds, endSeconds: endSeconds, secondsToPixels: secondsToPixels, size: size) // Draw playhead drawPlayhead(context: context, currentTime: currentTime, secondsToPixels: secondsToPixels, size: size) - - // Draw ticks (including frame-level ticks) - drawTicks(context: context, startSeconds: startSeconds, endSeconds: endSeconds, secondsToPixels: secondsToPixels, size: size) + + drawMarkers(context: context, startSeconds: startSeconds, endSeconds: endSeconds, secondsToPixels: secondsToPixels, size: size) } .frame(width: self.getSafeWidth()) } + + func drawMarkers(context: GraphicsContext, startSeconds: Double, endSeconds: Double, secondsToPixels: Double, size: CGSize) + { + if let tracks = self.timeline.tracks + { + let markerRange = tracks.markers.startIndex ..< tracks.markers.endIndex + for markerIndex in markerRange + { + let marker = tracks.markers[markerIndex] + let x = marker.markedRange.startTime.toSeconds() * self.secondsToPixels// + + +// let path = Path(roundedRect: CGRect(origin: CGPoint(x: x, y: 13), +// size: CGSize(width: 3.0, height: 9.0)), +// cornerRadius: 1.0) +// +// context.fill(path, with: .color(.red)) + + let text = marker.name + + if #available(macOS 14.0, *) + { + context.draw( + Text("\(Image(systemName: "arrowtriangle.down.fill"))") + .font(.system(size: 10)) + .foregroundStyle(.red), + at: CGPoint(x: x + 0.5, y: 21)) + + + context.draw( + Text(text) + .font(.system(size: 10)) + .foregroundStyle(.white), + at: CGPoint(x: x , y: 12)) + } + else + { + context.draw( + Text("\(Image(systemName: "arrowtriangle.down.fill"))") + .font(.system(size: 10)) + .foregroundColor(.red), + at: CGPoint(x: x + 0.5, y: 21)) + + context.draw( + Text(text) + .font(.system(size: 10)) + .foregroundColor(.orange), + at: CGPoint(x: x , y: 12)) + } + } + } + } + func drawTicks(context: GraphicsContext, startSeconds: Double, endSeconds: Double, secondsToPixels: Double, size: CGSize) { if self.secondsToPixels > 75 From 432011b9d2332501d1b4e28054c25e0f22a0d4d4 Mon Sep 17 00:00:00 2001 From: vade Date: Wed, 2 Jul 2025 01:10:39 -0400 Subject: [PATCH 16/67] Xleanup Signed-off-by: vade Signed-off-by: Anton Marini --- .../OpenTimelineIO-Reader/Views/TimeRulerView.swift | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift index f7c3083..eff5af2 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift @@ -44,13 +44,7 @@ struct TimeRulerView: View for markerIndex in markerRange { let marker = tracks.markers[markerIndex] - let x = marker.markedRange.startTime.toSeconds() * self.secondsToPixels// + - -// let path = Path(roundedRect: CGRect(origin: CGPoint(x: x, y: 13), -// size: CGSize(width: 3.0, height: 9.0)), -// cornerRadius: 1.0) -// -// context.fill(path, with: .color(.red)) + let x = marker.markedRange.startTime.toSeconds() * self.secondsToPixels let text = marker.name From 800bd79cb485916e2de0bfc245f07b959c280276 Mon Sep 17 00:00:00 2001 From: vade Date: Wed, 2 Jul 2025 01:27:06 -0400 Subject: [PATCH 17/67] more tweaks Signed-off-by: vade Signed-off-by: Anton Marini --- .../Views/TimeRulerView.swift | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift index eff5af2..2f03379 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift @@ -38,6 +38,7 @@ struct TimeRulerView: View func drawMarkers(context: GraphicsContext, startSeconds: Double, endSeconds: Double, secondsToPixels: Double, size: CGSize) { + let y = 15.0 if let tracks = self.timeline.tracks { let markerRange = tracks.markers.startIndex ..< tracks.markers.endIndex @@ -54,14 +55,17 @@ struct TimeRulerView: View Text("\(Image(systemName: "arrowtriangle.down.fill"))") .font(.system(size: 10)) .foregroundStyle(.red), - at: CGPoint(x: x + 0.5, y: 21)) + at: CGPoint(x: x + 0.5, y: y), + ) context.draw( Text(text) .font(.system(size: 10)) - .foregroundStyle(.white), - at: CGPoint(x: x , y: 12)) + .foregroundStyle(.red), + at: CGPoint(x: x + 9 , y: y - 2.0), + anchor: UnitPoint(x: 0, y: 0.5) +) } else { @@ -69,14 +73,23 @@ struct TimeRulerView: View Text("\(Image(systemName: "arrowtriangle.down.fill"))") .font(.system(size: 10)) .foregroundColor(.red), - at: CGPoint(x: x + 0.5, y: 21)) + at: CGPoint(x: x + 0.5, y: y), + ) context.draw( Text(text) .font(.system(size: 10)) - .foregroundColor(.orange), - at: CGPoint(x: x , y: 12)) + .foregroundColor(.red), + at: CGPoint(x: x + 9 , y: y - 2.0), + anchor: UnitPoint(x: 0, y: 0.5) + ) } + + let tickHeight = 20.0 + + // Draw tick line + let tickRect = CGRect(x: x, y: size.height - tickHeight, width: 1, height: tickHeight) + context.fill(Path(tickRect), with: .color(.red)) } } } @@ -177,7 +190,7 @@ struct TimeRulerView: View context.draw( Text(currentTimeLabel) - .font(.system(size: 10)) + .font(.system(size: 10, weight: .bold)) .foregroundStyle(.orange), at: CGPoint(x: playheadPositionX, y: 5)) } @@ -191,7 +204,7 @@ struct TimeRulerView: View context.draw( Text(currentTimeLabel) - .font(.system(size: 10)) + .font(.system(size: 10, weight: .bold)) .foregroundColor(.orange), at: CGPoint(x: playheadPositionX, y: 5)) } From 170009b153ef19e42077d56408ccb1420225d05f Mon Sep 17 00:00:00 2001 From: vade Date: Wed, 2 Jul 2025 09:04:55 -0400 Subject: [PATCH 18/67] Nicer marker drawing Signed-off-by: vade Signed-off-by: Anton Marini --- .../Views/ContentView.swift | 4 +- .../Views/ItemView.swift | 66 ++++++++----------- .../Views/TimeRulerView.swift | 7 +- .../Views/TimelineView.swift | 35 +++------- .../Views/TrackView.swift | 11 ++-- 5 files changed, 48 insertions(+), 75 deletions(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ContentView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ContentView.swift index a882cff..66c887e 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ContentView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ContentView.swift @@ -33,9 +33,7 @@ struct ContentView: View self.fileURL = fileURL guard let fileURL = fileURL else { return } - - - + self.document.setupPlayerWithBaseDocumentURL(fileURL) } diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift index 21df9dc..cdca6fb 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift @@ -13,50 +13,42 @@ import SwiftUI struct ItemView : View { let item:OpenTimelineIO.Item - @State var backgroundColor:Color - @State var selected:Bool + let backgroundColor:Color + let selected:Bool + + private var isGap: Bool { + item.isKind(of: Gap.self) + } @Binding var secondsToPixels:Double var body: some View { - ZStack { - - if let _ = item as? Gap - { - RoundedRectangle(cornerRadius: 3) - .fill(Color("GapTrackBaseColor")) // Fill the RoundedRectangle with color - .overlay( - RoundedRectangle(cornerRadius: 3) - .stroke(self.selected ? .white : .clear, lineWidth: 1) // Add stroke/outline - ) - .frame(width: self.getSafeWidth() - 2) - } - else - { + ZStack + { + let fill:AnyShapeStyle = ( isGap ) ? AnyShapeStyle( Color("GapTrackBaseColor") ) : AnyShapeStyle(self.backgroundColor.gradient) + let textGapOpacity:Double = ( isGap ) ? 0.0 : 1.0 + + RoundedRectangle(cornerRadius: 3) + .fill(fill, style: FillStyle()) + .overlay( RoundedRectangle(cornerRadius: 3) - .fill(self.backgroundColor.gradient) // Fill the RoundedRectangle with color - .overlay( - RoundedRectangle(cornerRadius: 3) - .stroke(self.selected ? .white : .clear, lineWidth: 1) // Add stroke/outline - ) + .strokeBorder(self.selected ? .orange : .clear, lineWidth: 1) // Add stroke/outline .frame(width: self.getSafeWidth() - 2) - - if self.getSafeWidth() > 40 - { - Text(item.name) - .lineLimit(1) - .font(.system(size: 10)) - .frame(width: self.getSafeWidth()) - } - } - } - .frame(width: self.getSafeWidth(), alignment: .leading ) - .overlay { - self.getMarkerView() - - } -// .offset(x:self.getSafePositionX() )//, y:geometry.size.height * 0.5 ) + ) + .frame(width: self.getSafeWidth() - 2) + + Text(item.name) + .lineLimit(1) + .font(.system(size: 10)) + .frame(width: self.getSafeWidth()) + .opacity(self.getSafeWidth() > 40 ? textGapOpacity : 0.0) + } + .frame(width: self.getSafeWidth(), alignment: .leading ) + .overlay { + // TODO: Get item marker coordinate system working +// self.getMarkerView() + } } func getSafeRange() -> OpenTimelineIO.TimeRange diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift index 2f03379..3417277 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift @@ -23,7 +23,6 @@ struct TimeRulerView: View let startSeconds = safeRange.startTime.toSeconds() let endSeconds = safeRange.endTimeInclusive().toSeconds() - // Draw ticks (including frame-level ticks) drawTicks(context: context, startSeconds: startSeconds, endSeconds: endSeconds, secondsToPixels: secondsToPixels, size: size) @@ -38,7 +37,7 @@ struct TimeRulerView: View func drawMarkers(context: GraphicsContext, startSeconds: Double, endSeconds: Double, secondsToPixels: Double, size: CGSize) { - let y = 15.0 + let y = 20.0 if let tracks = self.timeline.tracks { let markerRange = tracks.markers.startIndex ..< tracks.markers.endIndex @@ -85,7 +84,7 @@ struct TimeRulerView: View ) } - let tickHeight = 20.0 + let tickHeight = 24.0 // Draw tick line let tickRect = CGRect(x: x, y: size.height - tickHeight, width: 1, height: tickHeight) @@ -177,7 +176,7 @@ struct TimeRulerView: View } let playheadPositionX = currentTime.toSeconds() * secondsToPixels - let playheadRect = CGRect(x: playheadPositionX, y: 20, width: 1, height: size.height-20) + let playheadRect = CGRect(x: playheadPositionX, y: 22, width: 1, height: size.height - 22) // context.fill(Path(playheadRect), with: .color(.orange)) if #available(macOS 14.0, *) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift index b172d96..d09055c 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift @@ -32,7 +32,7 @@ struct TimelineView : View { { self.timelineView() .allowsHitTesting(self.hitTestEnabled) - .drawingGroup(opaque: true) +// .drawingGroup(opaque: true) } .onScrollPhaseChange({ oldPhase, newPhase, context in @@ -40,6 +40,9 @@ struct TimelineView : View { self.hitTestEnabled = !newPhase.isScrolling }) + .frame(idealHeight: ( CGFloat((timeline.videoTracks.count + timeline.audioTracks.count)) * TrackView.trackHeight) + 100 ) + // .frame(maxHeight: CGFloat((videoTracks.count + audioTracks.count)) * 500) + } else { @@ -55,17 +58,12 @@ struct TimelineView : View { { let videoTracks = timeline.videoTracks let audioTracks = timeline.audioTracks - VStack(alignment:.leading, spacing: 3) { - TimeRulerView(timeline: self.timeline, secondsToPixels: self.$secondsToPixels, currentTime: self.$currentTime ) - .frame(height: 40) + TimeRulerView(timeline: self.timeline, secondsToPixels: self.$secondsToPixels, currentTime: self.$currentTime ) + .frame(height: 50) .offset(x:100) -// .overlay( -// self.getMarkerView( ) -// ) - // ForEach(0.. some View -// { -// HStack(alignment: .top, spacing: 0) -// { -// let markerRange = (self.timeline.tracks?.markers.startIndex ?? 0) ..< (self.timeline.tracks?.markers.endIndex ?? 0) -// ForEach(markerRange, id:\.self) { markerIndex in -// MarkerView(marker: self.timeline.tracks!.markers[markerIndex], -// secondsToPixels: self.$secondsToPixels) -// } -// } -//// .frame(width: self.getSafeWidth(), alignment: .leading ) -// .frame(alignment: .leading) -// .border(Color.purple, width: 2) -// } } diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TrackView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TrackView.swift index ec29d44..4336b1b 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TrackView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TrackView.swift @@ -14,8 +14,10 @@ import SwiftUI struct TrackView : View { + static let trackHeight:CGFloat = 35 + let track:OpenTimelineIO.Track - @State var backgroundColor:Color + let backgroundColor:Color @Binding var secondsToPixels:Double @Binding var selectedItem:Item? @@ -27,14 +29,13 @@ struct TrackView : View { Section(header: self.headerView() ) { - ForEach(0.. Date: Wed, 2 Jul 2025 17:30:14 -0400 Subject: [PATCH 19/67] Cleanup Signed-off-by: Anton Marini --- .../OpenTimelineIO-Reader/Views/ItemInspectorView.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemInspectorView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemInspectorView.swift index 7020708..633d253 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemInspectorView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemInspectorView.swift @@ -129,7 +129,6 @@ struct ItemInspectorView: View } .font(.system(size: 10)) .listRowSeparator(.hidden) - } func safeToJSON(item: Item) -> String From 3b30da98438ee431f4044b4ffb9e3404b6366c22 Mon Sep 17 00:00:00 2001 From: vade Date: Wed, 2 Jul 2025 17:36:18 -0400 Subject: [PATCH 20/67] Fix alignment of TimelineRuler canvas to allow for "overdraw" fixing #25 Signed-off-by: Anton Marini --- .../OpenTimelineIO-Reader/Views/TimeRulerView.swift | 7 ++++++- .../OpenTimelineIO-Reader/Views/TimelineView.swift | 6 +++--- .../OpenTimelineIO-Reader/Views/TrackView.swift | 5 +++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift index 3417277..440cd5f 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift @@ -11,6 +11,9 @@ import TimecodeKit import SwiftUI struct TimeRulerView: View { + + static let VerticalPadding:CGFloat = 100 + let timeline: OpenTimelineIO.Timeline @Binding var secondsToPixels: Double @Binding var currentTime: OpenTimelineIO.RationalTime @@ -19,6 +22,8 @@ struct TimeRulerView: View { Canvas { context, size in + context.translateBy(x: TrackView.trackHeaderWidth, y: 0) + let safeRange = getSafeRange() let startSeconds = safeRange.startTime.toSeconds() let endSeconds = safeRange.endTimeInclusive().toSeconds() @@ -32,7 +37,7 @@ struct TimeRulerView: View drawMarkers(context: context, startSeconds: startSeconds, endSeconds: endSeconds, secondsToPixels: secondsToPixels, size: size) } - .frame(width: self.getSafeWidth()) + .frame(width: self.getSafeWidth() + TrackView.trackHeaderWidth) } func drawMarkers(context: GraphicsContext, startSeconds: Double, endSeconds: Double, secondsToPixels: Double, size: CGSize) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift index d09055c..eb16a86 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift @@ -40,8 +40,7 @@ struct TimelineView : View { self.hitTestEnabled = !newPhase.isScrolling }) - .frame(idealHeight: ( CGFloat((timeline.videoTracks.count + timeline.audioTracks.count)) * TrackView.trackHeight) + 100 ) - // .frame(maxHeight: CGFloat((videoTracks.count + audioTracks.count)) * 500) + .frame(idealHeight: ( CGFloat((timeline.videoTracks.count + timeline.audioTracks.count)) * TrackView.trackHeight) + TimeRulerView.VerticalPadding ) } else @@ -51,6 +50,8 @@ struct TimelineView : View { { self.timelineView() } + .frame(idealHeight: ( CGFloat((timeline.videoTracks.count + timeline.audioTracks.count)) * TrackView.trackHeight) + TimeRulerView.VerticalPadding ) + } } @@ -63,7 +64,6 @@ struct TimelineView : View { { TimeRulerView(timeline: self.timeline, secondsToPixels: self.$secondsToPixels, currentTime: self.$currentTime ) .frame(height: 50) - .offset(x:100) ForEach(0.. CGFloat { - return self.getSafeRange().duration.toSeconds() * self.secondsToPixels + 100 + return self.getSafeRange().duration.toSeconds() * self.secondsToPixels + TrackView.trackHeaderWidth } func getSafePositionX() -> CGFloat From 3c7cc4dc0eb58e39b7d62a621ef6f0072c5b6055 Mon Sep 17 00:00:00 2001 From: Anton Marini Date: Wed, 2 Jul 2025 17:51:22 -0400 Subject: [PATCH 21/67] Fix error Signed-off-by: Anton Marini --- .../OpenTimelineIO-Reader/Views/TimeRulerView.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift index 440cd5f..aef8ba9 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift @@ -59,7 +59,7 @@ struct TimeRulerView: View Text("\(Image(systemName: "arrowtriangle.down.fill"))") .font(.system(size: 10)) .foregroundStyle(.red), - at: CGPoint(x: x + 0.5, y: y), + at: CGPoint(x: x + 0.5, y: y) ) @@ -77,7 +77,7 @@ struct TimeRulerView: View Text("\(Image(systemName: "arrowtriangle.down.fill"))") .font(.system(size: 10)) .foregroundColor(.red), - at: CGPoint(x: x + 0.5, y: y), + at: CGPoint(x: x + 0.5, y: y) ) context.draw( From 4a0ae697beece57b580df748b77960f4e84b3e1b Mon Sep 17 00:00:00 2001 From: Anton Marini Date: Thu, 3 Oct 2024 13:45:55 -0400 Subject: [PATCH 22/67] Update README.md Signed-off-by: vade --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f112e7a..0a1fc04 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@
OTIO Reader Icon -OTIO Reader Screenshot +image
Loading OpenTimeline IO files opened and playing in the included OpenTimelineIO Reader sample app:
From 79c5917ce972aec0c0577e0e40ee4f1dbd31e3e8 Mon Sep 17 00:00:00 2001 From: vade Date: Thu, 24 Oct 2024 11:20:25 -0400 Subject: [PATCH 23/67] We dont need @State as we arent editing content in the OTIO TImeline, Tracks or Items. Signed-off-by: vade --- .../OpenTimelineIO-Reader/Views/ItemView.swift | 11 +++++++---- .../OpenTimelineIO-Reader/Views/TimeRulerView.swift | 2 +- .../OpenTimelineIO-Reader/Views/TimelineView.swift | 2 +- .../OpenTimelineIO-Reader/Views/TrackView.swift | 8 +++++--- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift index f8064e9..18663cc 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift @@ -43,10 +43,13 @@ struct ItemView : View { ) .frame(width: self.getSafeWidth() - 2) - Text(item.name) - .lineLimit(1) - .font(.system(size: 10)) - .frame(width: self.getSafeWidth()) + if self.getSafeWidth() > 40 + { + Text(item.name) + .lineLimit(1) + .font(.system(size: 10)) + .frame(width: self.getSafeWidth()) + } } } .frame(width: self.getSafeWidth()) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift index f55125d..e972c36 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift @@ -11,7 +11,7 @@ import TimecodeKit import SwiftUI struct TimeRulerView: View { - var timeline: OpenTimelineIO.Timeline + let timeline: OpenTimelineIO.Timeline @Binding var secondsToPixels: Double @Binding var currentTime: OpenTimelineIO.RationalTime diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift index 1bfd2c2..91e475b 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift @@ -12,7 +12,7 @@ import SwiftUI struct TimelineView : View { - var timeline:OpenTimelineIO.Timeline + let timeline:OpenTimelineIO.Timeline @Binding var currentTime:RationalTime @Binding var secondsToPixels:Double diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TrackView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TrackView.swift index 67ddfe8..30e2caa 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TrackView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TrackView.swift @@ -11,9 +11,10 @@ import OpenTimelineIO import TimecodeKit import SwiftUI + struct TrackView : View { - @State var track:OpenTimelineIO.Track + let track:OpenTimelineIO.Track @State var backgroundColor:Color @Binding var secondsToPixels:Double @Binding var selectedItem:Item? @@ -26,12 +27,13 @@ struct TrackView : View { Section(header: self.headerView() ) { + ForEach(0.. Date: Thu, 24 Oct 2024 11:20:44 -0400 Subject: [PATCH 24/67] Same here Signed-off-by: vade --- .../OpenTimelineIO-Reader/Views/ItemView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift index 18663cc..6d1da6f 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift @@ -12,7 +12,7 @@ import SwiftUI struct ItemView : View { - @State var item:OpenTimelineIO.Item + let item:OpenTimelineIO.Item @State var backgroundColor:Color @State var selected:Bool From 773cbd17a6e3c53048e4e2954ebb288382e236f5 Mon Sep 17 00:00:00 2001 From: vade Date: Thu, 24 Oct 2024 11:54:48 -0400 Subject: [PATCH 25/67] Add scrollview optimization Signed-off-by: vade --- .../Views/TimelineView.swift | 46 ++++++++++++++++--- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift index 91e475b..6e13f09 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift @@ -18,20 +18,52 @@ struct TimelineView : View { @Binding var secondsToPixels:Double @Binding var selectedItem:Item? + @State private var hitTestEnabled:Bool = false + var body: some View { - let videoTracks = timeline.videoTracks - let audioTracks = timeline.audioTracks + // Lame scroll view optimizations. + // It seems as though hit testing for selection causes massive slowdowns in SwiftUI + // Due to how recursive hit testing happens + // We disable hit testing using macos 15 onScrollPhaseChange - ScrollView([.horizontal, .vertical]) + if #available(macOS 15.0, *) { + ScrollView([.horizontal, .vertical]) + { + self.timelineView() + .allowsHitTesting(self.hitTestEnabled) + .drawingGroup(opaque: true) + + } + .onScrollPhaseChange({ oldPhase, newPhase, context in + guard oldPhase != newPhase else { return } + + self.hitTestEnabled = !newPhase.isScrolling + }) + } + else { + // Fallback on earlier versions + ScrollView([.horizontal, .vertical]) + { + self.timelineView() + } + } + } + + func timelineView() -> some View + { + let videoTracks = timeline.videoTracks + let audioTracks = timeline.audioTracks + + return VStack(alignment:.leading, spacing: 3) { TimeRulerView(timeline: self.timeline, secondsToPixels: self.$secondsToPixels, currentTime: self.$currentTime ) .frame(height: 40) .offset(x:100) -// -// Divider() + // + // Divider() ForEach(0.. Date: Thu, 24 Oct 2024 12:50:43 -0400 Subject: [PATCH 26/67] Add placeholder media which we can add to our composition if we cant find, or we cant load Signed-off-by: vade --- .../OpenTimelineIO_ReaderDocument.swift | 1 - .../Preview Content/MediaNotFound.mp4 | Bin 0 -> 9930 bytes .../Preview Content/MediaNotSupported.mp4 | Bin 0 -> 13223 bytes .../ExternalReference.swift | 39 ++++++++- .../OpenTimelineIO-Extensions/Timeline.swift | 82 +++++++++--------- 5 files changed, 76 insertions(+), 46 deletions(-) create mode 100644 OpenTimelineIO-Sample/OpenTimelineIO-Reader/Preview Content/MediaNotFound.mp4 create mode 100644 OpenTimelineIO-Sample/OpenTimelineIO-Reader/Preview Content/MediaNotSupported.mp4 diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/OpenTimelineIO_ReaderDocument.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/OpenTimelineIO_ReaderDocument.swift index 485200c..123026c 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/OpenTimelineIO_ReaderDocument.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/OpenTimelineIO_ReaderDocument.swift @@ -88,7 +88,6 @@ class OpenTimelineIO_ReaderDocument: FileDocument, ObservableObject queue: .main, using: { [weak self] time in - print("update current time \(time)") self?.currentTime = time.toOTIORationalTime() }) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Preview Content/MediaNotFound.mp4 b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Preview Content/MediaNotFound.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..ce7642ce0bf0734442325e7499a4e63b6ca01aeb GIT binary patch literal 9930 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 literal 0 HcmV?d00001 diff --git a/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/ExternalReference.swift b/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/ExternalReference.swift index 79b4128..54b3465 100644 --- a/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/ExternalReference.swift +++ b/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/ExternalReference.swift @@ -53,7 +53,7 @@ extension ExternalReference if FileManager.default.fileExists(atPath: path) { let sourceURL = URL(filePath: path) - return AVURLAsset(url: sourceURL) + return self.tryLoadAssetAtResolvedURL(url: sourceURL) } else if let baseURL = baseURL { @@ -62,7 +62,7 @@ extension ExternalReference if FileManager.default.fileExists(atPath: sourceURL.path(percentEncoded: false)) { - return AVURLAsset(url: sourceURL) + return self.tryLoadAssetAtResolvedURL(url: sourceURL) } // we cant have a base url with a relative path to root dir... @@ -73,12 +73,43 @@ extension ExternalReference var sourceURL = baseURL.appending(path: pathWithoutRoot ) if FileManager.default.fileExists(atPath: sourceURL.path(percentEncoded: false)) { - return AVURLAsset(url: sourceURL) + return self.tryLoadAssetAtResolvedURL(url: sourceURL) } } } - return nil + let missingMediaURL = Bundle.main.url(forResource: "MediaNotFound", withExtension: "mp4")! + + return AVURLAsset(url: missingMediaURL) + } + + fileprivate func tryLoadAssetAtResolvedURL(url:URL) -> AVURLAsset + { + // do some very simple semantics to see if theres a chance we can load the asset + + let supported:Bool + + switch url.pathExtension + { + case "mp4": + supported = true + case "mov": + supported = true + + default: + supported = false + } + + if supported + { + return AVURLAsset(url: url) + } + + let notSupportedMedia = Bundle.main.url(forResource: "MediaNotSupported", withExtension: "mp4")! + + return AVURLAsset(url: notSupportedMedia) + } + } diff --git a/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/Timeline.swift b/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/Timeline.swift index 1d2e234..4117a16 100644 --- a/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/Timeline.swift +++ b/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/Timeline.swift @@ -80,30 +80,30 @@ public extension Timeline { // TODO: GAP !? -// if let gap = item as? Gap, -// let compositionVideoTrack = compositionVideoTrack -// { -// do -// { -// let gapTimeRange = try gap.rangeInParent().toCMTimeRange() -// compositionVideoTrack.insertEmptyTimeRange(gapTimeRange) -// -// let compositionLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: compositionVideoTrack) -// let compositionLayerInstructions = [compositionLayerInstruction] -// -// // Video Composition Instruction -// let compositionVideoInstruction = AVMutableVideoCompositionInstruction() -// compositionVideoInstruction.layerInstructions = compositionLayerInstructions -// compositionVideoInstruction.timeRange = gapTimeRange -// compositionVideoInstruction.enablePostProcessing = true -// compositionVideoInstruction.backgroundColor = NSColor.clear.cgColor -// compositionVideoInstructions.append( compositionVideoInstruction) -// } -// catch -// { -// continue -// } -// } + if let gap = item as? Gap, + let compositionVideoTrack = compositionVideoTrack + { + do + { + let gapTimeRange = try gap.rangeInParent().toCMTimeRange() + compositionVideoTrack.insertEmptyTimeRange(gapTimeRange) + + let compositionLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: compositionVideoTrack) + let compositionLayerInstructions = [compositionLayerInstruction] + + // Video Composition Instruction + let compositionVideoInstruction = AVMutableVideoCompositionInstruction() + compositionVideoInstruction.layerInstructions = compositionLayerInstructions + compositionVideoInstruction.timeRange = gapTimeRange + compositionVideoInstruction.enablePostProcessing = true + compositionVideoInstruction.backgroundColor = CGColor.black + compositionVideoInstructions.append( compositionVideoInstruction) + } + catch + { + continue + } + } continue } @@ -162,23 +162,23 @@ public extension Timeline else { // TODO: GAP !? -// if let gap = child as? Gap, -// let compositionAudioTrack = compositionAudioTrack -// { -// do -// { -// let gapTimeRange = try gap.rangeInParent().toCMTimeRange() -// compositionAudioTrack.insertEmptyTimeRange(gapTimeRange) -// -// let audioMixParams = AVMutableAudioMixInputParameters(track: compositionAudioTrack) -// -// compositionAudioMixParams.append(audioMixParams) -// } -// catch -// { -// continue -// } -// } + if let gap = child as? Gap, + let compositionAudioTrack = compositionAudioTrack + { + do + { + let gapTimeRange = try gap.rangeInParent().toCMTimeRange() + compositionAudioTrack.insertEmptyTimeRange(gapTimeRange) + + let audioMixParams = AVMutableAudioMixInputParameters(track: compositionAudioTrack) + + compositionAudioMixParams.append(audioMixParams) + } + catch + { + continue + } + } continue } From 47021944cc37992dfa254031cfbd93c9f0434a3e Mon Sep 17 00:00:00 2001 From: vade Date: Thu, 24 Oct 2024 12:51:16 -0400 Subject: [PATCH 27/67] Media Signed-off-by: vade --- .../{Preview Content => }/MediaNotFound.mp4 | Bin .../{Preview Content => }/MediaNotSupported.mp4 | Bin 2 files changed, 0 insertions(+), 0 deletions(-) rename OpenTimelineIO-Sample/OpenTimelineIO-Reader/{Preview Content => }/MediaNotFound.mp4 (100%) rename OpenTimelineIO-Sample/OpenTimelineIO-Reader/{Preview Content => }/MediaNotSupported.mp4 (100%) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Preview Content/MediaNotFound.mp4 b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/MediaNotFound.mp4 similarity index 100% rename from OpenTimelineIO-Sample/OpenTimelineIO-Reader/Preview Content/MediaNotFound.mp4 rename to OpenTimelineIO-Sample/OpenTimelineIO-Reader/MediaNotFound.mp4 diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Preview Content/MediaNotSupported.mp4 b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/MediaNotSupported.mp4 similarity index 100% rename from OpenTimelineIO-Sample/OpenTimelineIO-Reader/Preview Content/MediaNotSupported.mp4 rename to OpenTimelineIO-Sample/OpenTimelineIO-Reader/MediaNotSupported.mp4 From 42696665547bbb62e95dabce1ae2bb5ed146f8d5 Mon Sep 17 00:00:00 2001 From: Anton Marini Date: Thu, 24 Oct 2024 15:53:11 -0400 Subject: [PATCH 28/67] Fix case where OTIO references TC that is not available and calculate corrections to time for that media Signed-off-by: vade --- .../OpenTimelineIO-Extensions/Clip.swift | 34 ++++++++++++++++--- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/Clip.swift b/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/Clip.swift index c2453cc..889b204 100644 --- a/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/Clip.swift +++ b/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/Clip.swift @@ -15,13 +15,21 @@ extension Clip // see https://opentimelineio.readthedocs.io/en/latest/tutorials/time-ranges.html func toAVAssetAndMapping(baseURL:URL? = nil, useTimecode:Bool = true, rescaleToAsset:Bool = true) throws -> (asset:AVAsset, timeMaping:CMTimeMapping)? { - guard - let externalReference = self.mediaReference as? ExternalReference, - let asset = externalReference.toAVAsset(baseURL: baseURL) -// let parent = self.parent as? Item + + let asset:AVURLAsset + + if let externalReference = self.mediaReference as? ExternalReference, + let maybeAsset = externalReference.toAVAsset(baseURL: baseURL) + { + asset = maybeAsset + } else { - return nil + //see AWS Picchu Edit - Premiere cant import either? + //we have a generator or just a dead reference? + let missingMediaURL = Bundle.main.url(forResource: "MediaNotFound", withExtension: "mp4")! + + asset = AVURLAsset(url: missingMediaURL) } var timeRangeInAsset = try self.trimmedRange() @@ -67,6 +75,22 @@ extension Clip // let timeRangeInParentTrackNoTC = timeRangeInParentTrack.startTime - startTimeCode.cmTimeValue.toOTIORationalTime() // timeRangeInParentTrack = TimeRange(startTime: timeRangeInParentTrackNoTC, duration: timeRangeInParentTrack.duration) } + + // We might find ourselves with a situation where the timecode of the source media in the timeline existed, but we are working with proxies without TC + // This means we need to deduce if the time in OTIO differs from the assets and adjust accordingly + else if let firstVideoTrackStart = asset.tracks(withMediaType: .video).first + { + let assetAvailableTime = try self.availableRange() + + // If our start times differ... + if firstVideoTrackStart.timeRange.start.toOTIORationalTime() != assetAvailableTime.startTime + { + let timeDifference = assetAvailableTime.startTime - firstVideoTrackStart.timeRange.start.toOTIORationalTime() + + let assetStartTimeNoTC = timeRangeInAsset.startTime - timeDifference + timeRangeInAsset = TimeRange(startTime: assetStartTimeNoTC, duration: timeRangeInAsset.duration) + } + } } catch Timecode.MediaParseError.missingOrNonStandardFrameRate { From 6083439a38af2388ea70e03a28c23f61a5f37a53 Mon Sep 17 00:00:00 2001 From: Anton Marini Date: Thu, 24 Oct 2024 15:54:00 -0400 Subject: [PATCH 29/67] Reverse index video to match NLE track standards Signed-off-by: vade --- .../OpenTimelineIO-Reader/Views/TimelineView.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift index 6e13f09..1a22602 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift @@ -63,11 +63,11 @@ struct TimelineView : View { .frame(height: 40) .offset(x:100) // - // Divider() ForEach(0.. Date: Thu, 24 Oct 2024 15:54:58 -0400 Subject: [PATCH 30/67] Add heuristic to calculate the largest track size and use as our compositions raster size Signed-off-by: vade --- .../OpenTimelineIO-Extensions/Timeline.swift | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/Timeline.swift b/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/Timeline.swift index 4117a16..c1296e4 100644 --- a/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/Timeline.swift +++ b/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/Timeline.swift @@ -47,6 +47,11 @@ public extension Timeline func toAVCompositionRenderables(baseURL:URL? = nil, customCompositorClass:AVVideoCompositing.Type? = nil, useAssetTimecode:Bool = true, rescaleToAsset:Bool = true) async throws -> (composition:AVComposition, videoComposition:AVVideoComposition, audioMix:AVAudioMix)? { + // OTIO Timelines for the current schema version does not support a well described timeline format - ie no resolution or framerate + // We deduce this via a heuristic here... + + var largestRasterSizeFound = CGSize.zero + let validator = VideoCompositionValidator() // Get our global offset - if we have one, to normalize track times @@ -125,6 +130,11 @@ public extension Timeline } } + largestRasterSizeFound = CGSize(width: max(largestRasterSizeFound.width , compositionVideoTrack.naturalSize.width), + height: max(largestRasterSizeFound.height , compositionVideoTrack.naturalSize.height) + ) + + // TODO: Fix - Support Time Scaling // let unscaledTrackTime = CMTimeRangeMake(start: trackTimeRange.start, duration: sourceAssetTimeRange.duration) // compositionVideoTrack.scaleTimeRange(unscaledTrackTime, toDuration: trackTimeRange.duration) @@ -226,8 +236,9 @@ public extension Timeline } let videoComposition = try await AVMutableVideoComposition.videoComposition(withPropertiesOf: composition) + // TODO: - Custom Resolution overrides? - // videoComposition.renderSize = CGSize(width: 1920, height: 1080) + videoComposition.renderSize = largestRasterSizeFound //CGSize(width: 1920, height: 1080) videoComposition.renderScale = 1.0 // TODO: It seems as though our custom instructions occasionally have a minor time gap From 72a3e79903608d9412ce604c50568a2308c5e39f Mon Sep 17 00:00:00 2001 From: Anton Marini Date: Thu, 24 Oct 2024 15:55:15 -0400 Subject: [PATCH 31/67] Update gap calculations Signed-off-by: vade --- .../OpenTimelineIO-Extensions/Timeline.swift | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/Timeline.swift b/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/Timeline.swift index c1296e4..f42cecc 100644 --- a/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/Timeline.swift +++ b/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/Timeline.swift @@ -83,16 +83,17 @@ public extension Timeline let compositionVideoTrack = compositionVideoTrack //composition.mutableTrack(compatibleWith: sourceAssetFirstVideoTrack) ?? else { - // TODO: GAP !? if let gap = item as? Gap, let compositionVideoTrack = compositionVideoTrack { do { - let gapTimeRange = try gap.rangeInParent().toCMTimeRange() + let gapTimeRangeOTIO = try gap.trimmedRangeInParent() ?? gap.rangeInParent() + let gapTimeRange = gapTimeRangeOTIO.toCMTimeRange() compositionVideoTrack.insertEmptyTimeRange(gapTimeRange) - + compositionVideoTrack.preferredTransform = .identity + let compositionLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: compositionVideoTrack) let compositionLayerInstructions = [compositionLayerInstruction] @@ -100,8 +101,8 @@ public extension Timeline let compositionVideoInstruction = AVMutableVideoCompositionInstruction() compositionVideoInstruction.layerInstructions = compositionLayerInstructions compositionVideoInstruction.timeRange = gapTimeRange - compositionVideoInstruction.enablePostProcessing = true - compositionVideoInstruction.backgroundColor = CGColor.black + compositionVideoInstruction.enablePostProcessing = false + compositionVideoInstruction.backgroundColor = CGColor(gray: 0, alpha: 0) compositionVideoInstructions.append( compositionVideoInstruction) } catch @@ -109,6 +110,11 @@ public extension Timeline continue } } + else + { + print("Got unsupported Item type") + } + continue } @@ -153,7 +159,7 @@ public extension Timeline compositionVideoInstruction.layerInstructions = compositionLayerInstructions compositionVideoInstruction.timeRange = trackTimeRange compositionVideoInstruction.enablePostProcessing = true - compositionVideoInstruction.backgroundColor = CGColor(gray: 0, alpha: 1) + compositionVideoInstruction.backgroundColor = CGColor(gray: 0, alpha: 0) compositionVideoInstructions.append( compositionVideoInstruction) } } From 17d4b626b55d16a67382d65330ea70339b1183ca Mon Sep 17 00:00:00 2001 From: Anton Marini Date: Thu, 24 Oct 2024 17:53:17 -0400 Subject: [PATCH 32/67] Add defensive checks to our missing asset to ensure we only add it if our track matches the media type Signed-off-by: vade --- .../OpenTimelineIO-Extensions/Clip.swift | 10 ++++++++-- .../OpenTimelineIO-Extensions/Timeline.swift | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/Clip.swift b/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/Clip.swift index 889b204..5545f37 100644 --- a/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/Clip.swift +++ b/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/Clip.swift @@ -13,7 +13,7 @@ import TimecodeKit extension Clip { // see https://opentimelineio.readthedocs.io/en/latest/tutorials/time-ranges.html - func toAVAssetAndMapping(baseURL:URL? = nil, useTimecode:Bool = true, rescaleToAsset:Bool = true) throws -> (asset:AVAsset, timeMaping:CMTimeMapping)? + func toAVAssetAndMapping(baseURL:URL? = nil, trackType:AVMediaType, useTimecode:Bool = true, rescaleToAsset:Bool = true) throws -> (asset:AVAsset, timeMaping:CMTimeMapping)? { let asset:AVURLAsset @@ -21,6 +21,9 @@ extension Clip if let externalReference = self.mediaReference as? ExternalReference, let maybeAsset = externalReference.toAVAsset(baseURL: baseURL) { + + guard !maybeAsset.tracks(withMediaType: trackType).isEmpty else { return nil } + asset = maybeAsset } else @@ -28,8 +31,11 @@ extension Clip //see AWS Picchu Edit - Premiere cant import either? //we have a generator or just a dead reference? let missingMediaURL = Bundle.main.url(forResource: "MediaNotFound", withExtension: "mp4")! + let missingAsset = AVURLAsset(url: missingMediaURL) + + guard !missingAsset.tracks(withMediaType: trackType).isEmpty else { return nil } - asset = AVURLAsset(url: missingMediaURL) + asset = missingAsset } var timeRangeInAsset = try self.trimmedRange() diff --git a/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/Timeline.swift b/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/Timeline.swift index f42cecc..da5ea06 100644 --- a/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/Timeline.swift +++ b/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/Timeline.swift @@ -78,7 +78,7 @@ public extension Timeline { guard let clip = item as? Clip, - let (sourceAsset, clipTimeMapping) = try clip.toAVAssetAndMapping(baseURL: baseURL, useTimecode: useAssetTimecode, rescaleToAsset: rescaleToAsset), + let (sourceAsset, clipTimeMapping) = try clip.toAVAssetAndMapping(baseURL: baseURL, trackType:.video, useTimecode: useAssetTimecode, rescaleToAsset: rescaleToAsset), let sourceAssetFirstVideoTrack = try await sourceAsset.loadTracks(withMediaType: .video).first, let compositionVideoTrack = compositionVideoTrack //composition.mutableTrack(compatibleWith: sourceAssetFirstVideoTrack) ?? else From fffe9abdb816104ecec44eb3d4cfca4975ca814c Mon Sep 17 00:00:00 2001 From: Anton Marini Date: Thu, 24 Oct 2024 17:53:33 -0400 Subject: [PATCH 33/67] This is admittedly lame. Signed-off-by: vade --- .../ExternalReference.swift | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/ExternalReference.swift b/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/ExternalReference.swift index 54b3465..834447f 100644 --- a/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/ExternalReference.swift +++ b/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/ExternalReference.swift @@ -95,6 +95,19 @@ extension ExternalReference supported = true case "mov": supported = true + case "m4v": + supported = true + + + case "m4a": + supported = true + case "mp3": + supported = true + case "aiff": + supported = true + case "wav": + supported = true + default: supported = false From 4b6ef7e77d80011ffd131f326663bb76b240049e Mon Sep 17 00:00:00 2001 From: Anton Marini Date: Thu, 24 Oct 2024 17:54:27 -0400 Subject: [PATCH 34/67] update our AVComposition building to ensure we disregard zero time edits which seem to appear in some content. We now can match Premieres integration of OTIO? Signed-off-by: vade --- .../OpenTimelineIO-Extensions/Timeline.swift | 60 ++++++++++++++++--- 1 file changed, 51 insertions(+), 9 deletions(-) diff --git a/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/Timeline.swift b/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/Timeline.swift index da5ea06..7ccc362 100644 --- a/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/Timeline.swift +++ b/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/Timeline.swift @@ -91,6 +91,14 @@ public extension Timeline { let gapTimeRangeOTIO = try gap.trimmedRangeInParent() ?? gap.rangeInParent() let gapTimeRange = gapTimeRangeOTIO.toCMTimeRange() + + guard + gapTimeRange.duration != .zero + else + { + continue + } + compositionVideoTrack.insertEmptyTimeRange(gapTimeRange) compositionVideoTrack.preferredTransform = .identity @@ -122,6 +130,13 @@ public extension Timeline let trackTimeRange = clipTimeMapping.source let sourceAssetTimeRange = clipTimeMapping.target + guard trackTimeRange.duration != .zero, + sourceAssetTimeRange.duration != .zero + else + { + continue + } + // We attempt to re-use a track per OTIO track, but we may have CMFormatDesc inconsistencies which means insertion will fails // If so - we make a new one do @@ -130,7 +145,7 @@ public extension Timeline } catch { - if let compositionVideoTrack = composition.mutableTrack(compatibleWith: sourceAssetFirstVideoTrack) ?? composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid) + if let compositionVideoTrack = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid) { try compositionVideoTrack.insertTimeRange(sourceAssetTimeRange, of: sourceAssetFirstVideoTrack, at: trackTimeRange.start) } @@ -172,7 +187,7 @@ public extension Timeline { guard let clip = child as? Clip, - let (sourceAsset, clipTimeMapping) = try clip.toAVAssetAndMapping(baseURL: baseURL, useTimecode: useAssetTimecode, rescaleToAsset: rescaleToAsset), + let (sourceAsset, clipTimeMapping) = try clip.toAVAssetAndMapping(baseURL: baseURL, trackType: .audio, useTimecode: useAssetTimecode, rescaleToAsset: rescaleToAsset), let sourceAssetFirstAudioTrack = try await sourceAsset.loadTracks(withMediaType: .audio).first, let compositionAudioTrack = compositionAudioTrack else @@ -183,7 +198,16 @@ public extension Timeline { do { - let gapTimeRange = try gap.rangeInParent().toCMTimeRange() + let gapTimeRangeOTIO = try gap.trimmedRangeInParent() ?? gap.rangeInParent() + let gapTimeRange = gapTimeRangeOTIO.toCMTimeRange() + + guard + gapTimeRange.duration != .zero + else + { + continue + } + compositionAudioTrack.insertEmptyTimeRange(gapTimeRange) let audioMixParams = AVMutableAudioMixInputParameters(track: compositionAudioTrack) @@ -202,6 +226,13 @@ public extension Timeline let trackTimeRange = clipTimeMapping.source let sourceAssetTimeRange = clipTimeMapping.target + guard trackTimeRange.duration != .zero, + sourceAssetTimeRange.duration != .zero + else + { + continue + } + // We attempt to re-use a track per OTIO track, but we may have CMFormatDesc inconsistencies which means insertion will fails // If so - we make a new one do @@ -210,9 +241,15 @@ public extension Timeline } catch { - if let compositionAudioTrack = composition.mutableTrack(compatibleWith: sourceAssetFirstAudioTrack) ?? composition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid) + if let compositionAudioTrack = composition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid) { - try compositionAudioTrack.insertTimeRange(sourceAssetTimeRange, of: sourceAssetFirstAudioTrack, at: trackTimeRange.start) + do { + try compositionAudioTrack.insertTimeRange(sourceAssetTimeRange, of: sourceAssetFirstAudioTrack, at: trackTimeRange.start) + + } + catch { + print("swallowing traack insertion error \(error)") + } } } @@ -220,8 +257,6 @@ public extension Timeline // let unscaledTrackTime = CMTimeRangeMake(start: trackTimeRange.start, duration: sourceAssetTimeRange.duration) // compositionAudioTrack.scaleTimeRange(unscaledTrackTime, toDuration: trackTimeRange.duration) - compositionAudioTrack.isEnabled = try await sourceAssetFirstAudioTrack.load(.isEnabled) - // TODO: a few milliseconds fade up / fade out to avoid pops let audioMixParams = AVMutableAudioMixInputParameters(track: compositionAudioTrack) @@ -229,9 +264,16 @@ public extension Timeline } } + + // Composition Validation for track in composition.tracks { + if track.segments.isEmpty + { + composition.removeTrack(track) + } + do { try track.validateSegments(track.segments) } @@ -246,7 +288,8 @@ public extension Timeline // TODO: - Custom Resolution overrides? videoComposition.renderSize = largestRasterSizeFound //CGSize(width: 1920, height: 1080) videoComposition.renderScale = 1.0 - + audioMix.inputParameters = compositionAudioMixParams + // TODO: It seems as though our custom instructions occasionally have a minor time gap // likely due to numerical conversion precision which throws a validation error // Im not entirely sure what to do there! @@ -261,7 +304,6 @@ public extension Timeline // Video Composition Validation try await videoComposition.isValid(for: composition, timeRange: CMTimeRange(start: .zero, end: composition.duration), validationDelegate:validator) - audioMix.inputParameters = compositionAudioMixParams return (composition:composition, videoComposition:videoComposition, audioMix:audioMix) } From 680b20ca8546966231c648a8e7d837d904c2e9ba Mon Sep 17 00:00:00 2001 From: Anton Marini Date: Thu, 24 Oct 2024 17:54:46 -0400 Subject: [PATCH 35/67] Update our zoom scale Signed-off-by: vade --- .../OpenTimelineIO-Reader/Views/ContentView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ContentView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ContentView.swift index 3d99bd1..647a297 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ContentView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ContentView.swift @@ -140,7 +140,7 @@ struct ContentView: View .lineLimit(1) .font(.system(size: 10)) - Slider(value: $secondsToPixels, in: 10...1000) + Slider(value: $secondsToPixels, in: 1...1000) .controlSize(.mini) .frame(width: 200) } From 278a4aa2c3818fed27937239ea814310b6c6c0c6 Mon Sep 17 00:00:00 2001 From: Anton Marini Date: Thu, 24 Oct 2024 17:54:57 -0400 Subject: [PATCH 36/67] Beta Signed-off-by: vade --- .../OpenTimelineIO-Reader.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader.xcodeproj/project.pbxproj b/OpenTimelineIO-Sample/OpenTimelineIO-Reader.xcodeproj/project.pbxproj index 70f93c6..96109f5 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader.xcodeproj/project.pbxproj +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader.xcodeproj/project.pbxproj @@ -178,7 +178,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "OpenTimelineIO-Reader/OpenTimelineIO_Reader.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_ASSET_PATHS = "\"OpenTimelineIO-Reader/Preview Content\""; DEVELOPMENT_TEAM = SHG3AW6YV7; ENABLE_HARDENED_RUNTIME = YES; @@ -201,7 +201,7 @@ LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 13.5; - MARKETING_VERSION = "1.0 Beta 2"; + MARKETING_VERSION = "1.0 Beta 3"; PRODUCT_BUNDLE_IDENTIFIER = "ai.ozu.OpenTimelineIO-Reader"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; @@ -220,7 +220,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "OpenTimelineIO-Reader/OpenTimelineIO_Reader.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_ASSET_PATHS = "\"OpenTimelineIO-Reader/Preview Content\""; DEVELOPMENT_TEAM = SHG3AW6YV7; ENABLE_HARDENED_RUNTIME = YES; @@ -243,7 +243,7 @@ LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 13.5; - MARKETING_VERSION = "1.0 Beta 2"; + MARKETING_VERSION = "1.0 Beta 3"; PRODUCT_BUNDLE_IDENTIFIER = "ai.ozu.OpenTimelineIO-Reader"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; From 14670b9ff9b1d06bac3102a554395b1e050df658 Mon Sep 17 00:00:00 2001 From: Anton Marini Date: Thu, 24 Oct 2024 18:13:13 -0400 Subject: [PATCH 37/67] Update README.md Signed-off-by: vade --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0a1fc04..d2114ed 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,12 @@
OTIO Reader Icon -image +image
Loading OpenTimeline IO files opened and playing in the included OpenTimelineIO Reader sample app:
- - ## Note @@ -128,7 +126,7 @@ See roadmap for transitions / effects. | Sample Project | Import to AVFoundation | Export from AVFoundation -- | -- | -- | [ALab Trailer](https://dpel.aswf.io/alab-trailer/) | :white_check_mark: | :white_check_mark: -| [AWS Picchu Edit](https://dpel.aswf.io/aws-picchu-edit/) | :x: (requires image support & OTIOZ support) | Not yet tested +| [AWS Picchu Edit](https://dpel.aswf.io/aws-picchu-edit/) | :white_check_mark: (Decompress .otioz zip file and open the bundled .otio file) | :white_check_mark: | [OTIO-OC-Examples](https://github.com/darbyjohnston/otio-oc-examples) | :white_check_mark: | :white_check_mark: ### Video Format Compatibility From ff78d3492ffa7a8f7a576c7e91e14efccd40ce9f Mon Sep 17 00:00:00 2001 From: Anton Marini Date: Thu, 31 Oct 2024 12:32:00 -0400 Subject: [PATCH 38/67] Fix bug in URL percent encoding handling with paths / file lookup causing false negatives Signed-off-by: vade --- .../OpenTimelineIO-Extensions/ExternalReference.swift | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/ExternalReference.swift b/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/ExternalReference.swift index 834447f..529fb6a 100644 --- a/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/ExternalReference.swift +++ b/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/ExternalReference.swift @@ -23,9 +23,14 @@ extension ExternalReference if targetURL.hasPrefix("file://") { + if let fileURL = URL(string: targetURL), + let asset = self.testForAsset(url:fileURL, baseURL: baseURL) + { + return asset + } + let fileURL = URL(fileURLWithPath:targetURL.replacingOccurrences(of:"file://", with: "./")) - if - let asset = self.testForAsset(url:fileURL, baseURL:baseURL) + if let asset = self.testForAsset(url:fileURL, baseURL:baseURL) { return asset } @@ -45,7 +50,7 @@ extension ExternalReference fileprivate func testForAsset(url:URL, baseURL:URL?) -> AVURLAsset? { - return self.testForAsset(path: url.standardizedFileURL.absoluteURL.path(), baseURL: baseURL) + return self.testForAsset(path: url.standardizedFileURL.absoluteURL.path(percentEncoded: false), baseURL: baseURL) } fileprivate func testForAsset(path:String, baseURL:URL?) -> AVURLAsset? From 00a0cd8236361d4f626872f5268cb9c878e7afb0 Mon Sep 17 00:00:00 2001 From: vade Date: Thu, 31 Oct 2024 13:37:18 -0400 Subject: [PATCH 39/67] Allow default hit testing Signed-off-by: vade --- .../OpenTimelineIO-Reader/Views/TimelineView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift index 1a22602..6b9694f 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift @@ -18,7 +18,7 @@ struct TimelineView : View { @Binding var secondsToPixels:Double @Binding var selectedItem:Item? - @State private var hitTestEnabled:Bool = false + @State private var hitTestEnabled:Bool = true var body: some View { From d571523e2eaf9ea01947f97d3bbb5b9dcf89a6c5 Mon Sep 17 00:00:00 2001 From: vade Date: Thu, 31 Oct 2024 13:37:54 -0400 Subject: [PATCH 40/67] Attempt to fix a timing ambiguity / interpretation issue in the Premiere Pro OTIO usage of global start time. Signed-off-by: vade --- .../AVComposition.swift | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Sources/OpenTimelineIO-AVFoundation/AVFoundation-Extensions/AVComposition.swift b/Sources/OpenTimelineIO-AVFoundation/AVFoundation-Extensions/AVComposition.swift index 612ed41..bc5b84e 100644 --- a/Sources/OpenTimelineIO-AVFoundation/AVFoundation-Extensions/AVComposition.swift +++ b/Sources/OpenTimelineIO-AVFoundation/AVFoundation-Extensions/AVComposition.swift @@ -32,7 +32,24 @@ public extension AVComposition { print("Making Timeline from Composition", self) - let timeline = Timeline(name: name, globalStartTime: config.globalStartTime ) + // Today OTIO has no way of describing frame rates + // So some integrations presume the rate of the global start time + // implies the frame rate of the sequence or project + + let minFrameDuration = self.tracks(withMediaType: .video).reduce(.positiveInfinity) { min($0, $1.minFrameDuration) } + + let globalStartTime:RationalTime + + if minFrameDuration.isValid && minFrameDuration != .positiveInfinity + { + globalStartTime = config.globalStartTime.rescaled(to: Double(minFrameDuration.timescale) ) + } + else + { + globalStartTime = config.globalStartTime + } + + let timeline = Timeline(name: name, globalStartTime: globalStartTime ) let all_tracks:[Track] = try self.tracks.compactMap { try $0.toOTIOTrack(config: config) } From 0cef719d95beb00f28d8f0be69cdfb22423ea845 Mon Sep 17 00:00:00 2001 From: vade Date: Thu, 31 Oct 2024 13:56:12 -0400 Subject: [PATCH 41/67] Update our time scale logic to better reflect a semantic that OTIO expects. Signed-off-by: vade --- .../AVFoundation-Extensions/AVComposition.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/OpenTimelineIO-AVFoundation/AVFoundation-Extensions/AVComposition.swift b/Sources/OpenTimelineIO-AVFoundation/AVFoundation-Extensions/AVComposition.swift index bc5b84e..12bbc3a 100644 --- a/Sources/OpenTimelineIO-AVFoundation/AVFoundation-Extensions/AVComposition.swift +++ b/Sources/OpenTimelineIO-AVFoundation/AVFoundation-Extensions/AVComposition.swift @@ -36,13 +36,13 @@ public extension AVComposition // So some integrations presume the rate of the global start time // implies the frame rate of the sequence or project - let minFrameDuration = self.tracks(withMediaType: .video).reduce(.positiveInfinity) { min($0, $1.minFrameDuration) } + let maxNominalFrameRate = self.tracks(withMediaType: .video).reduce(Float.zero) { max($0, $1.nominalFrameRate) } let globalStartTime:RationalTime - if minFrameDuration.isValid && minFrameDuration != .positiveInfinity + if maxNominalFrameRate != .zero { - globalStartTime = config.globalStartTime.rescaled(to: Double(minFrameDuration.timescale) ) + globalStartTime = config.globalStartTime.rescaled(to: Double(maxNominalFrameRate) ) } else { From 0b3a73dc998c60869cbbd2aa2562811bd4593c4e Mon Sep 17 00:00:00 2001 From: Anton Marini Date: Thu, 7 Nov 2024 15:02:44 -0500 Subject: [PATCH 42/67] Add defensive checks for min frame durations to be valid and non zero, fixing some MXF introspection bugs. Signed-off-by: vade --- .../OpenTimelineIO-Extensions/Clip.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/Clip.swift b/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/Clip.swift index 5545f37..75b7d5a 100644 --- a/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/Clip.swift +++ b/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/Clip.swift @@ -43,7 +43,9 @@ extension Clip var minFrameDuration:RationalTime? = nil if let videoTrack = asset.tracks(withMediaType: .video).first, - rescaleToAsset + rescaleToAsset, + videoTrack.minFrameDuration.isValid, + videoTrack.minFrameDuration != .zero { minFrameDuration = videoTrack.minFrameDuration.toOTIORationalTime() } From 0661178cfe93fe3aaf90d5003ff92f5cfcd122e3 Mon Sep 17 00:00:00 2001 From: Anton Marini Date: Thu, 7 Nov 2024 15:03:13 -0500 Subject: [PATCH 43/67] Add a fix for mxf support. Signed-off-by: vade --- .../OpenTimelineIO-Extensions/ExternalReference.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/ExternalReference.swift b/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/ExternalReference.swift index 529fb6a..5ecfefd 100644 --- a/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/ExternalReference.swift +++ b/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/ExternalReference.swift @@ -102,7 +102,9 @@ extension ExternalReference supported = true case "m4v": supported = true - + case "mxf": + supported = true + case "m4a": supported = true From 26a8083f053e1437080d8ede0b7249b3208f9fbf Mon Sep 17 00:00:00 2001 From: Anton Marini Date: Thu, 7 Nov 2024 15:03:44 -0500 Subject: [PATCH 44/67] Add a fix to discover local files that may be referenced off of an otio exported from a different machine with different root file paths. Signed-off-by: vade --- .../ExternalReference.swift | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/ExternalReference.swift b/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/ExternalReference.swift index 5ecfefd..58776b7 100644 --- a/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/ExternalReference.swift +++ b/Sources/OpenTimelineIO-AVFoundation/OpenTimelineIO-Extensions/ExternalReference.swift @@ -81,8 +81,23 @@ extension ExternalReference return self.tryLoadAssetAtResolvedURL(url: sourceURL) } } + + // edge case - + // if a OTIO file was packaged with media but paths werent linked, we should check CWD for the file + // as a last case attempt. + if let filename = path.components(separatedBy: "/").last + { + let newFileURL = baseURL.appending(component: filename) + + return self.tryLoadAssetAtResolvedURL(url: newFileURL) + } + + } + + + let missingMediaURL = Bundle.main.url(forResource: "MediaNotFound", withExtension: "mp4")! return AVURLAsset(url: missingMediaURL) @@ -122,12 +137,12 @@ extension ExternalReference if supported { - return AVURLAsset(url: url) + return AVURLAsset(url: url, options: [AVURLAssetPreferPreciseDurationAndTimingKey : true]) } let notSupportedMedia = Bundle.main.url(forResource: "MediaNotSupported", withExtension: "mp4")! - return AVURLAsset(url: notSupportedMedia) + return AVURLAsset(url: notSupportedMedia, options: [AVURLAssetPreferPreciseDurationAndTimingKey : true]) } From dcb6c37ba2a692ebb47a7fc523b5c9315be46abf Mon Sep 17 00:00:00 2001 From: Anton Marini Date: Thu, 7 Nov 2024 15:06:31 -0500 Subject: [PATCH 45/67] Scheme was missing for some reason? Signed-off-by: vade --- .../xcschemes/OpenTimelineIO-Reader.xcscheme | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 OpenTimelineIO-Sample/OpenTimelineIO-Reader.xcodeproj/xcshareddata/xcschemes/OpenTimelineIO-Reader.xcscheme diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader.xcodeproj/xcshareddata/xcschemes/OpenTimelineIO-Reader.xcscheme b/OpenTimelineIO-Sample/OpenTimelineIO-Reader.xcodeproj/xcshareddata/xcschemes/OpenTimelineIO-Reader.xcscheme new file mode 100644 index 0000000..7d8e34f --- /dev/null +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader.xcodeproj/xcshareddata/xcschemes/OpenTimelineIO-Reader.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From fa385273351ecc14664d52977758513ec13a40d7 Mon Sep 17 00:00:00 2001 From: Anton Marini Date: Thu, 7 Nov 2024 15:07:25 -0500 Subject: [PATCH 46/67] Beta 4 Signed-off-by: vade --- .../OpenTimelineIO-Reader.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader.xcodeproj/project.pbxproj b/OpenTimelineIO-Sample/OpenTimelineIO-Reader.xcodeproj/project.pbxproj index 96109f5..f4b1feb 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader.xcodeproj/project.pbxproj +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader.xcodeproj/project.pbxproj @@ -178,7 +178,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "OpenTimelineIO-Reader/OpenTimelineIO_Reader.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 4; DEVELOPMENT_ASSET_PATHS = "\"OpenTimelineIO-Reader/Preview Content\""; DEVELOPMENT_TEAM = SHG3AW6YV7; ENABLE_HARDENED_RUNTIME = YES; @@ -201,7 +201,7 @@ LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 13.5; - MARKETING_VERSION = "1.0 Beta 3"; + MARKETING_VERSION = "1.0 Beta 4"; PRODUCT_BUNDLE_IDENTIFIER = "ai.ozu.OpenTimelineIO-Reader"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; @@ -220,7 +220,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "OpenTimelineIO-Reader/OpenTimelineIO_Reader.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 4; DEVELOPMENT_ASSET_PATHS = "\"OpenTimelineIO-Reader/Preview Content\""; DEVELOPMENT_TEAM = SHG3AW6YV7; ENABLE_HARDENED_RUNTIME = YES; @@ -243,7 +243,7 @@ LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 13.5; - MARKETING_VERSION = "1.0 Beta 3"; + MARKETING_VERSION = "1.0 Beta 4"; PRODUCT_BUNDLE_IDENTIFIER = "ai.ozu.OpenTimelineIO-Reader"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; From af8cf304a9d0e2ce236f7f3855b598fb25efbfc5 Mon Sep 17 00:00:00 2001 From: Anton Marini Date: Thu, 7 Nov 2024 15:17:09 -0500 Subject: [PATCH 47/67] Update README.md add mxf codec notes Signed-off-by: vade --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d2114ed..0f88a17 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,14 @@ Generally if Quicktime or Final Cut Pro X Can support it, it should just work. | Apple ProRes | :white_check_mark: | Requires [Apple Pro Video Formats](https://support.apple.com/kb/DL2100?locale=en_US) for some variants | | Apple Intermediate Codec | :white_check_mark: | Requires [Apple Pro Video Formats](https://support.apple.com/kb/DL2100?locale=en_US) for some variants | MXF wrapped Pro Res | :white_check_mark: | For developers: Requires `MTRegisterProfessionalVideoWorkflowFormatReaders()` and `VTRegisterProfessionalVideoWorkflowVideoDecoders()` to be enabled| -| MXF Wrapped DNx | :white_check_mark: | Requires [Apple Pro Video Formats](https://support.apple.com/kb/DL2100?locale=en_US) for Avid DNxHD® / Avid DNxHR®. For developers: Requires `MTRegisterProfessionalVideoWorkflowFormatReaders()` and `VTRegisterProfessionalVideoWorkflowVideoDecoders()` to be enabled - only some DNx variants work| +| MXF Wrapped DNx | :white_check_mark: | Requires [Apple Pro Video Formats](https://support.apple.com/kb/DL2100?locale=en_US) for Avid DNxHD® / Avid DNxHR®. For developers: Requires `MTRegisterProfessionalVideoWorkflowFormatReaders()` and `VTRegisterProfessionalVideoWorkflowVideoDecoders()` to be enabled - only some DNx variants work: | +| MXF Wrapped DNxHD_36 | :white_check_mark: | | +| MXF Wrapped DNxHD_80 | :warning: | (codec specifically gets subsampled down to 1440 x 1080)| +| MXF Wrapped DNxHD_115 | :white_check_mark:| | +| MXF Wrapped DNxHD_175 | :white_check_mark:| | +| MXF Wrapped DNxHR_SQ | :white_check_mark:| | +| MXF Wrapped DNxHR_LB | :white_check_mark:| | +| MXF Wrapped DNxHR_HQ | :white_check_mark:| | | Image Frames | :x: | Requires custom compositor | | Image Sequences | :x: | Requires custom compositor| | Raw Formats (BRaw, Red, etc) | :x: | Requires you to have SDK - manage decode and roll your own custom compositor | From 4e01d78ad592d60fd61bb008b750da087a97bc7c Mon Sep 17 00:00:00 2001 From: Anton Marini Date: Thu, 7 Nov 2024 19:32:18 -0500 Subject: [PATCH 48/67] Update README.md Signed-off-by: vade --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0f88a17..945b654 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ OTIO Reader Icon image
- Loading OpenTimeline IO files opened and playing in the included OpenTimelineIO Reader sample app: + Realtime playback of OpenTimelineIO files with the sample app OpenTimelineIO Reader:
From aa69e8c1adb49ea6c829039cbcb3a20b2401da51 Mon Sep 17 00:00:00 2001 From: vade Date: Tue, 1 Jul 2025 18:46:31 -0400 Subject: [PATCH 49/67] First step to marker support - some test data borrowed from OTIO Core project Signed-off-by: vade --- .../Assets/premiere_example.otio | 2369 +++++++++++++++++ 1 file changed, 2369 insertions(+) create mode 100644 Tests/OpenTimelineIO-AVFoundationTests/Assets/premiere_example.otio diff --git a/Tests/OpenTimelineIO-AVFoundationTests/Assets/premiere_example.otio b/Tests/OpenTimelineIO-AVFoundationTests/Assets/premiere_example.otio new file mode 100644 index 0000000..17f96ee --- /dev/null +++ b/Tests/OpenTimelineIO-AVFoundationTests/Assets/premiere_example.otio @@ -0,0 +1,2369 @@ +{ + "OTIO_SCHEMA": "Timeline.1", + "metadata": { + "fcp_xml": { + "@MZ.EditLine": "0", + "@MZ.Sequence.AudioTimeDisplayFormat": "200", + "@MZ.Sequence.EditingModeGUID": "9678af98-a7b7-4bdb-b477-7ac9c8df4a4e", + "@MZ.Sequence.PreviewFrameSizeHeight": "720", + "@MZ.Sequence.PreviewFrameSizeWidth": "1280", + "@MZ.Sequence.PreviewRenderingClassID": "1297106761", + "@MZ.Sequence.PreviewRenderingPresetCodec": "1297107278", + "@MZ.Sequence.PreviewRenderingPresetPath": "EncoderPresets\\SequencePreview\\9678af98-a7b7-4bdb-b477-7ac9c8df4a4e\\I-Frame Only MPEG.epr", + "@MZ.Sequence.PreviewUseMaxBitDepth": "false", + "@MZ.Sequence.PreviewUseMaxRenderQuality": "false", + "@MZ.Sequence.VideoTimeDisplayFormat": "104", + "@MZ.WorkInPoint": "0", + "@MZ.WorkOutPoint": "10550131200000", + "@Monitor.ProgramZoomIn": "0", + "@Monitor.ProgramZoomOut": "10550131200000", + "@TL.SQAVDividerPosition": "0.5", + "@TL.SQAudioVisibleBase": "0", + "@TL.SQHeaderWidth": "184", + "@TL.SQHideShyTracks": "0", + "@TL.SQTimePerPixel": "0.033806825568230996", + "@TL.SQVideoVisibleBase": "0", + "@TL.SQVisibleBaseTime": "0", + "@explodedTracks": "true", + "@id": "sequence-1", + "labels": { + "label2": "Forest" + }, + "media": { + "audio": { + "format": { + "samplecharacteristics": { + "depth": "16", + "samplerate": "48000" + } + }, + "numOutputChannels": "2", + "outputs": { + "group": [ + { + "channel": { + "index": "1" + }, + "downmix": "0", + "index": "1", + "numchannels": "1" + }, + { + "channel": { + "index": "2" + }, + "downmix": "0", + "index": "2", + "numchannels": "1" + } + ] + } + }, + "video": { + "format": { + "samplecharacteristics": { + "anamorphic": "FALSE", + "codec": { + "appspecificdata": { + "appmanufacturer": "Apple Inc.", + "appname": "Final Cut Pro", + "appversion": "7.0", + "data": { + "qtcodec": { + "codecname": "Apple ProRes 422", + "codectypecode": "apcn", + "codectypename": "Apple ProRes 422", + "codecvendorcode": "appl", + "datarate": "0", + "keyframerate": "0", + "spatialquality": "1024", + "temporalquality": "0" + } + } + }, + "name": "Apple ProRes 422" + }, + "colordepth": "24", + "fielddominance": "none", + "height": "720", + "pixelaspectratio": "square", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "width": "1280" + } + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "NDF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + } + }, + "uuid": "5ea30a6b-552f-4722-be92-6dfdb66c97e6" + } + }, + "name": "sc01_sh010_layerA", + "global_start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + }, + "tracks": { + "OTIO_SCHEMA": "Stack.1", + "metadata": {}, + "name": "sc01_sh010_layerA", + "source_range": null, + "effects": [], + "markers": [ + { + "OTIO_SCHEMA": "Marker.2", + "metadata": { + "fcp_xml": { + "comment": "so, this happened" + } + }, + "name": "My MArker 1", + "color": "RED", + "marked_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 113.0 + } + } + }, + { + "OTIO_SCHEMA": "Marker.2", + "metadata": { + "fcp_xml": { + "comment": "fsfsfs" + } + }, + "name": "dsf", + "color": "RED", + "marked_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 492.0 + } + } + }, + { + "OTIO_SCHEMA": "Marker.2", + "metadata": { + "fcp_xml": { + "comment": null + } + }, + "name": "", + "color": "RED", + "marked_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 298.0 + } + } + } + ], + "enabled": true, + "children": [ + { + "OTIO_SCHEMA": "Track.1", + "metadata": { + "fcp_xml": { + "@MZ.TrackTargeted": "1", + "@TL.SQTrackExpanded": "0", + "@TL.SQTrackExpandedHeight": "25", + "@TL.SQTrackShy": "0", + "enabled": "TRUE", + "locked": "FALSE" + } + }, + "name": "", + "source_range": null, + "effects": [], + "markers": [], + "enabled": true, + "children": [ + { + "OTIO_SCHEMA": "Gap.1", + "metadata": {}, + "name": "", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 536.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true + }, + { + "OTIO_SCHEMA": "Clip.2", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-1", + "alphatype": "none", + "anamorphic": "FALSE", + "enabled": "TRUE", + "labels": { + "label2": "Iris" + }, + "link": [ + { + "clipindex": "1", + "linkclipref": "clipitem-1", + "mediatype": "video", + "trackindex": "1" + }, + { + "clipindex": "2", + "groupindex": "1", + "linkclipref": "clipitem-14", + "mediatype": "audio", + "trackindex": "1" + }, + { + "clipindex": "2", + "groupindex": "1", + "linkclipref": "clipitem-16", + "mediatype": "audio", + "trackindex": "2" + } + ], + "logginginfo": { + "description": null, + "lognote": null, + "scene": null, + "shottake": null + }, + "masterclipid": "masterclip-1", + "pixelaspectratio": "square", + "pproTicksIn": "0", + "pproTicksOut": "846720000000" + }, + "my_hook_function_was_here": true + }, + "name": "sc01_sh010_anim.mov", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 15.0, + "value": 50.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 15.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ExternalReference.1", + "metadata": { + "fcp_xml": { + "@id": "file-1", + "media": { + "audio": { + "channelcount": "2", + "samplecharacteristics": { + "depth": "16", + "samplerate": "48000" + } + }, + "video": { + "samplecharacteristics": { + "anamorphic": "FALSE", + "fielddominance": "none", + "height": "720", + "pixelaspectratio": "square", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "width": "1280" + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "NDF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "reel": { + "name": null + } + } + } + }, + "name": "sc01_sh010_anim.mov", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 100.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "available_image_bounds": null, + "target_url": "file://localhost/D%3a/media/sc01_sh010_anim.mov" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" + } + ], + "kind": "Video" + }, + { + "OTIO_SCHEMA": "Track.1", + "metadata": { + "fcp_xml": { + "@MZ.TrackTargeted": "0", + "@TL.SQTrackExpanded": "0", + "@TL.SQTrackExpandedHeight": "25", + "@TL.SQTrackShy": "0", + "enabled": "TRUE", + "locked": "FALSE" + } + }, + "name": "", + "source_range": null, + "effects": [], + "markers": [], + "enabled": true, + "children": [ + { + "OTIO_SCHEMA": "Gap.1", + "metadata": {}, + "name": "", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 13.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true + }, + { + "OTIO_SCHEMA": "Clip.2", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-2", + "alphatype": "none", + "enabled": "TRUE", + "labels": { + "label2": "Iris" + }, + "link": [ + { + "clipindex": "1", + "linkclipref": "clipitem-2", + "mediatype": "video", + "trackindex": "2" + }, + { + "clipindex": "1", + "groupindex": "1", + "linkclipref": "clipitem-13", + "mediatype": "audio", + "trackindex": "1" + }, + { + "clipindex": "1", + "groupindex": "1", + "linkclipref": "clipitem-15", + "mediatype": "audio", + "trackindex": "2" + } + ], + "logginginfo": { + "description": null, + "lognote": null, + "scene": null, + "shottake": null + }, + "masterclipid": "masterclip-1", + "pproTicksIn": "0", + "pproTicksOut": "846720000000" + }, + "my_hook_function_was_here": true + }, + "name": "sc01_sh010_anim.mov", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 100.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ExternalReference.1", + "metadata": { + "fcp_xml": { + "@id": "file-1", + "media": { + "audio": { + "channelcount": "2", + "samplecharacteristics": { + "depth": "16", + "samplerate": "48000" + } + }, + "video": { + "samplecharacteristics": { + "anamorphic": "FALSE", + "fielddominance": "none", + "height": "720", + "pixelaspectratio": "square", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "width": "1280" + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "NDF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "reel": { + "name": null + } + } + } + }, + "name": "sc01_sh010_anim.mov", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 100.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "available_image_bounds": null, + "target_url": "file://localhost/D%3a/media/sc01_sh010_anim.mov" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" + }, + { + "OTIO_SCHEMA": "Gap.1", + "metadata": {}, + "name": "", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 52.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true + }, + { + "OTIO_SCHEMA": "Clip.2", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-3", + "alphatype": "none", + "anamorphic": "FALSE", + "enabled": "TRUE", + "labels": { + "label2": "Iris" + }, + "logginginfo": { + "description": null, + "lognote": null, + "scene": null, + "shottake": null + }, + "masterclipid": "masterclip-2", + "pixelaspectratio": "square", + "pproTicksIn": "0", + "pproTicksOut": "1329350400000" + }, + "my_hook_function_was_here": true + }, + "name": "sc01_sh020_anim.mov", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 157.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ExternalReference.1", + "metadata": { + "fcp_xml": { + "@id": "file-2", + "media": { + "audio": { + "channelcount": "2", + "samplecharacteristics": { + "depth": "16", + "samplerate": "48000" + } + }, + "video": { + "samplecharacteristics": { + "anamorphic": "FALSE", + "fielddominance": "none", + "height": "720", + "pixelaspectratio": "square", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "width": "1280" + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "NDF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "reel": { + "name": null + } + } + } + }, + "name": "sc01_sh020_anim.mov", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 175.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "available_image_bounds": null, + "target_url": "file://localhost/D%3a/media/sc01_sh020_anim.mov" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" + }, + { + "OTIO_SCHEMA": "Clip.2", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-4", + "alphatype": "none", + "anamorphic": "FALSE", + "enabled": "TRUE", + "labels": { + "label2": "Iris" + }, + "logginginfo": { + "description": null, + "lognote": null, + "scene": null, + "shottake": null + }, + "masterclipid": "masterclip-3", + "pixelaspectratio": "square", + "pproTicksIn": "0", + "pproTicksOut": "1989792000000" + }, + "my_hook_function_was_here": true + }, + "name": "sc01_sh030_anim.mov", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 235.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [ + { + "OTIO_SCHEMA": "Marker.2", + "metadata": { + "fcp_xml": { + "comment": null + } + }, + "name": "", + "color": "RED", + "marked_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 73.0 + } + } + } + ], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ExternalReference.1", + "metadata": { + "fcp_xml": { + "@id": "file-3", + "media": { + "audio": { + "channelcount": "2", + "samplecharacteristics": { + "depth": "16", + "samplerate": "48000" + } + }, + "video": { + "samplecharacteristics": { + "anamorphic": "FALSE", + "fielddominance": "none", + "height": "720", + "pixelaspectratio": "square", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "width": "1280" + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "NDF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "reel": { + "name": null + } + } + } + }, + "name": "sc01_sh030_anim.mov", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 400.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "available_image_bounds": null, + "target_url": "file://localhost/D%3a/media/sc01_sh030_anim.mov" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" + }, + { + "OTIO_SCHEMA": "Transition.1", + "metadata": { + "fcp_xml": { + "alignment": "end-black", + "cutPointTicks": "160876800000" + } + }, + "name": "Cross Dissolve", + "in_offset": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 19.0 + }, + "out_offset": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + }, + "transition_type": "SMPTE_Dissolve" + }, + { + "OTIO_SCHEMA": "Gap.1", + "metadata": {}, + "name": "", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 79.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true + }, + { + "OTIO_SCHEMA": "Stack.1", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-5", + "enabled": "TRUE", + "labels": { + "label2": "Forest" + }, + "masterclipid": "masterclip-4", + "pproTicksIn": "0", + "pproTicksOut": "2709504000000" + } + }, + "name": "sc01_sh010_anim", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 320.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "children": [] + } + ], + "kind": "Video" + }, + { + "OTIO_SCHEMA": "Track.1", + "metadata": { + "fcp_xml": { + "@MZ.TrackTargeted": "0", + "@TL.SQTrackExpanded": "0", + "@TL.SQTrackExpandedHeight": "25", + "@TL.SQTrackShy": "0", + "enabled": "TRUE", + "locked": "FALSE" + } + }, + "name": "", + "source_range": null, + "effects": [], + "markers": [], + "enabled": true, + "children": [ + { + "OTIO_SCHEMA": "Gap.1", + "metadata": {}, + "name": "", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 15.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true + }, + { + "OTIO_SCHEMA": "Clip.2", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-10", + "alphatype": "straight", + "anamorphic": "FALSE", + "enabled": "TRUE", + "labels": { + "label2": "Lavender" + }, + "logginginfo": { + "description": null, + "lognote": null, + "scene": null, + "shottake": null + }, + "masterclipid": "masterclip-5", + "pixelaspectratio": "square", + "pproTicksIn": "914457600000000", + "pproTicksOut": "922425235200000" + }, + "my_hook_function_was_here": true + }, + "name": "test_title", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 941.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 108000.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "GeneratorReference.1", + "metadata": { + "fcp_xml": { + "@id": "file-4", + "media": { + "video": { + "samplecharacteristics": { + "anamorphic": "FALSE", + "fielddominance": "none", + "height": "720", + "pixelaspectratio": "square", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "width": "1280" + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "DF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "reel": { + "name": null + } + } + } + }, + "name": "test_title", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "available_image_bounds": null, + "generator_kind": "Slug", + "parameters": {} + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" + } + ], + "kind": "Video" + }, + { + "OTIO_SCHEMA": "Track.1", + "metadata": { + "fcp_xml": { + "@MZ.TrackTargeted": "0", + "@TL.SQTrackExpanded": "0", + "@TL.SQTrackExpandedHeight": "25", + "@TL.SQTrackShy": "0", + "enabled": "TRUE", + "locked": "FALSE" + } + }, + "name": "", + "source_range": null, + "effects": [], + "markers": [], + "enabled": true, + "children": [ + { + "OTIO_SCHEMA": "Gap.1", + "metadata": {}, + "name": "", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 956.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true + }, + { + "OTIO_SCHEMA": "Clip.2", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-11", + "alphatype": "none", + "anamorphic": "FALSE", + "enabled": "TRUE", + "labels": { + "label2": "Iris" + }, + "link": [ + { + "clipindex": "1", + "linkclipref": "clipitem-11", + "mediatype": "video", + "trackindex": "4" + }, + { + "clipindex": "1", + "groupindex": "1", + "linkclipref": "clipitem-23", + "mediatype": "audio", + "trackindex": "7" + }, + { + "clipindex": "1", + "groupindex": "1", + "linkclipref": "clipitem-25", + "mediatype": "audio", + "trackindex": "8" + } + ], + "logginginfo": { + "description": null, + "lognote": null, + "scene": null, + "shottake": null + }, + "masterclipid": "masterclip-6", + "pixelaspectratio": "square", + "pproTicksIn": "287884800000", + "pproTicksOut": "2159136000000" + }, + "my_hook_function_was_here": true + }, + "name": "sc01_master_layerA_sh030_temp.mov", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 208.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 133.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ExternalReference.1", + "metadata": { + "fcp_xml": { + "@id": "file-5", + "media": { + "audio": { + "channelcount": "2", + "samplecharacteristics": { + "depth": "16", + "samplerate": "48000" + } + }, + "video": { + "samplecharacteristics": { + "anamorphic": "FALSE", + "fielddominance": "none", + "height": "720", + "pixelaspectratio": "square", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "width": "1280" + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "NDF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "reel": { + "name": null + } + } + } + }, + "name": "sc01_master_layerA_sh030_temp.mov", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 400.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 99.0 + } + }, + "available_image_bounds": null, + "target_url": "file://localhost/D%3a/media/sc01_master_layerA_sh030_temp.mov" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" + }, + { + "OTIO_SCHEMA": "Transition.1", + "metadata": { + "fcp_xml": { + "alignment": "center", + "cutPointTicks": "101606400000" + } + }, + "name": "Cross Dissolve", + "in_offset": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 12.0 + }, + "out_offset": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 13.0 + }, + "transition_type": "SMPTE_Dissolve" + }, + { + "OTIO_SCHEMA": "Clip.2", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-12", + "alphatype": "none", + "enabled": "TRUE", + "labels": { + "label2": "Iris" + }, + "link": [ + { + "clipindex": "3", + "linkclipref": "clipitem-12", + "mediatype": "video", + "trackindex": "4" + }, + { + "clipindex": "2", + "groupindex": "1", + "linkclipref": "clipitem-24", + "mediatype": "audio", + "trackindex": "7" + }, + { + "clipindex": "2", + "groupindex": "1", + "linkclipref": "clipitem-26", + "mediatype": "audio", + "trackindex": "8" + } + ], + "logginginfo": { + "description": null, + "lognote": null, + "scene": null, + "shottake": null + }, + "masterclipid": "masterclip-1", + "pproTicksIn": "50803200000", + "pproTicksOut": "846720000000" + }, + "my_hook_function_was_here": true + }, + "name": "sc01_sh010_anim.mov", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 82.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 18.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ExternalReference.1", + "metadata": { + "fcp_xml": { + "@id": "file-1", + "media": { + "audio": { + "channelcount": "2", + "samplecharacteristics": { + "depth": "16", + "samplerate": "48000" + } + }, + "video": { + "samplecharacteristics": { + "anamorphic": "FALSE", + "fielddominance": "none", + "height": "720", + "pixelaspectratio": "square", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "width": "1280" + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "NDF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "reel": { + "name": null + } + } + } + }, + "name": "sc01_sh010_anim.mov", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 100.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "available_image_bounds": null, + "target_url": "file://localhost/D%3a/media/sc01_sh010_anim.mov" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" + } + ], + "kind": "Video" + }, + { + "OTIO_SCHEMA": "Track.1", + "metadata": { + "fcp_xml": { + "@MZ.TrackTargeted": "1", + "@PannerCurrentValue": "0.5", + "@PannerIsInverted": "true", + "@PannerName": "Balance", + "@PannerStartKeyframe": "-91445760000000000,0.5,0,0,0,0,0,0", + "@TL.SQTrackAudioKeyframeStyle": "0", + "@TL.SQTrackExpanded": "0", + "@TL.SQTrackExpandedHeight": "25", + "@TL.SQTrackShy": "0", + "@currentExplodedTrackIndex": "0", + "@premiereTrackType": "Stereo", + "@totalExplodedTrackCount": "2", + "enabled": "TRUE", + "locked": "FALSE", + "outputchannelindex": "1" + } + }, + "name": "", + "source_range": null, + "effects": [], + "markers": [], + "enabled": true, + "children": [ + { + "OTIO_SCHEMA": "Gap.1", + "metadata": {}, + "name": "", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 13.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true + }, + { + "OTIO_SCHEMA": "Clip.2", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-13", + "@premiereChannelType": "stereo", + "enabled": "TRUE", + "labels": { + "label2": "Iris" + }, + "link": [ + { + "clipindex": "1", + "linkclipref": "clipitem-2", + "mediatype": "video", + "trackindex": "2" + }, + { + "clipindex": "1", + "groupindex": "1", + "linkclipref": "clipitem-13", + "mediatype": "audio", + "trackindex": "1" + }, + { + "clipindex": "1", + "groupindex": "1", + "linkclipref": "clipitem-15", + "mediatype": "audio", + "trackindex": "2" + } + ], + "logginginfo": { + "description": null, + "lognote": null, + "scene": null, + "shottake": null + }, + "masterclipid": "masterclip-1", + "pproTicksIn": "0", + "pproTicksOut": "846720000000", + "sourcetrack": { + "mediatype": "audio", + "trackindex": "1" + } + }, + "my_hook_function_was_here": true + }, + "name": "sc01_sh010_anim.mov", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 100.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ExternalReference.1", + "metadata": { + "fcp_xml": { + "@id": "file-1", + "media": { + "audio": { + "channelcount": "2", + "samplecharacteristics": { + "depth": "16", + "samplerate": "48000" + } + }, + "video": { + "samplecharacteristics": { + "anamorphic": "FALSE", + "fielddominance": "none", + "height": "720", + "pixelaspectratio": "square", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "width": "1280" + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "NDF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "reel": { + "name": null + } + } + } + }, + "name": "sc01_sh010_anim.mov", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 100.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "available_image_bounds": null, + "target_url": "file://localhost/D%3a/media/sc01_sh010_anim.mov" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" + }, + { + "OTIO_SCHEMA": "Gap.1", + "metadata": {}, + "name": "", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 423.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true + }, + { + "OTIO_SCHEMA": "Clip.2", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-14", + "@premiereChannelType": "stereo", + "enabled": "TRUE", + "labels": { + "label2": "Iris" + }, + "link": [ + { + "clipindex": "1", + "linkclipref": "clipitem-1", + "mediatype": "video", + "trackindex": "1" + }, + { + "clipindex": "2", + "groupindex": "1", + "linkclipref": "clipitem-14", + "mediatype": "audio", + "trackindex": "1" + }, + { + "clipindex": "2", + "groupindex": "1", + "linkclipref": "clipitem-16", + "mediatype": "audio", + "trackindex": "2" + } + ], + "logginginfo": { + "description": null, + "lognote": null, + "scene": null, + "shottake": null + }, + "masterclipid": "masterclip-1", + "pproTicksIn": "0", + "pproTicksOut": "846720000000", + "sourcetrack": { + "mediatype": "audio", + "trackindex": "1" + } + }, + "my_hook_function_was_here": true + }, + "name": "sc01_sh010_anim.mov", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 100.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ExternalReference.1", + "metadata": { + "fcp_xml": { + "@id": "file-1", + "media": { + "audio": { + "channelcount": "2", + "samplecharacteristics": { + "depth": "16", + "samplerate": "48000" + } + }, + "video": { + "samplecharacteristics": { + "anamorphic": "FALSE", + "fielddominance": "none", + "height": "720", + "pixelaspectratio": "square", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "width": "1280" + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "NDF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "reel": { + "name": null + } + } + } + }, + "name": "sc01_sh010_anim.mov", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 100.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "available_image_bounds": null, + "target_url": "file://localhost/D%3a/media/sc01_sh010_anim.mov" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" + } + ], + "kind": "Audio" + }, + { + "OTIO_SCHEMA": "Track.1", + "metadata": { + "fcp_xml": { + "@MZ.TrackTargeted": "1", + "@PannerCurrentValue": "0.5", + "@PannerIsInverted": "true", + "@PannerName": "Balance", + "@PannerStartKeyframe": "-91445760000000000,0.5,0,0,0,0,0,0", + "@TL.SQTrackAudioKeyframeStyle": "0", + "@TL.SQTrackExpanded": "0", + "@TL.SQTrackExpandedHeight": "25", + "@TL.SQTrackShy": "0", + "@currentExplodedTrackIndex": "0", + "@premiereTrackType": "Stereo", + "@totalExplodedTrackCount": "2", + "enabled": "TRUE", + "locked": "FALSE", + "outputchannelindex": "1" + } + }, + "name": "", + "source_range": null, + "effects": [], + "markers": [], + "enabled": true, + "children": [ + { + "OTIO_SCHEMA": "Gap.1", + "metadata": {}, + "name": "", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 335.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true + }, + { + "OTIO_SCHEMA": "Clip.2", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-17", + "@premiereChannelType": "stereo", + "enabled": "TRUE", + "labels": { + "label2": "Caribbean" + }, + "link": [ + { + "clipindex": "1", + "groupindex": "1", + "linkclipref": "clipitem-17", + "mediatype": "audio", + "trackindex": "3" + }, + { + "clipindex": "1", + "groupindex": "1", + "linkclipref": "clipitem-19", + "mediatype": "audio", + "trackindex": "4" + } + ], + "logginginfo": { + "description": null, + "lognote": null, + "scene": null, + "shottake": null + }, + "masterclipid": "masterclip-7", + "pproTicksIn": "0", + "pproTicksOut": "1439424000000", + "sourcetrack": { + "mediatype": "audio", + "trackindex": "1" + } + }, + "my_hook_function_was_here": true + }, + "name": "sc01_placeholder.wav", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 170.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 8497.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ExternalReference.1", + "metadata": { + "fcp_xml": { + "@id": "file-6", + "media": { + "audio": { + "channelcount": "2", + "samplecharacteristics": { + "depth": "16", + "samplerate": "48000" + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "DF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "reel": { + "name": null + } + } + } + }, + "name": "sc01_placeholder.wav", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 170.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 8497.0 + } + }, + "available_image_bounds": null, + "target_url": "file://localhost/D%3a/media/sc01_placeholder.wav" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" + }, + { + "OTIO_SCHEMA": "Gap.1", + "metadata": {}, + "name": "", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 131.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true + }, + { + "OTIO_SCHEMA": "Stack.1", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-18", + "@premiereChannelType": "stereo", + "enabled": "TRUE", + "labels": { + "label2": "Forest" + }, + "link": [ + { + "clipindex": "2", + "groupindex": "1", + "linkclipref": "clipitem-18", + "mediatype": "audio", + "trackindex": "3" + }, + { + "clipindex": "2", + "groupindex": "1", + "linkclipref": "clipitem-20", + "mediatype": "audio", + "trackindex": "4" + } + ], + "masterclipid": "masterclip-4", + "pproTicksIn": "0", + "pproTicksOut": "2489356800000" + } + }, + "name": "sc01_sh010_anim", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 294.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "children": [] + } + ], + "kind": "Audio" + }, + { + "OTIO_SCHEMA": "Track.1", + "metadata": { + "fcp_xml": { + "@MZ.TrackTargeted": "1", + "@PannerCurrentValue": "0.5", + "@PannerIsInverted": "true", + "@PannerName": "Balance", + "@PannerStartKeyframe": "-91445760000000000,0.5,0,0,0,0,0,0", + "@TL.SQTrackAudioKeyframeStyle": "0", + "@TL.SQTrackExpanded": "0", + "@TL.SQTrackExpandedHeight": "25", + "@TL.SQTrackShy": "0", + "@currentExplodedTrackIndex": "0", + "@premiereTrackType": "Stereo", + "@totalExplodedTrackCount": "2", + "enabled": "TRUE", + "locked": "FALSE", + "outputchannelindex": "1" + } + }, + "name": "", + "source_range": null, + "effects": [], + "markers": [], + "enabled": true, + "children": [ + { + "OTIO_SCHEMA": "Gap.1", + "metadata": {}, + "name": "", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 153.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true + }, + { + "OTIO_SCHEMA": "Clip.2", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-21", + "@premiereChannelType": "stereo", + "enabled": "TRUE", + "labels": { + "label2": "Caribbean" + }, + "link": [ + { + "clipindex": "1", + "groupindex": "1", + "linkclipref": "clipitem-21", + "mediatype": "audio", + "trackindex": "5" + }, + { + "clipindex": "1", + "groupindex": "1", + "linkclipref": "clipitem-22", + "mediatype": "audio", + "trackindex": "6" + } + ], + "logginginfo": { + "description": null, + "lognote": null, + "scene": null, + "shottake": null + }, + "masterclipid": "masterclip-8", + "pproTicksIn": "0", + "pproTicksOut": "1676505600000", + "sourcetrack": { + "mediatype": "audio", + "trackindex": "1" + } + }, + "my_hook_function_was_here": true + }, + "name": "track_08.wav", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 198.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 6896.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ExternalReference.1", + "metadata": { + "fcp_xml": { + "@id": "file-7", + "media": { + "audio": { + "channelcount": "2", + "samplecharacteristics": { + "depth": "16", + "samplerate": "48000" + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "DF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "reel": { + "name": null + } + } + } + }, + "name": "track_08.wav", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 198.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 6896.0 + } + }, + "available_image_bounds": null, + "target_url": "file://localhost/D%3a/media/track_08.wav" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" + } + ], + "kind": "Audio" + }, + { + "OTIO_SCHEMA": "Track.1", + "metadata": { + "fcp_xml": { + "@MZ.TrackTargeted": "0", + "@PannerCurrentValue": "0.5", + "@PannerIsInverted": "true", + "@PannerName": "Balance", + "@PannerStartKeyframe": "-91445760000000000,0.5,0,0,0,0,0,0", + "@TL.SQTrackAudioKeyframeStyle": "0", + "@TL.SQTrackExpanded": "0", + "@TL.SQTrackExpandedHeight": "25", + "@TL.SQTrackShy": "0", + "@currentExplodedTrackIndex": "0", + "@premiereTrackType": "Stereo", + "@totalExplodedTrackCount": "2", + "enabled": "TRUE", + "locked": "FALSE", + "outputchannelindex": "1" + } + }, + "name": "", + "source_range": null, + "effects": [], + "markers": [], + "enabled": true, + "children": [ + { + "OTIO_SCHEMA": "Gap.1", + "metadata": {}, + "name": "", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 956.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "effects": [], + "markers": [], + "enabled": true + }, + { + "OTIO_SCHEMA": "Clip.2", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-23", + "@premiereChannelType": "stereo", + "enabled": "TRUE", + "labels": { + "label2": "Iris" + }, + "link": [ + { + "clipindex": "1", + "linkclipref": "clipitem-11", + "mediatype": "video", + "trackindex": "4" + }, + { + "clipindex": "1", + "groupindex": "1", + "linkclipref": "clipitem-23", + "mediatype": "audio", + "trackindex": "7" + }, + { + "clipindex": "1", + "groupindex": "1", + "linkclipref": "clipitem-25", + "mediatype": "audio", + "trackindex": "8" + } + ], + "logginginfo": { + "description": null, + "lognote": null, + "scene": null, + "shottake": null + }, + "masterclipid": "masterclip-6", + "pproTicksIn": "287884800000", + "pproTicksOut": "2049062400000", + "sourcetrack": { + "mediatype": "audio", + "trackindex": "1" + } + }, + "my_hook_function_was_here": true + }, + "name": "sc01_master_layerA_sh030_temp.mov", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 221.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 133.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ExternalReference.1", + "metadata": { + "fcp_xml": { + "@id": "file-5", + "media": { + "audio": { + "channelcount": "2", + "samplecharacteristics": { + "depth": "16", + "samplerate": "48000" + } + }, + "video": { + "samplecharacteristics": { + "anamorphic": "FALSE", + "fielddominance": "none", + "height": "720", + "pixelaspectratio": "square", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "width": "1280" + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "NDF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "reel": { + "name": null + } + } + } + }, + "name": "sc01_master_layerA_sh030_temp.mov", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 400.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 99.0 + } + }, + "available_image_bounds": null, + "target_url": "file://localhost/D%3a/media/sc01_master_layerA_sh030_temp.mov" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" + }, + { + "OTIO_SCHEMA": "Clip.2", + "metadata": { + "fcp_xml": { + "@frameBlend": "FALSE", + "@id": "clipitem-24", + "@premiereChannelType": "stereo", + "enabled": "TRUE", + "labels": { + "label2": "Iris" + }, + "link": [ + { + "clipindex": "3", + "linkclipref": "clipitem-12", + "mediatype": "video", + "trackindex": "4" + }, + { + "clipindex": "2", + "groupindex": "1", + "linkclipref": "clipitem-24", + "mediatype": "audio", + "trackindex": "7" + }, + { + "clipindex": "2", + "groupindex": "1", + "linkclipref": "clipitem-26", + "mediatype": "audio", + "trackindex": "8" + } + ], + "logginginfo": { + "description": null, + "lognote": null, + "scene": null, + "shottake": null + }, + "masterclipid": "masterclip-1", + "pproTicksIn": "152409600000", + "pproTicksOut": "846720000000", + "sourcetrack": { + "mediatype": "audio", + "trackindex": "1" + } + }, + "my_hook_function_was_here": true + }, + "name": "sc01_sh010_anim.mov", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 94.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 6.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ExternalReference.1", + "metadata": { + "fcp_xml": { + "@id": "file-1", + "media": { + "audio": { + "channelcount": "2", + "samplecharacteristics": { + "depth": "16", + "samplerate": "48000" + } + }, + "video": { + "samplecharacteristics": { + "anamorphic": "FALSE", + "fielddominance": "none", + "height": "720", + "pixelaspectratio": "square", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "width": "1280" + } + } + }, + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "timecode": { + "displayformat": "NDF", + "rate": { + "ntsc": "FALSE", + "timebase": "30" + }, + "reel": { + "name": null + } + } + } + }, + "name": "sc01_sh010_anim.mov", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 100.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 30.0, + "value": 0.0 + } + }, + "available_image_bounds": null, + "target_url": "file://localhost/D%3a/media/sc01_sh010_anim.mov" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" + } + ], + "kind": "Audio" + } + ] + } +} \ No newline at end of file From 24cd6a5f2bf9f26a86989bde3b98d26fc0caf652 Mon Sep 17 00:00:00 2001 From: vade Date: Tue, 1 Jul 2025 19:15:37 -0400 Subject: [PATCH 50/67] Draft of marker view Signed-off-by: vade --- .../Views/ItemView.swift | 8 +++ .../Views/MarkerView.swift | 59 +++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift index 6d1da6f..69eef9e 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift @@ -53,6 +53,7 @@ struct ItemView : View { } } .frame(width: self.getSafeWidth()) + // .offset(x:self.getSafePositionX() )//, y:geometry.size.height * 0.5 ) } @@ -81,4 +82,11 @@ struct ItemView : View { { return self.getSafeRange().startTime.toSeconds() * self.secondsToPixels// + self.getSafeWidth()/2.0 } + +// @ViewBuilder func getMarkerView() -> some View +// { +// ForEach(self.item.markers) { marker in +// +// } +// } } diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift new file mode 100644 index 0000000..b1357f2 --- /dev/null +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift @@ -0,0 +1,59 @@ +// +// MarkerView.swift +// OpenTimelineIO-Reader +// +// Created by Anton Marini on 7/1/25. +// + +import OpenTimelineIO_AVFoundation +import OpenTimelineIO +import TimecodeKit +import SwiftUI + +struct MarkerView : View { + + let marker:OpenTimelineIO.Marker + + @Binding var secondsToPixels:Double + + var body: some View + { + self.colorForMarker() + .frame(width: self.getSafeWidth() ) + .offset(x:self.getSafePositionX() ) + } + + func getSafeWidth() -> CGFloat + { + return max(self.marker.markedRange.duration.toSeconds() * self.secondsToPixels, 3.0) + } + + func getSafePositionX() -> CGFloat + { + return self.marker.markedRange.startTime.toSeconds() * self.secondsToPixels// + self.getSafeWidth()/2.0 + } + + func colorForMarker() -> Color + { + if let color:Marker.Color = Marker.Color(rawValue: marker.color) + { + switch color + { + case .pink : return Color.pink + case .red : return Color.red + case .orange : return Color.orange + case .yellow : return Color.yellow + case .green : return Color.green + case .cyan : return Color.cyan + case .blue : return Color.blue + case .purple : return Color.purple + case .magenta : return Color(red: 1.0, green: 0, blue: 1.0) + case .black : return Color.black + case .white : return Color.white + + } + } + + return Color.red + } +} From d3b7c860026c3f1b40b155d82885feabb3493325 Mon Sep 17 00:00:00 2001 From: vade Date: Tue, 1 Jul 2025 20:21:21 -0400 Subject: [PATCH 51/67] Wire basic marker view up - need to validate placement logic Signed-off-by: vade --- .../Views/ItemView.swift | 24 ++++++++++++------- .../Views/MarkerView.swift | 5 ++-- .../Views/TimeRulerView.swift | 9 +++---- .../Views/TimelineView.swift | 19 ++++++++++++--- .../Views/TrackView.swift | 15 ++++++++++++ 5 files changed, 53 insertions(+), 19 deletions(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift index 69eef9e..635569e 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift @@ -20,7 +20,6 @@ struct ItemView : View { var body: some View { - ZStack { if let _ = item as? Gap @@ -52,8 +51,10 @@ struct ItemView : View { } } } - .frame(width: self.getSafeWidth()) - + .frame(width: self.getSafeWidth(), alignment: .leading ) + .overlay { + self.getMarkerView() + } // .offset(x:self.getSafePositionX() )//, y:geometry.size.height * 0.5 ) } @@ -83,10 +84,15 @@ struct ItemView : View { return self.getSafeRange().startTime.toSeconds() * self.secondsToPixels// + self.getSafeWidth()/2.0 } -// @ViewBuilder func getMarkerView() -> some View -// { -// ForEach(self.item.markers) { marker in -// -// } -// } + @ViewBuilder func getMarkerView() -> some View + { + HStack(alignment: .top) + { + let markerRange = self.item.markers.startIndex ..< self.item.markers.endIndex + ForEach(markerRange, id:\.self) { markerIndex in + MarkerView(marker: self.item.markers[markerIndex], + secondsToPixels: self.$secondsToPixels) + } + } + } } diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift index b1357f2..426af0b 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift @@ -19,13 +19,13 @@ struct MarkerView : View { var body: some View { self.colorForMarker() - .frame(width: self.getSafeWidth() ) + .frame(width: self.getSafeWidth(), height: 7.0, alignment: .leading ) .offset(x:self.getSafePositionX() ) } func getSafeWidth() -> CGFloat { - return max(self.marker.markedRange.duration.toSeconds() * self.secondsToPixels, 3.0) + return max(self.marker.markedRange.duration.toSeconds() * self.secondsToPixels, 5.0) } func getSafePositionX() -> CGFloat @@ -50,7 +50,6 @@ struct MarkerView : View { case .magenta : return Color(red: 1.0, green: 0, blue: 1.0) case .black : return Color.black case .white : return Color.white - } } diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift index e972c36..f0c18f4 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift @@ -34,7 +34,7 @@ struct TimeRulerView: View } func drawTicks(context: GraphicsContext, startSeconds: Double, endSeconds: Double, secondsToPixels: Double, size: CGSize) { - if self.secondsToPixels > 100 + if self.secondsToPixels > 75 { self.drawFrameTicks(context: context, startSeconds: startSeconds, endSeconds: endSeconds, secondsToPixels: secondsToPixels, size: size) } @@ -66,7 +66,7 @@ struct TimeRulerView: View context.fill(Path(tickRect), with: .color(.white)) // Draw label if it's an hour or minute - if self.secondsToPixels > 50 + if self.secondsToPixels > 75 { context.draw(Text(label).font(.system(size: 10)), at: CGPoint(x: positionX + 2, y: size.height - tickHeight - 10)) } @@ -93,9 +93,10 @@ struct TimeRulerView: View let tickRect = CGRect(x: positionX, y: size.height - tickHeight, width: 1, height: tickHeight) context.fill(Path(tickRect), with: .color(.white)) - if self.secondsToPixels > 400 + if self.secondsToPixels > 500 { - context.draw(Text(String(frameNum)).font(.system(size: 10)), at: CGPoint(x: positionX, y: size.height - tickHeight - 5)) + context.draw(Text(String(frameNum)).font(.system(size: 10)), + at: CGPoint(x: positionX, y: size.height - tickHeight - 5)) } } } diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift index 6b9694f..7c6d599 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift @@ -51,17 +51,19 @@ struct TimelineView : View { } } - func timelineView() -> some View + @ViewBuilder func timelineView() -> some View { let videoTracks = timeline.videoTracks let audioTracks = timeline.audioTracks - return + VStack(alignment:.leading, spacing: 3) { TimeRulerView(timeline: self.timeline, secondsToPixels: self.$secondsToPixels, currentTime: self.$currentTime ) .frame(height: 40) .offset(x:100) + .overlay( self.getMarkerView( ) ) + // ForEach(0.. some View + { + HStack(alignment: .top) + { + let markerRange = (self.timeline.tracks?.markers.startIndex ?? 0) ..< (self.timeline.tracks?.markers.endIndex ?? 0) + ForEach(markerRange, id:\.self) { markerIndex in + MarkerView(marker: self.timeline.tracks!.markers[markerIndex], + secondsToPixels: self.$secondsToPixels) + } + } } } diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TrackView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TrackView.swift index 30e2caa..ec29d44 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TrackView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TrackView.swift @@ -45,6 +45,9 @@ struct TrackView : View } } .frame(width: self.getSafeWidth(), alignment: .leading ) + .overlay { + self.getMarkerView() + } // .position(x:self.getSafePositionX(), y:0 ) } @@ -94,4 +97,16 @@ struct TrackView : View { return self.getSafeRange().startTime.toSeconds() * self.secondsToPixels - self.getSafeWidth()/2.0 } + + @ViewBuilder func getMarkerView() -> some View + { + HStack(alignment: .top) + { + let markerRange = self.track.markers.startIndex ..< self.track.markers.endIndex + ForEach(markerRange, id:\.self) { markerIndex in + MarkerView(marker: self.track.markers[markerIndex], + secondsToPixels: self.$secondsToPixels) + } + } + } } From 02055bf63f59b6edd999f3a186b15a16086e5cb0 Mon Sep 17 00:00:00 2001 From: vade Date: Wed, 2 Jul 2025 00:47:05 -0400 Subject: [PATCH 52/67] Fix marker position on main stack Signed-off-by: vade --- .../Views/ContentView.swift | 1 + .../Views/ItemView.swift | 3 + .../Views/MarkerView.swift | 2 +- .../Views/TimeRulerView.swift | 17 ++++ .../Views/TimelineView.swift | 83 ++++++++++--------- 5 files changed, 66 insertions(+), 40 deletions(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ContentView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ContentView.swift index 647a297..a882cff 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ContentView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ContentView.swift @@ -65,6 +65,7 @@ struct ContentView: View self.controlsViewStack() } } + } detail: { ItemInspectorView(selectedItem: self.$selectedItem) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift index 635569e..21df9dc 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift @@ -54,6 +54,7 @@ struct ItemView : View { .frame(width: self.getSafeWidth(), alignment: .leading ) .overlay { self.getMarkerView() + } // .offset(x:self.getSafePositionX() )//, y:geometry.size.height * 0.5 ) } @@ -94,5 +95,7 @@ struct ItemView : View { secondsToPixels: self.$secondsToPixels) } } + .frame(width: self.getSafeWidth(), alignment: .leading ) + } } diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift index 426af0b..348d3ef 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift @@ -19,7 +19,7 @@ struct MarkerView : View { var body: some View { self.colorForMarker() - .frame(width: self.getSafeWidth(), height: 7.0, alignment: .leading ) + .frame(width: self.getSafeWidth(), height: 7.0 ) .offset(x:self.getSafePositionX() ) } diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift index f0c18f4..c80d6c5 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift @@ -23,6 +23,23 @@ struct TimeRulerView: View let startSeconds = safeRange.startTime.toSeconds() let endSeconds = safeRange.endTimeInclusive().toSeconds() + if let tracks = self.timeline.tracks + { + let markerRange = tracks.markers.startIndex ..< tracks.markers.endIndex + for markerIndex in markerRange + { + let marker = tracks.markers[markerIndex] + let x = marker.markedRange.startTime.toSeconds() * self.secondsToPixels// + + + let path = Path(roundedRect: CGRect(origin: CGPoint(x: x, y: 0), + size: CGSize(width: 5.0, height: 9.0)), + cornerRadius: 3.0) + + context.fill(path, with: .color(.red)) + } + } + + // Draw playhead drawPlayhead(context: context, currentTime: currentTime, secondsToPixels: secondsToPixels, size: size) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift index 7c6d599..b172d96 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift @@ -55,53 +55,58 @@ struct TimelineView : View { { let videoTracks = timeline.videoTracks let audioTracks = timeline.audioTracks - - VStack(alignment:.leading, spacing: 3) - { + + VStack(alignment:.leading, spacing: 3) + { TimeRulerView(timeline: self.timeline, secondsToPixels: self.$secondsToPixels, currentTime: self.$currentTime ) .frame(height: 40) .offset(x:100) - .overlay( self.getMarkerView( ) ) - - // +// .overlay( +// self.getMarkerView( ) +// ) + // + + ForEach(0.. some View - { - HStack(alignment: .top) - { - let markerRange = (self.timeline.tracks?.markers.startIndex ?? 0) ..< (self.timeline.tracks?.markers.endIndex ?? 0) - ForEach(markerRange, id:\.self) { markerIndex in - MarkerView(marker: self.timeline.tracks!.markers[markerIndex], - secondsToPixels: self.$secondsToPixels) + + Divider() + + ForEach(0.. some View +// { +// HStack(alignment: .top, spacing: 0) +// { +// let markerRange = (self.timeline.tracks?.markers.startIndex ?? 0) ..< (self.timeline.tracks?.markers.endIndex ?? 0) +// ForEach(markerRange, id:\.self) { markerIndex in +// MarkerView(marker: self.timeline.tracks!.markers[markerIndex], +// secondsToPixels: self.$secondsToPixels) +// } +// } +//// .frame(width: self.getSafeWidth(), alignment: .leading ) +// .frame(alignment: .leading) +// .border(Color.purple, width: 2) +// } } From 7114ad7b5480d7b06f9bc2eb1bd7d5d57a9725f3 Mon Sep 17 00:00:00 2001 From: vade Date: Wed, 2 Jul 2025 01:08:44 -0400 Subject: [PATCH 53/67] Better marker drawing Signed-off-by: vade --- .../Views/TimeRulerView.swift | 74 ++++++++++++++----- 1 file changed, 56 insertions(+), 18 deletions(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift index c80d6c5..f7c3083 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift @@ -23,32 +23,70 @@ struct TimeRulerView: View let startSeconds = safeRange.startTime.toSeconds() let endSeconds = safeRange.endTimeInclusive().toSeconds() - if let tracks = self.timeline.tracks - { - let markerRange = tracks.markers.startIndex ..< tracks.markers.endIndex - for markerIndex in markerRange - { - let marker = tracks.markers[markerIndex] - let x = marker.markedRange.startTime.toSeconds() * self.secondsToPixels// + - - let path = Path(roundedRect: CGRect(origin: CGPoint(x: x, y: 0), - size: CGSize(width: 5.0, height: 9.0)), - cornerRadius: 3.0) - - context.fill(path, with: .color(.red)) - } - } + // Draw ticks (including frame-level ticks) + drawTicks(context: context, startSeconds: startSeconds, endSeconds: endSeconds, secondsToPixels: secondsToPixels, size: size) // Draw playhead drawPlayhead(context: context, currentTime: currentTime, secondsToPixels: secondsToPixels, size: size) - - // Draw ticks (including frame-level ticks) - drawTicks(context: context, startSeconds: startSeconds, endSeconds: endSeconds, secondsToPixels: secondsToPixels, size: size) + + drawMarkers(context: context, startSeconds: startSeconds, endSeconds: endSeconds, secondsToPixels: secondsToPixels, size: size) } .frame(width: self.getSafeWidth()) } + + func drawMarkers(context: GraphicsContext, startSeconds: Double, endSeconds: Double, secondsToPixels: Double, size: CGSize) + { + if let tracks = self.timeline.tracks + { + let markerRange = tracks.markers.startIndex ..< tracks.markers.endIndex + for markerIndex in markerRange + { + let marker = tracks.markers[markerIndex] + let x = marker.markedRange.startTime.toSeconds() * self.secondsToPixels// + + +// let path = Path(roundedRect: CGRect(origin: CGPoint(x: x, y: 13), +// size: CGSize(width: 3.0, height: 9.0)), +// cornerRadius: 1.0) +// +// context.fill(path, with: .color(.red)) + + let text = marker.name + + if #available(macOS 14.0, *) + { + context.draw( + Text("\(Image(systemName: "arrowtriangle.down.fill"))") + .font(.system(size: 10)) + .foregroundStyle(.red), + at: CGPoint(x: x + 0.5, y: 21)) + + + context.draw( + Text(text) + .font(.system(size: 10)) + .foregroundStyle(.white), + at: CGPoint(x: x , y: 12)) + } + else + { + context.draw( + Text("\(Image(systemName: "arrowtriangle.down.fill"))") + .font(.system(size: 10)) + .foregroundColor(.red), + at: CGPoint(x: x + 0.5, y: 21)) + + context.draw( + Text(text) + .font(.system(size: 10)) + .foregroundColor(.orange), + at: CGPoint(x: x , y: 12)) + } + } + } + } + func drawTicks(context: GraphicsContext, startSeconds: Double, endSeconds: Double, secondsToPixels: Double, size: CGSize) { if self.secondsToPixels > 75 From 240ae638894228af543a7940a4d7f7ca9e2ba42e Mon Sep 17 00:00:00 2001 From: vade Date: Wed, 2 Jul 2025 01:10:39 -0400 Subject: [PATCH 54/67] Xleanup Signed-off-by: vade --- .../OpenTimelineIO-Reader/Views/TimeRulerView.swift | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift index f7c3083..eff5af2 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift @@ -44,13 +44,7 @@ struct TimeRulerView: View for markerIndex in markerRange { let marker = tracks.markers[markerIndex] - let x = marker.markedRange.startTime.toSeconds() * self.secondsToPixels// + - -// let path = Path(roundedRect: CGRect(origin: CGPoint(x: x, y: 13), -// size: CGSize(width: 3.0, height: 9.0)), -// cornerRadius: 1.0) -// -// context.fill(path, with: .color(.red)) + let x = marker.markedRange.startTime.toSeconds() * self.secondsToPixels let text = marker.name From 40946f11b8d904d56a936d171344bb6818ce469f Mon Sep 17 00:00:00 2001 From: vade Date: Wed, 2 Jul 2025 01:27:06 -0400 Subject: [PATCH 55/67] more tweaks Signed-off-by: vade --- .../Views/TimeRulerView.swift | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift index eff5af2..2f03379 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift @@ -38,6 +38,7 @@ struct TimeRulerView: View func drawMarkers(context: GraphicsContext, startSeconds: Double, endSeconds: Double, secondsToPixels: Double, size: CGSize) { + let y = 15.0 if let tracks = self.timeline.tracks { let markerRange = tracks.markers.startIndex ..< tracks.markers.endIndex @@ -54,14 +55,17 @@ struct TimeRulerView: View Text("\(Image(systemName: "arrowtriangle.down.fill"))") .font(.system(size: 10)) .foregroundStyle(.red), - at: CGPoint(x: x + 0.5, y: 21)) + at: CGPoint(x: x + 0.5, y: y), + ) context.draw( Text(text) .font(.system(size: 10)) - .foregroundStyle(.white), - at: CGPoint(x: x , y: 12)) + .foregroundStyle(.red), + at: CGPoint(x: x + 9 , y: y - 2.0), + anchor: UnitPoint(x: 0, y: 0.5) +) } else { @@ -69,14 +73,23 @@ struct TimeRulerView: View Text("\(Image(systemName: "arrowtriangle.down.fill"))") .font(.system(size: 10)) .foregroundColor(.red), - at: CGPoint(x: x + 0.5, y: 21)) + at: CGPoint(x: x + 0.5, y: y), + ) context.draw( Text(text) .font(.system(size: 10)) - .foregroundColor(.orange), - at: CGPoint(x: x , y: 12)) + .foregroundColor(.red), + at: CGPoint(x: x + 9 , y: y - 2.0), + anchor: UnitPoint(x: 0, y: 0.5) + ) } + + let tickHeight = 20.0 + + // Draw tick line + let tickRect = CGRect(x: x, y: size.height - tickHeight, width: 1, height: tickHeight) + context.fill(Path(tickRect), with: .color(.red)) } } } @@ -177,7 +190,7 @@ struct TimeRulerView: View context.draw( Text(currentTimeLabel) - .font(.system(size: 10)) + .font(.system(size: 10, weight: .bold)) .foregroundStyle(.orange), at: CGPoint(x: playheadPositionX, y: 5)) } @@ -191,7 +204,7 @@ struct TimeRulerView: View context.draw( Text(currentTimeLabel) - .font(.system(size: 10)) + .font(.system(size: 10, weight: .bold)) .foregroundColor(.orange), at: CGPoint(x: playheadPositionX, y: 5)) } From 8ceb8b8d43c129eddc3fea5b40956d12f6307ff3 Mon Sep 17 00:00:00 2001 From: vade Date: Wed, 2 Jul 2025 09:04:55 -0400 Subject: [PATCH 56/67] Nicer marker drawing Signed-off-by: vade --- .../Views/ContentView.swift | 4 +- .../Views/ItemView.swift | 66 ++++++++----------- .../Views/TimeRulerView.swift | 7 +- .../Views/TimelineView.swift | 35 +++------- .../Views/TrackView.swift | 11 ++-- 5 files changed, 48 insertions(+), 75 deletions(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ContentView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ContentView.swift index a882cff..66c887e 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ContentView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ContentView.swift @@ -33,9 +33,7 @@ struct ContentView: View self.fileURL = fileURL guard let fileURL = fileURL else { return } - - - + self.document.setupPlayerWithBaseDocumentURL(fileURL) } diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift index 21df9dc..cdca6fb 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift @@ -13,50 +13,42 @@ import SwiftUI struct ItemView : View { let item:OpenTimelineIO.Item - @State var backgroundColor:Color - @State var selected:Bool + let backgroundColor:Color + let selected:Bool + + private var isGap: Bool { + item.isKind(of: Gap.self) + } @Binding var secondsToPixels:Double var body: some View { - ZStack { - - if let _ = item as? Gap - { - RoundedRectangle(cornerRadius: 3) - .fill(Color("GapTrackBaseColor")) // Fill the RoundedRectangle with color - .overlay( - RoundedRectangle(cornerRadius: 3) - .stroke(self.selected ? .white : .clear, lineWidth: 1) // Add stroke/outline - ) - .frame(width: self.getSafeWidth() - 2) - } - else - { + ZStack + { + let fill:AnyShapeStyle = ( isGap ) ? AnyShapeStyle( Color("GapTrackBaseColor") ) : AnyShapeStyle(self.backgroundColor.gradient) + let textGapOpacity:Double = ( isGap ) ? 0.0 : 1.0 + + RoundedRectangle(cornerRadius: 3) + .fill(fill, style: FillStyle()) + .overlay( RoundedRectangle(cornerRadius: 3) - .fill(self.backgroundColor.gradient) // Fill the RoundedRectangle with color - .overlay( - RoundedRectangle(cornerRadius: 3) - .stroke(self.selected ? .white : .clear, lineWidth: 1) // Add stroke/outline - ) + .strokeBorder(self.selected ? .orange : .clear, lineWidth: 1) // Add stroke/outline .frame(width: self.getSafeWidth() - 2) - - if self.getSafeWidth() > 40 - { - Text(item.name) - .lineLimit(1) - .font(.system(size: 10)) - .frame(width: self.getSafeWidth()) - } - } - } - .frame(width: self.getSafeWidth(), alignment: .leading ) - .overlay { - self.getMarkerView() - - } -// .offset(x:self.getSafePositionX() )//, y:geometry.size.height * 0.5 ) + ) + .frame(width: self.getSafeWidth() - 2) + + Text(item.name) + .lineLimit(1) + .font(.system(size: 10)) + .frame(width: self.getSafeWidth()) + .opacity(self.getSafeWidth() > 40 ? textGapOpacity : 0.0) + } + .frame(width: self.getSafeWidth(), alignment: .leading ) + .overlay { + // TODO: Get item marker coordinate system working +// self.getMarkerView() + } } func getSafeRange() -> OpenTimelineIO.TimeRange diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift index 2f03379..3417277 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift @@ -23,7 +23,6 @@ struct TimeRulerView: View let startSeconds = safeRange.startTime.toSeconds() let endSeconds = safeRange.endTimeInclusive().toSeconds() - // Draw ticks (including frame-level ticks) drawTicks(context: context, startSeconds: startSeconds, endSeconds: endSeconds, secondsToPixels: secondsToPixels, size: size) @@ -38,7 +37,7 @@ struct TimeRulerView: View func drawMarkers(context: GraphicsContext, startSeconds: Double, endSeconds: Double, secondsToPixels: Double, size: CGSize) { - let y = 15.0 + let y = 20.0 if let tracks = self.timeline.tracks { let markerRange = tracks.markers.startIndex ..< tracks.markers.endIndex @@ -85,7 +84,7 @@ struct TimeRulerView: View ) } - let tickHeight = 20.0 + let tickHeight = 24.0 // Draw tick line let tickRect = CGRect(x: x, y: size.height - tickHeight, width: 1, height: tickHeight) @@ -177,7 +176,7 @@ struct TimeRulerView: View } let playheadPositionX = currentTime.toSeconds() * secondsToPixels - let playheadRect = CGRect(x: playheadPositionX, y: 20, width: 1, height: size.height-20) + let playheadRect = CGRect(x: playheadPositionX, y: 22, width: 1, height: size.height - 22) // context.fill(Path(playheadRect), with: .color(.orange)) if #available(macOS 14.0, *) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift index b172d96..d09055c 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift @@ -32,7 +32,7 @@ struct TimelineView : View { { self.timelineView() .allowsHitTesting(self.hitTestEnabled) - .drawingGroup(opaque: true) +// .drawingGroup(opaque: true) } .onScrollPhaseChange({ oldPhase, newPhase, context in @@ -40,6 +40,9 @@ struct TimelineView : View { self.hitTestEnabled = !newPhase.isScrolling }) + .frame(idealHeight: ( CGFloat((timeline.videoTracks.count + timeline.audioTracks.count)) * TrackView.trackHeight) + 100 ) + // .frame(maxHeight: CGFloat((videoTracks.count + audioTracks.count)) * 500) + } else { @@ -55,17 +58,12 @@ struct TimelineView : View { { let videoTracks = timeline.videoTracks let audioTracks = timeline.audioTracks - VStack(alignment:.leading, spacing: 3) { - TimeRulerView(timeline: self.timeline, secondsToPixels: self.$secondsToPixels, currentTime: self.$currentTime ) - .frame(height: 40) + TimeRulerView(timeline: self.timeline, secondsToPixels: self.$secondsToPixels, currentTime: self.$currentTime ) + .frame(height: 50) .offset(x:100) -// .overlay( -// self.getMarkerView( ) -// ) - // ForEach(0.. some View -// { -// HStack(alignment: .top, spacing: 0) -// { -// let markerRange = (self.timeline.tracks?.markers.startIndex ?? 0) ..< (self.timeline.tracks?.markers.endIndex ?? 0) -// ForEach(markerRange, id:\.self) { markerIndex in -// MarkerView(marker: self.timeline.tracks!.markers[markerIndex], -// secondsToPixels: self.$secondsToPixels) -// } -// } -//// .frame(width: self.getSafeWidth(), alignment: .leading ) -// .frame(alignment: .leading) -// .border(Color.purple, width: 2) -// } } diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TrackView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TrackView.swift index ec29d44..4336b1b 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TrackView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TrackView.swift @@ -14,8 +14,10 @@ import SwiftUI struct TrackView : View { + static let trackHeight:CGFloat = 35 + let track:OpenTimelineIO.Track - @State var backgroundColor:Color + let backgroundColor:Color @Binding var secondsToPixels:Double @Binding var selectedItem:Item? @@ -27,14 +29,13 @@ struct TrackView : View { Section(header: self.headerView() ) { - ForEach(0.. Date: Wed, 2 Jul 2025 17:30:14 -0400 Subject: [PATCH 57/67] Cleanup Signed-off-by: vade --- .../OpenTimelineIO-Reader/Views/ItemInspectorView.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemInspectorView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemInspectorView.swift index 7020708..633d253 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemInspectorView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemInspectorView.swift @@ -129,7 +129,6 @@ struct ItemInspectorView: View } .font(.system(size: 10)) .listRowSeparator(.hidden) - } func safeToJSON(item: Item) -> String From 597c4c3a687a212986771cfd8110ed6c11e45650 Mon Sep 17 00:00:00 2001 From: vade Date: Wed, 2 Jul 2025 17:36:18 -0400 Subject: [PATCH 58/67] Fix alignment of TimelineRuler canvas to allow for "overdraw" fixing #25 Signed-off-by: vade --- .../OpenTimelineIO-Reader/Views/TimeRulerView.swift | 7 ++++++- .../OpenTimelineIO-Reader/Views/TimelineView.swift | 6 +++--- .../OpenTimelineIO-Reader/Views/TrackView.swift | 5 +++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift index 3417277..440cd5f 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift @@ -11,6 +11,9 @@ import TimecodeKit import SwiftUI struct TimeRulerView: View { + + static let VerticalPadding:CGFloat = 100 + let timeline: OpenTimelineIO.Timeline @Binding var secondsToPixels: Double @Binding var currentTime: OpenTimelineIO.RationalTime @@ -19,6 +22,8 @@ struct TimeRulerView: View { Canvas { context, size in + context.translateBy(x: TrackView.trackHeaderWidth, y: 0) + let safeRange = getSafeRange() let startSeconds = safeRange.startTime.toSeconds() let endSeconds = safeRange.endTimeInclusive().toSeconds() @@ -32,7 +37,7 @@ struct TimeRulerView: View drawMarkers(context: context, startSeconds: startSeconds, endSeconds: endSeconds, secondsToPixels: secondsToPixels, size: size) } - .frame(width: self.getSafeWidth()) + .frame(width: self.getSafeWidth() + TrackView.trackHeaderWidth) } func drawMarkers(context: GraphicsContext, startSeconds: Double, endSeconds: Double, secondsToPixels: Double, size: CGSize) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift index d09055c..eb16a86 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift @@ -40,8 +40,7 @@ struct TimelineView : View { self.hitTestEnabled = !newPhase.isScrolling }) - .frame(idealHeight: ( CGFloat((timeline.videoTracks.count + timeline.audioTracks.count)) * TrackView.trackHeight) + 100 ) - // .frame(maxHeight: CGFloat((videoTracks.count + audioTracks.count)) * 500) + .frame(idealHeight: ( CGFloat((timeline.videoTracks.count + timeline.audioTracks.count)) * TrackView.trackHeight) + TimeRulerView.VerticalPadding ) } else @@ -51,6 +50,8 @@ struct TimelineView : View { { self.timelineView() } + .frame(idealHeight: ( CGFloat((timeline.videoTracks.count + timeline.audioTracks.count)) * TrackView.trackHeight) + TimeRulerView.VerticalPadding ) + } } @@ -63,7 +64,6 @@ struct TimelineView : View { { TimeRulerView(timeline: self.timeline, secondsToPixels: self.$secondsToPixels, currentTime: self.$currentTime ) .frame(height: 50) - .offset(x:100) ForEach(0.. CGFloat { - return self.getSafeRange().duration.toSeconds() * self.secondsToPixels + 100 + return self.getSafeRange().duration.toSeconds() * self.secondsToPixels + TrackView.trackHeaderWidth } func getSafePositionX() -> CGFloat From 8589d6d4e336380d3bdd0f3cc87c4ece85930150 Mon Sep 17 00:00:00 2001 From: vade Date: Tue, 1 Jul 2025 19:15:37 -0400 Subject: [PATCH 59/67] Draft of marker view Signed-off-by: vade Signed-off-by: Anton Marini --- .../Views/ItemView.swift | 81 +++++++++---------- .../Views/MarkerView.swift | 5 +- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift index cdca6fb..69eef9e 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift @@ -13,42 +13,48 @@ import SwiftUI struct ItemView : View { let item:OpenTimelineIO.Item - let backgroundColor:Color - let selected:Bool - - private var isGap: Bool { - item.isKind(of: Gap.self) - } + @State var backgroundColor:Color + @State var selected:Bool @Binding var secondsToPixels:Double var body: some View { - ZStack - { - let fill:AnyShapeStyle = ( isGap ) ? AnyShapeStyle( Color("GapTrackBaseColor") ) : AnyShapeStyle(self.backgroundColor.gradient) - let textGapOpacity:Double = ( isGap ) ? 0.0 : 1.0 - - RoundedRectangle(cornerRadius: 3) - .fill(fill, style: FillStyle()) - .overlay( + + ZStack { + + if let _ = item as? Gap + { + RoundedRectangle(cornerRadius: 3) + .fill(Color("GapTrackBaseColor")) // Fill the RoundedRectangle with color + .overlay( + RoundedRectangle(cornerRadius: 3) + .stroke(self.selected ? .white : .clear, lineWidth: 1) // Add stroke/outline + ) + .frame(width: self.getSafeWidth() - 2) + } + else + { RoundedRectangle(cornerRadius: 3) - .strokeBorder(self.selected ? .orange : .clear, lineWidth: 1) // Add stroke/outline + .fill(self.backgroundColor.gradient) // Fill the RoundedRectangle with color + .overlay( + RoundedRectangle(cornerRadius: 3) + .stroke(self.selected ? .white : .clear, lineWidth: 1) // Add stroke/outline + ) .frame(width: self.getSafeWidth() - 2) - ) - .frame(width: self.getSafeWidth() - 2) + + if self.getSafeWidth() > 40 + { + Text(item.name) + .lineLimit(1) + .font(.system(size: 10)) + .frame(width: self.getSafeWidth()) + } + } + } + .frame(width: self.getSafeWidth()) - Text(item.name) - .lineLimit(1) - .font(.system(size: 10)) - .frame(width: self.getSafeWidth()) - .opacity(self.getSafeWidth() > 40 ? textGapOpacity : 0.0) - } - .frame(width: self.getSafeWidth(), alignment: .leading ) - .overlay { - // TODO: Get item marker coordinate system working -// self.getMarkerView() - } +// .offset(x:self.getSafePositionX() )//, y:geometry.size.height * 0.5 ) } func getSafeRange() -> OpenTimelineIO.TimeRange @@ -77,17 +83,10 @@ struct ItemView : View { return self.getSafeRange().startTime.toSeconds() * self.secondsToPixels// + self.getSafeWidth()/2.0 } - @ViewBuilder func getMarkerView() -> some View - { - HStack(alignment: .top) - { - let markerRange = self.item.markers.startIndex ..< self.item.markers.endIndex - ForEach(markerRange, id:\.self) { markerIndex in - MarkerView(marker: self.item.markers[markerIndex], - secondsToPixels: self.$secondsToPixels) - } - } - .frame(width: self.getSafeWidth(), alignment: .leading ) - - } +// @ViewBuilder func getMarkerView() -> some View +// { +// ForEach(self.item.markers) { marker in +// +// } +// } } diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift index 348d3ef..b1357f2 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift @@ -19,13 +19,13 @@ struct MarkerView : View { var body: some View { self.colorForMarker() - .frame(width: self.getSafeWidth(), height: 7.0 ) + .frame(width: self.getSafeWidth() ) .offset(x:self.getSafePositionX() ) } func getSafeWidth() -> CGFloat { - return max(self.marker.markedRange.duration.toSeconds() * self.secondsToPixels, 5.0) + return max(self.marker.markedRange.duration.toSeconds() * self.secondsToPixels, 3.0) } func getSafePositionX() -> CGFloat @@ -50,6 +50,7 @@ struct MarkerView : View { case .magenta : return Color(red: 1.0, green: 0, blue: 1.0) case .black : return Color.black case .white : return Color.white + } } From e2e7636861d3acab30aad4bd724d3c13fc92d9de Mon Sep 17 00:00:00 2001 From: vade Date: Tue, 1 Jul 2025 20:21:21 -0400 Subject: [PATCH 60/67] Wire basic marker view up - need to validate placement logic Signed-off-by: vade Signed-off-by: Anton Marini --- .../Views/ItemView.swift | 24 +++++++++------ .../Views/MarkerView.swift | 5 ++-- .../Views/TimelineView.swift | 29 +++++++++++++++++++ .../Views/TrackView.swift | 4 +++ 4 files changed, 50 insertions(+), 12 deletions(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift index 69eef9e..635569e 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift @@ -20,7 +20,6 @@ struct ItemView : View { var body: some View { - ZStack { if let _ = item as? Gap @@ -52,8 +51,10 @@ struct ItemView : View { } } } - .frame(width: self.getSafeWidth()) - + .frame(width: self.getSafeWidth(), alignment: .leading ) + .overlay { + self.getMarkerView() + } // .offset(x:self.getSafePositionX() )//, y:geometry.size.height * 0.5 ) } @@ -83,10 +84,15 @@ struct ItemView : View { return self.getSafeRange().startTime.toSeconds() * self.secondsToPixels// + self.getSafeWidth()/2.0 } -// @ViewBuilder func getMarkerView() -> some View -// { -// ForEach(self.item.markers) { marker in -// -// } -// } + @ViewBuilder func getMarkerView() -> some View + { + HStack(alignment: .top) + { + let markerRange = self.item.markers.startIndex ..< self.item.markers.endIndex + ForEach(markerRange, id:\.self) { markerIndex in + MarkerView(marker: self.item.markers[markerIndex], + secondsToPixels: self.$secondsToPixels) + } + } + } } diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift index b1357f2..426af0b 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift @@ -19,13 +19,13 @@ struct MarkerView : View { var body: some View { self.colorForMarker() - .frame(width: self.getSafeWidth() ) + .frame(width: self.getSafeWidth(), height: 7.0, alignment: .leading ) .offset(x:self.getSafePositionX() ) } func getSafeWidth() -> CGFloat { - return max(self.marker.markedRange.duration.toSeconds() * self.secondsToPixels, 3.0) + return max(self.marker.markedRange.duration.toSeconds() * self.secondsToPixels, 5.0) } func getSafePositionX() -> CGFloat @@ -50,7 +50,6 @@ struct MarkerView : View { case .magenta : return Color(red: 1.0, green: 0, blue: 1.0) case .black : return Color.black case .white : return Color.white - } } diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift index eb16a86..7b0d831 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift @@ -60,12 +60,24 @@ struct TimelineView : View { let videoTracks = timeline.videoTracks let audioTracks = timeline.audioTracks +<<<<<<< HEAD VStack(alignment:.leading, spacing: 3) { TimeRulerView(timeline: self.timeline, secondsToPixels: self.$secondsToPixels, currentTime: self.$currentTime ) .frame(height: 50) ForEach(0..>>>>>> 883836b (Wire basic marker view up - need to validate placement logic) // Reverse let track = videoTracks[(videoTracks.count - 1 ) - index] @@ -75,6 +87,7 @@ struct TimelineView : View { secondsToPixels: self.$secondsToPixels, selectedItem: self.$selectedItem ) } +<<<<<<< HEAD Spacer() Divider() @@ -91,5 +104,21 @@ struct TimelineView : View { } } +======= + .frame(height: CGFloat((videoTracks.count + audioTracks.count)) * 25 + 50 ) + .frame(maxHeight: CGFloat((videoTracks.count + audioTracks.count)) * 500) + } + + @ViewBuilder func getMarkerView() -> some View + { + HStack(alignment: .top) + { + let markerRange = (self.timeline.tracks?.markers.startIndex ?? 0) ..< (self.timeline.tracks?.markers.endIndex ?? 0) + ForEach(markerRange, id:\.self) { markerIndex in + MarkerView(marker: self.timeline.tracks!.markers[markerIndex], + secondsToPixels: self.$secondsToPixels) + } + } +>>>>>>> 883836b (Wire basic marker view up - need to validate placement logic) } } diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TrackView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TrackView.swift index 28d4c65..edf5202 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TrackView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TrackView.swift @@ -46,7 +46,11 @@ struct TrackView : View } } } +<<<<<<< HEAD .frame(width: self.getSafeWidth(), height:Self.trackHeight, alignment: .leading ) +======= + .frame(width: self.getSafeWidth(), alignment: .leading ) +>>>>>>> 883836b (Wire basic marker view up - need to validate placement logic) .overlay { self.getMarkerView() } From 0b9ef6b144190d60e5365cc862089daec679a79f Mon Sep 17 00:00:00 2001 From: vade Date: Wed, 2 Jul 2025 00:47:05 -0400 Subject: [PATCH 61/67] Fix marker position on main stack Signed-off-by: vade Signed-off-by: Anton Marini --- .../Views/ItemView.swift | 3 + .../Views/MarkerView.swift | 2 +- .../Views/TimeRulerView.swift | 91 +++++-------------- .../Views/TimelineView.swift | 62 +++++-------- 4 files changed, 50 insertions(+), 108 deletions(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift index 635569e..21df9dc 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift @@ -54,6 +54,7 @@ struct ItemView : View { .frame(width: self.getSafeWidth(), alignment: .leading ) .overlay { self.getMarkerView() + } // .offset(x:self.getSafePositionX() )//, y:geometry.size.height * 0.5 ) } @@ -94,5 +95,7 @@ struct ItemView : View { secondsToPixels: self.$secondsToPixels) } } + .frame(width: self.getSafeWidth(), alignment: .leading ) + } } diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift index 426af0b..348d3ef 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/MarkerView.swift @@ -19,7 +19,7 @@ struct MarkerView : View { var body: some View { self.colorForMarker() - .frame(width: self.getSafeWidth(), height: 7.0, alignment: .leading ) + .frame(width: self.getSafeWidth(), height: 7.0 ) .offset(x:self.getSafePositionX() ) } diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift index 440cd5f..c80d6c5 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift @@ -11,9 +11,6 @@ import TimecodeKit import SwiftUI struct TimeRulerView: View { - - static let VerticalPadding:CGFloat = 100 - let timeline: OpenTimelineIO.Timeline @Binding var secondsToPixels: Double @Binding var currentTime: OpenTimelineIO.RationalTime @@ -22,82 +19,36 @@ struct TimeRulerView: View { Canvas { context, size in - context.translateBy(x: TrackView.trackHeaderWidth, y: 0) - let safeRange = getSafeRange() let startSeconds = safeRange.startTime.toSeconds() let endSeconds = safeRange.endTimeInclusive().toSeconds() - // Draw ticks (including frame-level ticks) - drawTicks(context: context, startSeconds: startSeconds, endSeconds: endSeconds, secondsToPixels: secondsToPixels, size: size) - - // Draw playhead - drawPlayhead(context: context, currentTime: currentTime, secondsToPixels: secondsToPixels, size: size) - - drawMarkers(context: context, startSeconds: startSeconds, endSeconds: endSeconds, secondsToPixels: secondsToPixels, size: size) - - } - .frame(width: self.getSafeWidth() + TrackView.trackHeaderWidth) - } - - func drawMarkers(context: GraphicsContext, startSeconds: Double, endSeconds: Double, secondsToPixels: Double, size: CGSize) - { - let y = 20.0 - if let tracks = self.timeline.tracks - { - let markerRange = tracks.markers.startIndex ..< tracks.markers.endIndex - for markerIndex in markerRange + if let tracks = self.timeline.tracks { - let marker = tracks.markers[markerIndex] - let x = marker.markedRange.startTime.toSeconds() * self.secondsToPixels - - let text = marker.name - - if #available(macOS 14.0, *) + let markerRange = tracks.markers.startIndex ..< tracks.markers.endIndex + for markerIndex in markerRange { - context.draw( - Text("\(Image(systemName: "arrowtriangle.down.fill"))") - .font(.system(size: 10)) - .foregroundStyle(.red), - at: CGPoint(x: x + 0.5, y: y), - ) - + let marker = tracks.markers[markerIndex] + let x = marker.markedRange.startTime.toSeconds() * self.secondsToPixels// + - context.draw( - Text(text) - .font(.system(size: 10)) - .foregroundStyle(.red), - at: CGPoint(x: x + 9 , y: y - 2.0), - anchor: UnitPoint(x: 0, y: 0.5) -) - } - else - { - context.draw( - Text("\(Image(systemName: "arrowtriangle.down.fill"))") - .font(.system(size: 10)) - .foregroundColor(.red), - at: CGPoint(x: x + 0.5, y: y), - ) + let path = Path(roundedRect: CGRect(origin: CGPoint(x: x, y: 0), + size: CGSize(width: 5.0, height: 9.0)), + cornerRadius: 3.0) - context.draw( - Text(text) - .font(.system(size: 10)) - .foregroundColor(.red), - at: CGPoint(x: x + 9 , y: y - 2.0), - anchor: UnitPoint(x: 0, y: 0.5) - ) + context.fill(path, with: .color(.red)) } - - let tickHeight = 24.0 - - // Draw tick line - let tickRect = CGRect(x: x, y: size.height - tickHeight, width: 1, height: tickHeight) - context.fill(Path(tickRect), with: .color(.red)) } + + + // Draw playhead + drawPlayhead(context: context, currentTime: currentTime, secondsToPixels: secondsToPixels, size: size) + + // Draw ticks (including frame-level ticks) + drawTicks(context: context, startSeconds: startSeconds, endSeconds: endSeconds, secondsToPixels: secondsToPixels, size: size) + } + .frame(width: self.getSafeWidth()) } - func drawTicks(context: GraphicsContext, startSeconds: Double, endSeconds: Double, secondsToPixels: Double, size: CGSize) { if self.secondsToPixels > 75 @@ -181,7 +132,7 @@ struct TimeRulerView: View } let playheadPositionX = currentTime.toSeconds() * secondsToPixels - let playheadRect = CGRect(x: playheadPositionX, y: 22, width: 1, height: size.height - 22) + let playheadRect = CGRect(x: playheadPositionX, y: 20, width: 1, height: size.height-20) // context.fill(Path(playheadRect), with: .color(.orange)) if #available(macOS 14.0, *) @@ -194,7 +145,7 @@ struct TimeRulerView: View context.draw( Text(currentTimeLabel) - .font(.system(size: 10, weight: .bold)) + .font(.system(size: 10)) .foregroundStyle(.orange), at: CGPoint(x: playheadPositionX, y: 5)) } @@ -208,7 +159,7 @@ struct TimeRulerView: View context.draw( Text(currentTimeLabel) - .font(.system(size: 10, weight: .bold)) + .font(.system(size: 10)) .foregroundColor(.orange), at: CGPoint(x: playheadPositionX, y: 5)) } diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift index 7b0d831..b172d96 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift @@ -32,7 +32,7 @@ struct TimelineView : View { { self.timelineView() .allowsHitTesting(self.hitTestEnabled) -// .drawingGroup(opaque: true) + .drawingGroup(opaque: true) } .onScrollPhaseChange({ oldPhase, newPhase, context in @@ -40,8 +40,6 @@ struct TimelineView : View { self.hitTestEnabled = !newPhase.isScrolling }) - .frame(idealHeight: ( CGFloat((timeline.videoTracks.count + timeline.audioTracks.count)) * TrackView.trackHeight) + TimeRulerView.VerticalPadding ) - } else { @@ -50,8 +48,6 @@ struct TimelineView : View { { self.timelineView() } - .frame(idealHeight: ( CGFloat((timeline.videoTracks.count + timeline.audioTracks.count)) * TrackView.trackHeight) + TimeRulerView.VerticalPadding ) - } } @@ -59,25 +55,19 @@ struct TimelineView : View { { let videoTracks = timeline.videoTracks let audioTracks = timeline.audioTracks + -<<<<<<< HEAD VStack(alignment:.leading, spacing: 3) { - TimeRulerView(timeline: self.timeline, secondsToPixels: self.$secondsToPixels, currentTime: self.$currentTime ) - .frame(height: 50) - - ForEach(0..>>>>>> 883836b (Wire basic marker view up - need to validate placement logic) +// .overlay( +// self.getMarkerView( ) +// ) + // + + ForEach(0.. some View - { - HStack(alignment: .top) - { - let markerRange = (self.timeline.tracks?.markers.startIndex ?? 0) ..< (self.timeline.tracks?.markers.endIndex ?? 0) - ForEach(markerRange, id:\.self) { markerIndex in - MarkerView(marker: self.timeline.tracks!.markers[markerIndex], - secondsToPixels: self.$secondsToPixels) - } - } ->>>>>>> 883836b (Wire basic marker view up - need to validate placement logic) - } +// @ViewBuilder func getMarkerView() -> some View +// { +// HStack(alignment: .top, spacing: 0) +// { +// let markerRange = (self.timeline.tracks?.markers.startIndex ?? 0) ..< (self.timeline.tracks?.markers.endIndex ?? 0) +// ForEach(markerRange, id:\.self) { markerIndex in +// MarkerView(marker: self.timeline.tracks!.markers[markerIndex], +// secondsToPixels: self.$secondsToPixels) +// } +// } +//// .frame(width: self.getSafeWidth(), alignment: .leading ) +// .frame(alignment: .leading) +// .border(Color.purple, width: 2) +// } } From fc3df51fce4a8bfc87ac5777ad672ffdac415253 Mon Sep 17 00:00:00 2001 From: vade Date: Wed, 2 Jul 2025 01:08:44 -0400 Subject: [PATCH 62/67] Better marker drawing Signed-off-by: vade Signed-off-by: Anton Marini Signed-off-by: vade --- .../Views/TimeRulerView.swift | 74 ++++++++++++++----- 1 file changed, 56 insertions(+), 18 deletions(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift index c80d6c5..f7c3083 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift @@ -23,32 +23,70 @@ struct TimeRulerView: View let startSeconds = safeRange.startTime.toSeconds() let endSeconds = safeRange.endTimeInclusive().toSeconds() - if let tracks = self.timeline.tracks - { - let markerRange = tracks.markers.startIndex ..< tracks.markers.endIndex - for markerIndex in markerRange - { - let marker = tracks.markers[markerIndex] - let x = marker.markedRange.startTime.toSeconds() * self.secondsToPixels// + - - let path = Path(roundedRect: CGRect(origin: CGPoint(x: x, y: 0), - size: CGSize(width: 5.0, height: 9.0)), - cornerRadius: 3.0) - - context.fill(path, with: .color(.red)) - } - } + // Draw ticks (including frame-level ticks) + drawTicks(context: context, startSeconds: startSeconds, endSeconds: endSeconds, secondsToPixels: secondsToPixels, size: size) // Draw playhead drawPlayhead(context: context, currentTime: currentTime, secondsToPixels: secondsToPixels, size: size) - - // Draw ticks (including frame-level ticks) - drawTicks(context: context, startSeconds: startSeconds, endSeconds: endSeconds, secondsToPixels: secondsToPixels, size: size) + + drawMarkers(context: context, startSeconds: startSeconds, endSeconds: endSeconds, secondsToPixels: secondsToPixels, size: size) } .frame(width: self.getSafeWidth()) } + + func drawMarkers(context: GraphicsContext, startSeconds: Double, endSeconds: Double, secondsToPixels: Double, size: CGSize) + { + if let tracks = self.timeline.tracks + { + let markerRange = tracks.markers.startIndex ..< tracks.markers.endIndex + for markerIndex in markerRange + { + let marker = tracks.markers[markerIndex] + let x = marker.markedRange.startTime.toSeconds() * self.secondsToPixels// + + +// let path = Path(roundedRect: CGRect(origin: CGPoint(x: x, y: 13), +// size: CGSize(width: 3.0, height: 9.0)), +// cornerRadius: 1.0) +// +// context.fill(path, with: .color(.red)) + + let text = marker.name + + if #available(macOS 14.0, *) + { + context.draw( + Text("\(Image(systemName: "arrowtriangle.down.fill"))") + .font(.system(size: 10)) + .foregroundStyle(.red), + at: CGPoint(x: x + 0.5, y: 21)) + + + context.draw( + Text(text) + .font(.system(size: 10)) + .foregroundStyle(.white), + at: CGPoint(x: x , y: 12)) + } + else + { + context.draw( + Text("\(Image(systemName: "arrowtriangle.down.fill"))") + .font(.system(size: 10)) + .foregroundColor(.red), + at: CGPoint(x: x + 0.5, y: 21)) + + context.draw( + Text(text) + .font(.system(size: 10)) + .foregroundColor(.orange), + at: CGPoint(x: x , y: 12)) + } + } + } + } + func drawTicks(context: GraphicsContext, startSeconds: Double, endSeconds: Double, secondsToPixels: Double, size: CGSize) { if self.secondsToPixels > 75 From cc9453c4f0c2236c2561969292aec9e8b196a3e4 Mon Sep 17 00:00:00 2001 From: vade Date: Wed, 2 Jul 2025 01:10:39 -0400 Subject: [PATCH 63/67] Xleanup Signed-off-by: vade Signed-off-by: Anton Marini Signed-off-by: vade --- .../OpenTimelineIO-Reader/Views/TimeRulerView.swift | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift index f7c3083..eff5af2 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift @@ -44,13 +44,7 @@ struct TimeRulerView: View for markerIndex in markerRange { let marker = tracks.markers[markerIndex] - let x = marker.markedRange.startTime.toSeconds() * self.secondsToPixels// + - -// let path = Path(roundedRect: CGRect(origin: CGPoint(x: x, y: 13), -// size: CGSize(width: 3.0, height: 9.0)), -// cornerRadius: 1.0) -// -// context.fill(path, with: .color(.red)) + let x = marker.markedRange.startTime.toSeconds() * self.secondsToPixels let text = marker.name From 2d36954a8a9bf74005a08c38a9284ccb8f534d0d Mon Sep 17 00:00:00 2001 From: vade Date: Wed, 2 Jul 2025 01:27:06 -0400 Subject: [PATCH 64/67] more tweaks Signed-off-by: vade Signed-off-by: Anton Marini Signed-off-by: vade --- .../Views/TimeRulerView.swift | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift index eff5af2..2f03379 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift @@ -38,6 +38,7 @@ struct TimeRulerView: View func drawMarkers(context: GraphicsContext, startSeconds: Double, endSeconds: Double, secondsToPixels: Double, size: CGSize) { + let y = 15.0 if let tracks = self.timeline.tracks { let markerRange = tracks.markers.startIndex ..< tracks.markers.endIndex @@ -54,14 +55,17 @@ struct TimeRulerView: View Text("\(Image(systemName: "arrowtriangle.down.fill"))") .font(.system(size: 10)) .foregroundStyle(.red), - at: CGPoint(x: x + 0.5, y: 21)) + at: CGPoint(x: x + 0.5, y: y), + ) context.draw( Text(text) .font(.system(size: 10)) - .foregroundStyle(.white), - at: CGPoint(x: x , y: 12)) + .foregroundStyle(.red), + at: CGPoint(x: x + 9 , y: y - 2.0), + anchor: UnitPoint(x: 0, y: 0.5) +) } else { @@ -69,14 +73,23 @@ struct TimeRulerView: View Text("\(Image(systemName: "arrowtriangle.down.fill"))") .font(.system(size: 10)) .foregroundColor(.red), - at: CGPoint(x: x + 0.5, y: 21)) + at: CGPoint(x: x + 0.5, y: y), + ) context.draw( Text(text) .font(.system(size: 10)) - .foregroundColor(.orange), - at: CGPoint(x: x , y: 12)) + .foregroundColor(.red), + at: CGPoint(x: x + 9 , y: y - 2.0), + anchor: UnitPoint(x: 0, y: 0.5) + ) } + + let tickHeight = 20.0 + + // Draw tick line + let tickRect = CGRect(x: x, y: size.height - tickHeight, width: 1, height: tickHeight) + context.fill(Path(tickRect), with: .color(.red)) } } } @@ -177,7 +190,7 @@ struct TimeRulerView: View context.draw( Text(currentTimeLabel) - .font(.system(size: 10)) + .font(.system(size: 10, weight: .bold)) .foregroundStyle(.orange), at: CGPoint(x: playheadPositionX, y: 5)) } @@ -191,7 +204,7 @@ struct TimeRulerView: View context.draw( Text(currentTimeLabel) - .font(.system(size: 10)) + .font(.system(size: 10, weight: .bold)) .foregroundColor(.orange), at: CGPoint(x: playheadPositionX, y: 5)) } From 798aaf19a260114f0cf7e3f495f56a9d23199111 Mon Sep 17 00:00:00 2001 From: vade Date: Wed, 2 Jul 2025 09:04:55 -0400 Subject: [PATCH 65/67] Nicer marker drawing Signed-off-by: vade Signed-off-by: Anton Marini --- .../Views/ItemView.swift | 66 ++++++++----------- .../Views/TimeRulerView.swift | 7 +- .../Views/TimelineView.swift | 35 +++------- .../Views/TrackView.swift | 9 +-- 4 files changed, 43 insertions(+), 74 deletions(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift index 21df9dc..cdca6fb 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/ItemView.swift @@ -13,50 +13,42 @@ import SwiftUI struct ItemView : View { let item:OpenTimelineIO.Item - @State var backgroundColor:Color - @State var selected:Bool + let backgroundColor:Color + let selected:Bool + + private var isGap: Bool { + item.isKind(of: Gap.self) + } @Binding var secondsToPixels:Double var body: some View { - ZStack { - - if let _ = item as? Gap - { - RoundedRectangle(cornerRadius: 3) - .fill(Color("GapTrackBaseColor")) // Fill the RoundedRectangle with color - .overlay( - RoundedRectangle(cornerRadius: 3) - .stroke(self.selected ? .white : .clear, lineWidth: 1) // Add stroke/outline - ) - .frame(width: self.getSafeWidth() - 2) - } - else - { + ZStack + { + let fill:AnyShapeStyle = ( isGap ) ? AnyShapeStyle( Color("GapTrackBaseColor") ) : AnyShapeStyle(self.backgroundColor.gradient) + let textGapOpacity:Double = ( isGap ) ? 0.0 : 1.0 + + RoundedRectangle(cornerRadius: 3) + .fill(fill, style: FillStyle()) + .overlay( RoundedRectangle(cornerRadius: 3) - .fill(self.backgroundColor.gradient) // Fill the RoundedRectangle with color - .overlay( - RoundedRectangle(cornerRadius: 3) - .stroke(self.selected ? .white : .clear, lineWidth: 1) // Add stroke/outline - ) + .strokeBorder(self.selected ? .orange : .clear, lineWidth: 1) // Add stroke/outline .frame(width: self.getSafeWidth() - 2) - - if self.getSafeWidth() > 40 - { - Text(item.name) - .lineLimit(1) - .font(.system(size: 10)) - .frame(width: self.getSafeWidth()) - } - } - } - .frame(width: self.getSafeWidth(), alignment: .leading ) - .overlay { - self.getMarkerView() - - } -// .offset(x:self.getSafePositionX() )//, y:geometry.size.height * 0.5 ) + ) + .frame(width: self.getSafeWidth() - 2) + + Text(item.name) + .lineLimit(1) + .font(.system(size: 10)) + .frame(width: self.getSafeWidth()) + .opacity(self.getSafeWidth() > 40 ? textGapOpacity : 0.0) + } + .frame(width: self.getSafeWidth(), alignment: .leading ) + .overlay { + // TODO: Get item marker coordinate system working +// self.getMarkerView() + } } func getSafeRange() -> OpenTimelineIO.TimeRange diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift index 2f03379..3417277 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift @@ -23,7 +23,6 @@ struct TimeRulerView: View let startSeconds = safeRange.startTime.toSeconds() let endSeconds = safeRange.endTimeInclusive().toSeconds() - // Draw ticks (including frame-level ticks) drawTicks(context: context, startSeconds: startSeconds, endSeconds: endSeconds, secondsToPixels: secondsToPixels, size: size) @@ -38,7 +37,7 @@ struct TimeRulerView: View func drawMarkers(context: GraphicsContext, startSeconds: Double, endSeconds: Double, secondsToPixels: Double, size: CGSize) { - let y = 15.0 + let y = 20.0 if let tracks = self.timeline.tracks { let markerRange = tracks.markers.startIndex ..< tracks.markers.endIndex @@ -85,7 +84,7 @@ struct TimeRulerView: View ) } - let tickHeight = 20.0 + let tickHeight = 24.0 // Draw tick line let tickRect = CGRect(x: x, y: size.height - tickHeight, width: 1, height: tickHeight) @@ -177,7 +176,7 @@ struct TimeRulerView: View } let playheadPositionX = currentTime.toSeconds() * secondsToPixels - let playheadRect = CGRect(x: playheadPositionX, y: 20, width: 1, height: size.height-20) + let playheadRect = CGRect(x: playheadPositionX, y: 22, width: 1, height: size.height - 22) // context.fill(Path(playheadRect), with: .color(.orange)) if #available(macOS 14.0, *) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift index b172d96..d09055c 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift @@ -32,7 +32,7 @@ struct TimelineView : View { { self.timelineView() .allowsHitTesting(self.hitTestEnabled) - .drawingGroup(opaque: true) +// .drawingGroup(opaque: true) } .onScrollPhaseChange({ oldPhase, newPhase, context in @@ -40,6 +40,9 @@ struct TimelineView : View { self.hitTestEnabled = !newPhase.isScrolling }) + .frame(idealHeight: ( CGFloat((timeline.videoTracks.count + timeline.audioTracks.count)) * TrackView.trackHeight) + 100 ) + // .frame(maxHeight: CGFloat((videoTracks.count + audioTracks.count)) * 500) + } else { @@ -55,17 +58,12 @@ struct TimelineView : View { { let videoTracks = timeline.videoTracks let audioTracks = timeline.audioTracks - VStack(alignment:.leading, spacing: 3) { - TimeRulerView(timeline: self.timeline, secondsToPixels: self.$secondsToPixels, currentTime: self.$currentTime ) - .frame(height: 40) + TimeRulerView(timeline: self.timeline, secondsToPixels: self.$secondsToPixels, currentTime: self.$currentTime ) + .frame(height: 50) .offset(x:100) -// .overlay( -// self.getMarkerView( ) -// ) - // ForEach(0.. some View -// { -// HStack(alignment: .top, spacing: 0) -// { -// let markerRange = (self.timeline.tracks?.markers.startIndex ?? 0) ..< (self.timeline.tracks?.markers.endIndex ?? 0) -// ForEach(markerRange, id:\.self) { markerIndex in -// MarkerView(marker: self.timeline.tracks!.markers[markerIndex], -// secondsToPixels: self.$secondsToPixels) -// } -// } -//// .frame(width: self.getSafeWidth(), alignment: .leading ) -// .frame(alignment: .leading) -// .border(Color.purple, width: 2) -// } } diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TrackView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TrackView.swift index edf5202..4336b1b 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TrackView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TrackView.swift @@ -14,7 +14,6 @@ import SwiftUI struct TrackView : View { - static let trackHeaderWidth:CGFloat = 100 static let trackHeight:CGFloat = 35 let track:OpenTimelineIO.Track @@ -46,11 +45,7 @@ struct TrackView : View } } } -<<<<<<< HEAD .frame(width: self.getSafeWidth(), height:Self.trackHeight, alignment: .leading ) -======= - .frame(width: self.getSafeWidth(), alignment: .leading ) ->>>>>>> 883836b (Wire basic marker view up - need to validate placement logic) .overlay { self.getMarkerView() } @@ -70,7 +65,7 @@ struct TrackView : View .font(.system(size: 10)) .bold() } - .frame(width: Self.trackHeaderWidth, height:Self.trackHeight) + .frame(width: 100, height:Self.trackHeight) .onTapGesture { self.selectedItem = track print("selected Item") @@ -96,7 +91,7 @@ struct TrackView : View func getSafeWidth() -> CGFloat { - return self.getSafeRange().duration.toSeconds() * self.secondsToPixels + TrackView.trackHeaderWidth + return self.getSafeRange().duration.toSeconds() * self.secondsToPixels + 100 } func getSafePositionX() -> CGFloat From e9dafc61b4697a86355d71e8c7088d73be8b9d47 Mon Sep 17 00:00:00 2001 From: vade Date: Wed, 2 Jul 2025 17:36:18 -0400 Subject: [PATCH 66/67] Fix alignment of TimelineRuler canvas to allow for "overdraw" fixing #25 Signed-off-by: Anton Marini Signed-off-by: vade --- .../OpenTimelineIO-Reader/Views/TimeRulerView.swift | 7 ++++++- .../OpenTimelineIO-Reader/Views/TimelineView.swift | 6 +++--- .../OpenTimelineIO-Reader/Views/TrackView.swift | 5 +++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift index 3417277..440cd5f 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift @@ -11,6 +11,9 @@ import TimecodeKit import SwiftUI struct TimeRulerView: View { + + static let VerticalPadding:CGFloat = 100 + let timeline: OpenTimelineIO.Timeline @Binding var secondsToPixels: Double @Binding var currentTime: OpenTimelineIO.RationalTime @@ -19,6 +22,8 @@ struct TimeRulerView: View { Canvas { context, size in + context.translateBy(x: TrackView.trackHeaderWidth, y: 0) + let safeRange = getSafeRange() let startSeconds = safeRange.startTime.toSeconds() let endSeconds = safeRange.endTimeInclusive().toSeconds() @@ -32,7 +37,7 @@ struct TimeRulerView: View drawMarkers(context: context, startSeconds: startSeconds, endSeconds: endSeconds, secondsToPixels: secondsToPixels, size: size) } - .frame(width: self.getSafeWidth()) + .frame(width: self.getSafeWidth() + TrackView.trackHeaderWidth) } func drawMarkers(context: GraphicsContext, startSeconds: Double, endSeconds: Double, secondsToPixels: Double, size: CGSize) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift index d09055c..eb16a86 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimelineView.swift @@ -40,8 +40,7 @@ struct TimelineView : View { self.hitTestEnabled = !newPhase.isScrolling }) - .frame(idealHeight: ( CGFloat((timeline.videoTracks.count + timeline.audioTracks.count)) * TrackView.trackHeight) + 100 ) - // .frame(maxHeight: CGFloat((videoTracks.count + audioTracks.count)) * 500) + .frame(idealHeight: ( CGFloat((timeline.videoTracks.count + timeline.audioTracks.count)) * TrackView.trackHeight) + TimeRulerView.VerticalPadding ) } else @@ -51,6 +50,8 @@ struct TimelineView : View { { self.timelineView() } + .frame(idealHeight: ( CGFloat((timeline.videoTracks.count + timeline.audioTracks.count)) * TrackView.trackHeight) + TimeRulerView.VerticalPadding ) + } } @@ -63,7 +64,6 @@ struct TimelineView : View { { TimeRulerView(timeline: self.timeline, secondsToPixels: self.$secondsToPixels, currentTime: self.$currentTime ) .frame(height: 50) - .offset(x:100) ForEach(0.. CGFloat { - return self.getSafeRange().duration.toSeconds() * self.secondsToPixels + 100 + return self.getSafeRange().duration.toSeconds() * self.secondsToPixels + TrackView.trackHeaderWidth } func getSafePositionX() -> CGFloat From 94472ff12adb9eb31c6abb5e818c170c24373578 Mon Sep 17 00:00:00 2001 From: Anton Marini Date: Wed, 2 Jul 2025 17:51:22 -0400 Subject: [PATCH 67/67] Fix error Signed-off-by: Anton Marini Signed-off-by: vade --- .../OpenTimelineIO-Reader/Views/TimeRulerView.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift index 440cd5f..aef8ba9 100644 --- a/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift +++ b/OpenTimelineIO-Sample/OpenTimelineIO-Reader/Views/TimeRulerView.swift @@ -59,7 +59,7 @@ struct TimeRulerView: View Text("\(Image(systemName: "arrowtriangle.down.fill"))") .font(.system(size: 10)) .foregroundStyle(.red), - at: CGPoint(x: x + 0.5, y: y), + at: CGPoint(x: x + 0.5, y: y) ) @@ -77,7 +77,7 @@ struct TimeRulerView: View Text("\(Image(systemName: "arrowtriangle.down.fill"))") .font(.system(size: 10)) .foregroundColor(.red), - at: CGPoint(x: x + 0.5, y: y), + at: CGPoint(x: x + 0.5, y: y) ) context.draw(