├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── config.yml └── workflows │ └── build.yml ├── .gitignore ├── .swift-version ├── .swiftformat ├── .swiftpm └── xcode │ ├── package.xcworkspace │ └── contents.xcworkspacedata │ └── xcshareddata │ └── xcschemes │ └── DAWFileKit-CI.xcscheme ├── LICENSE ├── Package.swift ├── README.md ├── Sources └── DAWFileKit │ ├── Cubase │ ├── Cubase.swift │ └── TrackArchive │ │ ├── Conversion │ │ ├── TrackArchive Converting DAWMarkers.swift │ │ └── TrackArchive Extract DAWMarkers.swift │ │ ├── Errors │ │ ├── TrackArchive EncodeError.swift │ │ └── TrackArchive ParseError.swift │ │ ├── Messages │ │ ├── TrackArchive EncodeMessage.swift │ │ └── TrackArchive ParseMessage.swift │ │ ├── Protocols │ │ ├── CubaseTrackArchiveMarker.swift │ │ └── CubaseTrackArchiveTrack.swift │ │ ├── TrackArchive CycleMarker.swift │ │ ├── TrackArchive Helpers.swift │ │ ├── TrackArchive Init.swift │ │ ├── TrackArchive Main.swift │ │ ├── TrackArchive Marker.swift │ │ ├── TrackArchive MarkerTrack.swift │ │ ├── TrackArchive OrphanTrack.swift │ │ ├── TrackArchive Parse.swift │ │ ├── TrackArchive TempoTrack.swift │ │ ├── TrackArchive TimeType.swift │ │ ├── TrackArchive TrackTimeDomain.swift │ │ ├── TrackArchive xmlString.swift │ │ └── TrackArchive.swift │ ├── DAW Agnostic Types │ ├── DAWMarker Comparable.swift │ ├── DAWMarker Conversions.swift │ ├── DAWMarker Storage Value.swift │ ├── DAWMarker Storage.swift │ ├── DAWMarker.swift │ ├── DAWMarkerTrack.swift │ └── DAWTrackType.swift │ ├── FinalCutPro │ ├── FCPXML │ │ ├── Context │ │ │ ├── FCPXML ElementContext Items.swift │ │ │ ├── FCPXML ElementContext Tools.swift │ │ │ ├── FCPXML ElementContext.swift │ │ │ └── FCPXML FrameRateSource.swift │ │ ├── Element Types │ │ │ ├── ElementModelType │ │ │ │ ├── FCPXML AnyElementModelType Static.swift │ │ │ │ ├── FCPXML AnyElementModelType.swift │ │ │ │ ├── FCPXML ElementModelType XML.swift │ │ │ │ ├── FCPXML ElementModelType.swift │ │ │ │ ├── FCPXMLElementModelTypeProtocol Static.swift │ │ │ │ └── FCPXMLElementModelTypeProtocol.swift │ │ │ └── ElementType │ │ │ │ ├── FCPXML ElementType XML.swift │ │ │ │ └── FCPXML ElementType.swift │ │ ├── Errors │ │ │ └── FCPXML ParseError.swift │ │ ├── Extraction │ │ │ ├── FCPXML Extract.swift │ │ │ ├── FCPXML ExtractableChildren.swift │ │ │ ├── FCPXML ExtractedElement.swift │ │ │ ├── FCPXML Extraction.swift │ │ │ ├── FCPXML ExtractionScope.swift │ │ │ ├── FCPXMLExtractedElement.swift │ │ │ ├── FCPXMLExtractedModelElement.swift │ │ │ └── Presets │ │ │ │ ├── FCPXML CaptionsExtractionPreset.swift │ │ │ │ ├── FCPXML FrameDataPreset.swift │ │ │ │ ├── FCPXML MarkersExtractionPreset.swift │ │ │ │ ├── FCPXML RolesExtractionPreset.swift │ │ │ │ └── FCPXMLExtractionPreset.swift │ │ ├── FCPXML Properties.swift │ │ ├── FCPXML init.swift │ │ ├── FCPXML.swift │ │ ├── Model │ │ │ ├── Common │ │ │ │ ├── Attributes │ │ │ │ │ ├── FCPXML AudioLayout.swift │ │ │ │ │ ├── FCPXML AudioRate.swift │ │ │ │ │ ├── FCPXML ClipSourceEnable.swift │ │ │ │ │ ├── FCPXML FrameSampling.swift │ │ │ │ │ └── FCPXML TimecodeFormat.swift │ │ │ │ └── Elements │ │ │ │ │ ├── FCPXML AudioChannelSource.swift │ │ │ │ │ ├── FCPXML AudioRoleSource.swift │ │ │ │ │ ├── FCPXML ConformRate.swift │ │ │ │ │ ├── FCPXML MediaRep.swift │ │ │ │ │ ├── FCPXML Metadata Metadatum.swift │ │ │ │ │ ├── FCPXML Metadata.swift │ │ │ │ │ ├── FCPXML Text.swift │ │ │ │ │ ├── FCPXML TimeMap TimePoint.swift │ │ │ │ │ └── FCPXML TimeMap.swift │ │ │ ├── FCPXML Root Version.swift │ │ │ ├── FCPXML Root.swift │ │ │ ├── Meta │ │ │ │ └── AnyTimeline │ │ │ │ │ └── FCPXML AnyTimeline.swift │ │ │ ├── Protocols │ │ │ │ ├── FCPXMLAttribute.swift │ │ │ │ ├── FCPXMLElement Extensions.swift │ │ │ │ ├── FCPXMLElement.swift │ │ │ │ ├── Single Attributes │ │ │ │ │ ├── FCPXMLElementDuration.swift │ │ │ │ │ ├── FCPXMLElementFrameSampling.swift │ │ │ │ │ ├── FCPXMLElementModDate.swift │ │ │ │ │ ├── FCPXMLElementOffset.swift │ │ │ │ │ ├── FCPXMLElementStart.swift │ │ │ │ │ ├── FCPXMLElementTCFormat.swift │ │ │ │ │ └── FCPXMLElementTCStart.swift │ │ │ │ ├── Single Children │ │ │ │ │ ├── FCPXMLElementAudioChannelSourceChildren.swift │ │ │ │ │ ├── FCPXMLElementAudioRoleSourceChildren.swift │ │ │ │ │ ├── FCPXMLElementBookmarkChild.swift │ │ │ │ │ ├── FCPXMLElementMetadataChild.swift │ │ │ │ │ ├── FCPXMLElementNoteChild.swift │ │ │ │ │ ├── FCPXMLElementTextChildren.swift │ │ │ │ │ └── FCPXMLElementTextStyleDefinitionChildren.swift │ │ │ │ └── Story Elements │ │ │ │ │ ├── FCPXMLElementAnchorableAttributes.swift │ │ │ │ │ ├── FCPXMLElementAudioStartAndDuration.swift │ │ │ │ │ ├── FCPXMLElementClipAttributes.swift │ │ │ │ │ ├── FCPXMLElementClipAttributesOptionalDuration.swift │ │ │ │ │ ├── FCPXMLElementMediaAttributes.swift │ │ │ │ │ ├── FCPXMLElementMetaTimeline.swift │ │ │ │ │ └── FCPXMLElementTimingParams.swift │ │ │ ├── Resources │ │ │ │ ├── FCPXML Asset.swift │ │ │ │ ├── FCPXML Effect.swift │ │ │ │ ├── FCPXML Format.swift │ │ │ │ ├── FCPXML Locator.swift │ │ │ │ ├── FCPXML Media Multicam Angle.swift │ │ │ │ ├── FCPXML Media Multicam.swift │ │ │ │ ├── FCPXML Media.swift │ │ │ │ ├── FCPXML ObjectTracker TrackingShape.swift │ │ │ │ └── FCPXML ObjectTracker.swift │ │ │ ├── Roles │ │ │ │ ├── FCPXML AudioRole.swift │ │ │ │ ├── FCPXML CaptionRole.swift │ │ │ │ ├── FCPXML VideoRole.swift │ │ │ │ └── Meta │ │ │ │ │ ├── FCPXML AncestorRoles.swift │ │ │ │ │ ├── FCPXML AnyInterpolatedRole.swift │ │ │ │ │ ├── FCPXML AnyRole.swift │ │ │ │ │ ├── FCPXML RoleType.swift │ │ │ │ │ ├── FCPXMLCollapsibleRole.swift │ │ │ │ │ └── FCPXMLRole.swift │ │ │ ├── Story │ │ │ │ ├── Annotations │ │ │ │ │ ├── FCPXML Caption.swift │ │ │ │ │ ├── FCPXML Keyword.swift │ │ │ │ │ └── FCPXML Marker.swift │ │ │ │ ├── Clips │ │ │ │ │ ├── FCPXML AssetClip.swift │ │ │ │ │ ├── FCPXML Audio.swift │ │ │ │ │ ├── FCPXML Audition.swift │ │ │ │ │ ├── FCPXML Clip.swift │ │ │ │ │ ├── FCPXML Gap.swift │ │ │ │ │ ├── FCPXML RefClip.swift │ │ │ │ │ ├── FCPXML Title.swift │ │ │ │ │ ├── FCPXML Transition.swift │ │ │ │ │ ├── FCPXML Video.swift │ │ │ │ │ ├── MCClip │ │ │ │ │ │ ├── FCPXML MCClip.swift │ │ │ │ │ │ └── FCPXML MulticamSource.swift │ │ │ │ │ └── SyncClip │ │ │ │ │ │ ├── FCPXML SyncClip.swift │ │ │ │ │ │ └── FCPXML SyncSource.swift │ │ │ │ ├── FCPXML Sequence.swift │ │ │ │ └── FCPXML Spine.swift │ │ │ └── Structure │ │ │ │ ├── FCPXML Event.swift │ │ │ │ ├── FCPXML Library.swift │ │ │ │ └── FCPXML Project.swift │ │ ├── Occlusion │ │ │ ├── FCPXML Element Occlusion.swift │ │ │ └── FCPXML ElementOcclusion.swift │ │ ├── Utilities │ │ │ └── FCPXML Time Utilities.swift │ │ └── XML │ │ │ ├── FCPXML Attributes.swift │ │ │ ├── FCPXML Clip Parsing.swift │ │ │ ├── FCPXML Elements Parsing.swift │ │ │ ├── FCPXML Metadata Parsing.swift │ │ │ ├── FCPXML Resources Parsing.swift │ │ │ ├── FCPXML Roles Parsing.swift │ │ │ ├── FCPXML Root Parsing.swift │ │ │ ├── FCPXML Time and Frame Rate Parsing.swift │ │ │ └── XMLParsableAttributesKey.swift │ └── FinalCutPro.swift │ ├── MIDIFile │ ├── Conversion │ │ └── MIDIFile Converting DAWMarkers.swift │ └── Errors │ │ └── MIDIFile BuildError.swift │ ├── ProTools │ ├── ProTools.swift │ └── SessionInfo │ │ ├── Conversion │ │ └── SessionInfo Extract DAWMarkers.swift │ │ ├── Errors │ │ └── SessionInfo ParseError.swift │ │ ├── Messages │ │ └── SessionInfo ParseMessage.swift │ │ ├── Parse │ │ ├── SessionInfo Parse Sections.swift │ │ └── SessionInfo Parse.swift │ │ ├── SessionInfo Clip.swift │ │ ├── SessionInfo File.swift │ │ ├── SessionInfo Init.swift │ │ ├── SessionInfo Main.swift │ │ ├── SessionInfo Marker.swift │ │ ├── SessionInfo OrphanData.swift │ │ ├── SessionInfo Plugin.swift │ │ ├── SessionInfo TimeValue.swift │ │ ├── SessionInfo TimeValueFormat.swift │ │ ├── SessionInfo Track.swift │ │ ├── SessionInfo Versions.swift │ │ └── SessionInfo.swift │ └── Utilities │ ├── Utilities.swift │ └── XML Utilities.swift └── Tests └── DAWFileKitTests ├── Cubase ├── Cubase TrackArchive BasicMarkers.swift ├── Cubase TrackArchive Helper Tests.swift ├── Cubase TrackArchive MusicalAndLinearTest.swift ├── Cubase TrackArchive RoundingTest.swift ├── Cubase TrackArchive init converting.swift ├── Cubase TrackArchive xmlString.swift └── Resources │ └── Cubase TrackArchive XML Exports │ ├── BasicMarkers.xml │ ├── MusicalAndLinearTest.xml │ └── RoundingTest.xml ├── DAW Agnostic Types ├── DAWMarker Codable Tests.swift ├── DAWMarker Comparable Tests.swift └── DAWMarker Conversions Tests.swift ├── DAWFileKitTests Constants.swift ├── FinalCutPro ├── FCPXMLTestCase.swift ├── File Tests │ ├── FinalCutPro FCPXML 23.98.swift │ ├── FinalCutPro FCPXML 24.swift │ ├── FinalCutPro FCPXML 24With25Media.swift │ ├── FinalCutPro FCPXML 25i.swift │ ├── FinalCutPro FCPXML 29.97.swift │ ├── FinalCutPro FCPXML 29.97d.swift │ ├── FinalCutPro FCPXML 30.swift │ ├── FinalCutPro FCPXML 50.swift │ ├── FinalCutPro FCPXML 59.94.swift │ ├── FinalCutPro FCPXML 60.swift │ ├── FinalCutPro FCPXML Annotations.swift │ ├── FinalCutPro FCPXML AudioOnly.swift │ ├── FinalCutPro FCPXML AuditionMarkers.swift │ ├── FinalCutPro FCPXML AuditionMarkers2.swift │ ├── FinalCutPro FCPXML AuditionMarkers3.swift │ ├── FinalCutPro FCPXML BasicMarkers.swift │ ├── FinalCutPro FCPXML BasicMarkers_1HourProjectStart.swift │ ├── FinalCutPro FCPXML ClipMetadata.swift │ ├── FinalCutPro FCPXML Complex.swift │ ├── FinalCutPro FCPXML CompoundClips.swift │ ├── FinalCutPro FCPXML DisabledClips.swift │ ├── FinalCutPro FCPXML Keywords.swift │ ├── FinalCutPro FCPXML MulticamMarkers.swift │ ├── FinalCutPro FCPXML MulticamMarkers2.swift │ ├── FinalCutPro FCPXML Occlusion.swift │ ├── FinalCutPro FCPXML Occlusion2.swift │ ├── FinalCutPro FCPXML Occlusion3.swift │ ├── FinalCutPro FCPXML RolesList.swift │ ├── FinalCutPro FCPXML StandaloneAssetClip.swift │ ├── FinalCutPro FCPXML StandaloneLibraryEventClip.swift │ ├── FinalCutPro FCPXML StandaloneRefClip.swift │ ├── FinalCutPro FCPXML SyncClip.swift │ ├── FinalCutPro FCPXML SyncClipRoles.swift │ ├── FinalCutPro FCPXML SyncClipRoles2.swift │ ├── FinalCutPro FCPXML TitlesRoles.swift │ ├── FinalCutPro FCPXML TransitionMarkers1.swift │ ├── FinalCutPro FCPXML TransitionMarkers2.swift │ └── FinalCutPro FCPXML TwoClipsMarkers.swift ├── Logic and Parsing │ ├── FinalCutPro FCPXML Calculations.swift │ ├── FinalCutPro FCPXML Element Init Tests.swift │ ├── FinalCutPro FCPXML Format Info.swift │ ├── FinalCutPro FCPXML Frame Data Tests.swift │ ├── FinalCutPro FCPXML Library Tests.swift │ ├── FinalCutPro FCPXML Roles Parsing.swift │ ├── FinalCutPro FCPXML Root Version Tests.swift │ └── FinalCutPro FCPXML Structure.swift └── Resources │ └── FCPXML Exports │ ├── 23.98.fcpxml │ ├── 24.fcpxml │ ├── 24With25Media.fcpxml │ ├── 25i.fcpxml │ ├── 29.97.fcpxml │ ├── 29.97d.fcpxml │ ├── 30.fcpxml │ ├── 50.fcpxml │ ├── 59.94.fcpxml │ ├── 60.fcpxml │ ├── Annotations.fcpxml │ ├── AudioOnly.fcpxml │ ├── AuditionMarkers.fcpxml │ ├── AuditionMarkers2.fcpxml │ ├── AuditionMarkers3.fcpxml │ ├── BasicMarkers.fcpxml │ ├── BasicMarkers_1HourProjectStart.fcpxml │ ├── ClipMetadata.fcpxml │ ├── Complex.fcpxml │ ├── CompoundClips.fcpxml │ ├── DisabledClips.fcpxml │ ├── Keywords.fcpxml │ ├── MulticamMarkers.fcpxml │ ├── MulticamMarkers2.fcpxml │ ├── Occlusion.fcpxml │ ├── Occlusion2.fcpxml │ ├── Occlusion3.fcpxml │ ├── RolesList.fcpxml │ ├── StandaloneAssetClip.fcpxml │ ├── StandaloneLibraryEventClip.fcpxml │ ├── StandaloneRefClip.fcpxml │ ├── Structure.fcpxml │ ├── SyncClip.fcpxml │ ├── SyncClipRoles.fcpxml │ ├── SyncClipRoles2.fcpxml │ ├── TitlesRoles.fcpxml │ ├── TransitionMarkers1.fcpxml │ ├── TransitionMarkers2.fcpxml │ └── TwoClipsMarkers.fcpxml └── ProTools ├── ProTools SessionText 2023.12 Markers.swift ├── ProTools SessionText EmptySession.swift ├── ProTools SessionText ExtendedChars.swift ├── ProTools SessionText FPPFinal.swift ├── ProTools SessionText NewLinesAndTabs.swift ├── ProTools SessionText OneOfEverything.swift ├── ProTools SessionText OrphanData.swift ├── ProTools SessionText Plugins.swift ├── ProTools SessionText SimpleTest.swift ├── ProTools SessionText Time Formats BarsBeats.swift ├── ProTools SessionText TracksOnly.swift ├── Resources └── PT Session Text Exports │ ├── SessionText_EmptySession_23-976fps_DefaultExportOptions_PT2020.3.txt │ ├── SessionText_ExtendedChars_TextEditFormat_PT2023.3.txt │ ├── SessionText_ExtendedChars_UTF8Format_PT2023.3.txt │ ├── SessionText_FPPFinal_23-976fps_DefaultExportOptions_PT2020.3.txt │ ├── SessionText_MarkerRulersAndTrackMarkers_PT2023.12.txt │ ├── SessionText_NewLinesAndTabs_DefaultExportOptions_PT2023.6.txt │ ├── SessionText_OneOfEverything_23-976fps_DefaultExportOptions_PT2020.3.txt │ ├── SessionText_Plugins_23-976fps_DefaultExportOptions_PT2020.3.txt │ ├── SessionText_SimpleTest_23-976fps_DefaultExportOptions_PT2020.3.txt │ ├── SessionText_TimeFormats_BarsBeats_PT2022.9.txt │ ├── SessionText_TimeFormats_BarsBeats_ShowSubframes_PT2022.9.txt │ ├── SessionText_TimeFormats_FeetFrames_PT2022.9.txt │ ├── SessionText_TimeFormats_FeetFrames_ShowSubframes_PT2022.9.txt │ ├── SessionText_TimeFormats_MinSecs_PT2022.9.txt │ ├── SessionText_TimeFormats_MinSecs_ShowSubframes_PT2022.9.txt │ ├── SessionText_TimeFormats_Samples_PT2022.9.txt │ ├── SessionText_TimeFormats_Samples_ShowSubframes_PT2022.9.txt │ ├── SessionText_TimeFormats_Timecode_PT2022.9.txt │ ├── SessionText_TimeFormats_Timecode_ShowSubframes_PT2022.9.txt │ ├── SessionText_TracksOnly_OnlyTrackEDLs_PT2023.6.txt │ └── SessionText_UnrecognizedSection_23-976fps_DefaultExportOptions_PT2020.3.txt ├── TimeValue Tests.swift └── TimeValueFormat Tests.swift /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: orchetect 4 | # patreon: # Replace with a single Patreon username 5 | # open_collective: # Replace with a single Open Collective username 6 | # ko_fi: # Replace with a single Ko-fi username 7 | # tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | # community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | # liberapay: # Replace with a single Liberapay username 10 | # issuehunt: # Replace with a single IssueHunt username 11 | # otechie: # Replace with a single Otechie username 12 | # lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | # custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Create a bug report about a reproducible problem. 3 | labels: bug 4 | body: 5 | - type: textarea 6 | id: bug-description 7 | attributes: 8 | label: Bug Description, Steps to Reproduce, Crash Logs, Screenshots, etc. 9 | description: "A clear and concise description of the bug and steps to reproduce. Include system details (OS version) and build environment particulars (Xcode version, etc.)." 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Feature request 4 | url: https://github.com/orchetect/DAWFileKit/discussions 5 | about: Suggest new features or improvements. 6 | - name: I need help setting up or troubleshooting 7 | url: https://github.com/orchetect/DAWFileKit/discussions 8 | about: Questions not answered in the documentation, discussions forum, or example projects. 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Custom 2 | [Dd]ev/ 3 | 4 | # Xcode 5 | 6 | # macOS 7 | .DS_Store 8 | 9 | ## Build generated 10 | build/ 11 | DerivedData/ 12 | 13 | ## Various settings 14 | *.pbxuser 15 | !default.pbxuser 16 | *.mode1v3 17 | !default.mode1v3 18 | *.mode2v3 19 | !default.mode2v3 20 | *.perspectivev3 21 | !default.perspectivev3 22 | xcuserdata/ 23 | 24 | ## Other 25 | *.moved-aside 26 | *.xccheckout 27 | *.xcscmblueprint 28 | 29 | ## Obj-C/Swift specific 30 | *.hmap 31 | *.ipa 32 | *.dSYM.zip 33 | *.dSYM 34 | 35 | ## Playgrounds 36 | timeline.xctimeline 37 | playground.xcworkspace 38 | 39 | ## SPM support in Xcode 40 | # .swiftpm - for shared CI schemes we need these checked in: 41 | # -> .swiftpm/xcode/package.xcworkspace 42 | # -> .swiftpm/xcode/xcshareddata/xcschemes/*.* 43 | 44 | # Swift Package Manager 45 | # 46 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 47 | Packages/ 48 | Package.pins 49 | Package.resolved 50 | .build/ 51 | 52 | # CocoaPods 53 | # 54 | # We recommend against adding the Pods directory to your .gitignore. However 55 | # you should judge for yourself, the pros and cons are mentioned at: 56 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 57 | 58 | Pods/ 59 | 60 | # Carthage 61 | # 62 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 63 | # Carthage/Checkouts 64 | 65 | Carthage/Build 66 | 67 | # fastlane 68 | # 69 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 70 | # screenshots whenever they are needed. 71 | # For more information about the recommended setup visit: 72 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 73 | 74 | fastlane/report.xml 75 | fastlane/Preview.html 76 | fastlane/screenshots/**/*.png 77 | fastlane/test_output 78 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 5.8 -------------------------------------------------------------------------------- /.swiftformat: -------------------------------------------------------------------------------- 1 | --acronyms ID,URL,UUID 2 | --allman false 3 | --assetliterals visual-width 4 | --beforemarks 5 | --binarygrouping 8,8 6 | --categorymark "MARK: %c" 7 | --classthreshold 0 8 | --closingparen balanced 9 | --closurevoid remove 10 | --commas inline 11 | --conflictmarkers reject 12 | --decimalgrouping ignore 13 | --elseposition same-line 14 | --emptybraces spaced 15 | --enumthreshold 0 16 | --exponentcase lowercase 17 | --exponentgrouping disabled 18 | --extensionacl on-declarations 19 | --extensionlength 0 20 | --extensionmark "MARK: - %t + %c" 21 | --fractiongrouping disabled 22 | --fragment false 23 | --funcattributes prev-line 24 | --groupedextension "MARK: %c" 25 | --guardelse auto 26 | --header "\n {file}\n DAWFileKit • https://github.com/orchetect/DAWFileKit\n © {year} Steffan Andrews • Licensed under MIT License\n" 27 | --hexgrouping 4,8 28 | --hexliteralcase uppercase 29 | --ifdef no-indent 30 | --importgrouping alpha 31 | --indent 4 32 | --indentcase false 33 | --indentstrings true 34 | --lifecycle 35 | --lineaftermarks true 36 | --linebreaks lf 37 | --markcategories true 38 | --markextensions always 39 | --marktypes always 40 | --maxwidth 100 41 | --modifierorder 42 | --nevertrailing 43 | --nospaceoperators 44 | --nowrapoperators 45 | --octalgrouping 4,8 46 | --operatorfunc spaced 47 | --organizetypes actor,class,enum,struct 48 | --patternlet hoist 49 | --ranges spaced 50 | --redundanttype infer-locals-only 51 | --self remove 52 | --selfrequired 53 | --semicolons inline 54 | --shortoptionals always 55 | --smarttabs enabled 56 | --stripunusedargs always 57 | --structthreshold 0 58 | --tabwidth unspecified 59 | --trailingclosures 60 | --trimwhitespace nonblank-lines 61 | --typeattributes preserve 62 | --typemark "MARK: - %t" 63 | --varattributes preserve 64 | --voidtype void 65 | --wraparguments before-first 66 | --wrapcollections before-first 67 | --wrapconditions after-first 68 | --wrapparameters before-first 69 | --wrapreturntype preserve 70 | --wrapternary before-operators 71 | --wraptypealiases before-first 72 | --xcodeindentation enabled 73 | --yodaswap always 74 | --disable blankLinesAroundMark,consecutiveSpaces,preferKeyPath,redundantParens,sortDeclarations,sortedImports,unusedArguments 75 | --enable blankLinesBetweenImports,blockComments,isEmpty,wrapEnumCases 76 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Steffan Andrews - https://github.com/orchetect 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.8 2 | // (be sure to update the .swift-version file when this Swift version changes) 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "DAWFileKit", 8 | platforms: [ 9 | .macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6) 10 | ], 11 | products: [ 12 | .library(name: "DAWFileKit", targets: ["DAWFileKit"]) 13 | ], 14 | dependencies: [ 15 | .package(url: "https://github.com/orchetect/OTCore", from: "1.7.3"), 16 | .package(url: "https://github.com/orchetect/TimecodeKit", from: "2.3.3"), 17 | .package(url: "https://github.com/orchetect/MIDIKit", from: "0.10.0") 18 | ], 19 | targets: [ 20 | .target( 21 | name: "DAWFileKit", 22 | dependencies: [ 23 | "OTCore", 24 | "TimecodeKit", 25 | .product(name: "MIDIKitSMF", package: "MIDIKit") 26 | ] 27 | ), 28 | .testTarget( 29 | name: "DAWFileKitTests", 30 | dependencies: ["DAWFileKit"], 31 | resources: [ 32 | .copy("Cubase/Resources/Cubase TrackArchive XML Exports"), 33 | .copy("ProTools/Resources/PT Session Text Exports"), 34 | .copy("FinalCutPro/Resources/FCPXML Exports") 35 | ] 36 | ) 37 | ] 38 | ) 39 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/Cubase/Cubase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Cubase.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | import Foundation 8 | import TimecodeKit 9 | 10 | /// Collection of methods and structures related to Cubase. 11 | /// Do not instance; use methods within directly. 12 | public enum Cubase { 13 | public typealias PPQ = Double 14 | public typealias Tempo = Double 15 | 16 | /// `Timecode` setting for `.subFramesBase`. 17 | /// Cubase uses 80 subframes per frame. 18 | public static let timecodeSubFramesBase: Timecode.SubFramesBase = .max80SubFrames 19 | 20 | /// `Timecode` setting for `.upperLimit`. 21 | /// Cubase allows for up to 100 days, not confined to a 24-hour SMPTE timecode clock. 22 | public static let timecodeUpperLimit: Timecode.UpperLimit = .max100Days 23 | 24 | /// `Timecode` setting for `.stringFormat`. 25 | public static let timecodeStringFormat: Timecode.StringFormat = [] 26 | } 27 | 28 | extension Cubase { 29 | /// `Timecode` struct template. 30 | public static func formTimecode( 31 | realTime: TimeInterval, 32 | at rate: TimecodeFrameRate 33 | ) throws -> Timecode { 34 | try Timecode( 35 | .realTime(seconds: realTime), 36 | at: rate, 37 | base: timecodeSubFramesBase, 38 | limit: timecodeUpperLimit 39 | ) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/Cubase/TrackArchive/Errors/TrackArchive EncodeError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrackArchive EncodeError.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | 11 | extension Cubase.TrackArchive { 12 | public enum EncodeError: Error { 13 | case general(String) 14 | } 15 | } 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/Cubase/TrackArchive/Errors/TrackArchive ParseError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrackArchive ParseError.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | 11 | extension Cubase.TrackArchive { 12 | /// Cubase track archive XML parsing error. 13 | public enum ParseError: Error { 14 | case general(String) 15 | } 16 | } 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/Cubase/TrackArchive/Messages/TrackArchive EncodeMessage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrackArchive EncodeMessage.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | 11 | extension Cubase.TrackArchive { 12 | public enum EncodeMessage: Error { 13 | /// Info message. 14 | /// Can be disregarded and only useful for debugging. 15 | case info(String) 16 | 17 | /// Error message. 18 | /// Something was malformed or data format was not expected. 19 | case error(String) 20 | } 21 | } 22 | 23 | // MARK: - Extensions 24 | 25 | extension Collection where Element == Cubase.TrackArchive.EncodeMessage { 26 | /// Returns all `.info` cases as enum-unwrapped Strings. 27 | public var infos: [String] { 28 | reduce(into: [String]()) { 29 | switch $1 { 30 | case let .info(message): 31 | $0.append(message) 32 | default: 33 | break 34 | } 35 | } 36 | } 37 | 38 | /// Returns all `.error` cases as enum-unwrapped Strings. 39 | public var errors: [String] { 40 | reduce(into: [String]()) { 41 | switch $1 { 42 | case let .error(message): 43 | $0.append(message) 44 | default: 45 | break 46 | } 47 | } 48 | } 49 | } 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/Cubase/TrackArchive/Messages/TrackArchive ParseMessage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrackArchive ParseMessage.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | 11 | extension Cubase.TrackArchive { 12 | public enum ParseMessage: Error { 13 | /// Info message. 14 | /// Can be disregarded and only useful for debugging. 15 | case info(String) 16 | 17 | /// Error message. 18 | /// Something was malformed or data format was not expected. 19 | case error(String) 20 | } 21 | } 22 | 23 | // MARK: - Extensions 24 | 25 | extension Collection where Element == Cubase.TrackArchive.ParseMessage { 26 | /// Returns all `.info` cases as enum-unwrapped Strings. 27 | public var infos: [String] { 28 | reduce(into: [String]()) { 29 | switch $1 { 30 | case let .info(message): 31 | $0.append(message) 32 | default: 33 | break 34 | } 35 | } 36 | } 37 | 38 | /// Returns all `.error` cases as enum-unwrapped Strings. 39 | public var errors: [String] { 40 | reduce(into: [String]()) { 41 | switch $1 { 42 | case let .error(message): 43 | $0.append(message) 44 | default: 45 | break 46 | } 47 | } 48 | } 49 | } 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/Cubase/TrackArchive/Protocols/CubaseTrackArchiveMarker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CubaseTrackArchiveMarker.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | import TimecodeKit 11 | 12 | /// Protocol that DAWFileKit `Cubase.TrackArchive` markers conform to. 13 | public protocol CubaseTrackArchiveMarker { 14 | var name: String { get set } 15 | 16 | var startTimecode: Timecode { get set } 17 | var startRealTime: TimeInterval? { get set } 18 | } 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/Cubase/TrackArchive/Protocols/CubaseTrackArchiveTrack.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CubaseTrackArchiveTrack.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | 11 | /// Protocol that DAWFileKit `Cubase.TrackArchive` tracks conform to. 12 | public protocol CubaseTrackArchiveTrack { 13 | var name: String? { get set } 14 | } 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/Cubase/TrackArchive/TrackArchive CycleMarker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrackArchive CycleMarker.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | import TimecodeKit 11 | 12 | extension Cubase.TrackArchive { 13 | /// Represents a cycle marker event and its contents. 14 | public struct CycleMarker: CubaseTrackArchiveMarker { 15 | public var name: String = "" 16 | 17 | public var startTimecode: Timecode 18 | public var startRealTime: TimeInterval? 19 | 20 | public var lengthTimecode: Timecode 21 | public var lengthRealTime: TimeInterval? 22 | 23 | public init( 24 | name: String, 25 | startTimecode: Timecode, 26 | startRealTime: TimeInterval? = nil, 27 | lengthTimecode: Timecode, 28 | lengthRealTime: TimeInterval? = nil 29 | ) { 30 | self.name = name 31 | 32 | self.startTimecode = startTimecode 33 | self.startRealTime = startRealTime 34 | 35 | self.lengthTimecode = lengthTimecode 36 | self.lengthRealTime = lengthRealTime 37 | } 38 | } 39 | } 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/Cubase/TrackArchive/TrackArchive Init.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrackArchive Init.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | import OTCore 11 | import TimecodeKit 12 | 13 | // MARK: - Init 14 | 15 | extension Cubase.TrackArchive { 16 | /// Parse Track Archive XML file contents exported from Cubase. 17 | public init(fileContent data: Data) throws { 18 | var dummy: [ParseMessage] = [] 19 | try self.init(fileContent: data, messages: &dummy) 20 | } 21 | 22 | /// Parse Track Archive XML file contents exported from Cubase. 23 | public init( 24 | fileContent data: Data, 25 | messages: inout [ParseMessage] 26 | ) throws { 27 | let xmlDocument = try XMLDocument(data: data) 28 | let parsed = try Self.parse(fileContent: xmlDocument) 29 | self = parsed.trackArchive 30 | messages = parsed.messages 31 | } 32 | } 33 | 34 | extension Cubase.TrackArchive { 35 | /// Parse Track Archive XML file contents exported from Cubase. 36 | public init(fileContent xml: XMLDocument) throws { 37 | var dummy: [ParseMessage] = [] 38 | try self.init(fileContent: xml, messages: &dummy) 39 | } 40 | 41 | /// Parse Track Archive XML file contents exported from Cubase. 42 | public init( 43 | fileContent xml: XMLDocument, 44 | messages: inout [ParseMessage] 45 | ) throws { 46 | let parsed = try Self.parse(fileContent: xml) 47 | self = parsed.trackArchive 48 | messages = parsed.messages 49 | } 50 | } 51 | 52 | extension Cubase.TrackArchive { 53 | /// Parse Track Archive XML file contents exported from Cubase. 54 | public init(fileContent xmlRoot: XMLElement) { 55 | var dummy: [ParseMessage] = [] 56 | self.init(fileContent: xmlRoot, messages: &dummy) 57 | } 58 | 59 | /// Parse Track Archive XML file contents exported from Cubase. 60 | public init( 61 | fileContent xmlRoot: XMLElement, 62 | messages: inout [ParseMessage] 63 | ) { 64 | let parsed = Self.parse(fileContent: xmlRoot) 65 | self = parsed.trackArchive 66 | messages = parsed.messages 67 | } 68 | } 69 | 70 | #endif 71 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/Cubase/TrackArchive/TrackArchive Main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrackArchive Main.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | import TimecodeKit 11 | 12 | extension Cubase.TrackArchive { 13 | /// Contains the global session meta-data. 14 | public struct Main { 15 | public var startTimecode: Timecode? // 'Start'.'Time' (float, load as double) 16 | public var startTimeSeconds: TimeInterval? // 'Start'.'Time' (float, load as double) 17 | 18 | // public var startTimeDomain: ? // 'Start'.'Domain'.'Type' & 19 | // 'Start'.'Domain'.'Period' 20 | public var lengthTimecode: Timecode? // 'Length'.'Time' (float, load as double) 21 | // public var lengthTimeDomain: ? // 'Length'.'Domain'.'Type' & 22 | // 'Start'.'Domain'.'Period' 23 | 24 | public var frameRate: TimecodeFrameRate? // 'FrameType' 25 | 26 | // public var timeType: ? // 'TimeType' 27 | public var barOffset: Int? // 'BarOffset' 28 | 29 | public var sampleRate: Double? // 'SampleRate' 30 | public var bitDepth: Int? // 'SampleSize' 31 | 32 | // SampleFormatSize 33 | 34 | // 'RecordFile' 35 | // 'RecordFileType' ... 36 | 37 | // 'PanLaw' 38 | // 'VolumeMax' 39 | 40 | // 'HmtType' 41 | public var hmtDepth: Int? // 'HmtDepth' (percentage) 42 | } 43 | } 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/Cubase/TrackArchive/TrackArchive Marker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrackArchive Marker.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | import TimecodeKit 11 | 12 | extension Cubase.TrackArchive { 13 | /// Represents a marker event and its contents. 14 | public struct Marker: CubaseTrackArchiveMarker { 15 | public var name: String = "" 16 | 17 | public var startTimecode: Timecode 18 | public var startRealTime: TimeInterval? 19 | 20 | public init( 21 | name: String, 22 | startTimecode: Timecode, 23 | startRealTime: TimeInterval? = nil 24 | ) { 25 | self.name = name 26 | 27 | self.startTimecode = startTimecode 28 | self.startRealTime = startRealTime 29 | } 30 | } 31 | } 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/Cubase/TrackArchive/TrackArchive MarkerTrack.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrackArchive MarkerTrack.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | 11 | extension Cubase.TrackArchive { 12 | /// Represents a track and its contents. 13 | public struct MarkerTrack: CubaseTrackArchiveTrack { 14 | public var name: String? 15 | 16 | public var events: [CubaseTrackArchiveMarker] = [] 17 | 18 | public init() { } 19 | } 20 | } 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/Cubase/TrackArchive/TrackArchive OrphanTrack.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrackArchive OrphanTrack.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | 11 | extension Cubase.TrackArchive { 12 | /// An orphan track that could not be parsed. 13 | public struct OrphanTrack: CubaseTrackArchiveTrack { 14 | public var name: String? 15 | 16 | public let rawXMLContent: String 17 | } 18 | } 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/Cubase/TrackArchive/TrackArchive TempoTrack.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrackArchive TempoTrack.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | 11 | extension Cubase.TrackArchive { 12 | /// Represents a cycle marker event and its contents. 13 | public struct TempoTrack: CubaseTrackArchiveTrack { 14 | public var name: String? 15 | public var events: [Event] = [] 16 | } 17 | } 18 | 19 | extension Cubase.TrackArchive.TempoTrack { 20 | /// A tempo track event. 21 | public struct Event { 22 | public var startTimeAsPPQ: Cubase.PPQ 23 | public var tempo: Cubase.Tempo 24 | public var type: TempoEventType 25 | } 26 | } 27 | 28 | extension Cubase.TrackArchive.TempoTrack.Event { 29 | /// A tempo track event type. 30 | public enum TempoEventType { 31 | case jump 32 | case ramp 33 | } 34 | } 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/Cubase/TrackArchive/TrackArchive TimeType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrackArchive TimeType.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | 11 | extension Cubase.TrackArchive { 12 | /// Cubase Track Archive time type. 13 | public enum TimeType: Int { 14 | case secondsOrBarsAndBeats = 0 // seconds and bars+beats both show up as 0 15 | case timecode = 4 // and 13? 16 | case samples = 10 17 | case user = 11 18 | } 19 | } 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/Cubase/TrackArchive/TrackArchive TrackTimeDomain.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrackArchive TrackTimeDomain.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | 11 | extension Cubase.TrackArchive { 12 | /// Cubase Track Archive track time domain. 13 | public enum TrackTimeDomain: Int { 14 | /// Bars & beats timebase - computations are against PPQ base and tempo 15 | case musical = 0 16 | 17 | /// Time linear timebase - real / absolute time 18 | case linear = 1 19 | } 20 | } 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/Cubase/TrackArchive/TrackArchive.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrackArchive.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | import TimecodeKit 11 | 12 | // MARK: - Cubase.TrackArchive 13 | 14 | extension Cubase { 15 | /// Contains parsed data after reading a Cubase Track Archive XML file. 16 | public struct TrackArchive { 17 | // MARK: Contents 18 | 19 | /// Meta data contained in the main header of the data file. 20 | public var main = Main() 21 | 22 | /// Tempo track. 23 | /// (Essentially, a session can contain only one tempo track, but there is not a "tempo 24 | /// track" in the XML file; instead, tempo events are written to the first actual track.) 25 | public var tempoTrack = TempoTrack() 26 | 27 | /// Tracks listing. 28 | public var tracks: [CubaseTrackArchiveTrack]? 29 | 30 | // MARK: - Default init 31 | 32 | public init() { } 33 | } 34 | } 35 | 36 | // MARK: - Constants 37 | 38 | extension Cubase.TrackArchive { 39 | /// Static PPQ value used in Track Archive XML files (allegedly, until proven otherwise?) 40 | /// Changing PPQbase in Cubase preferences has no effect on this value. 41 | internal static let xmlPPQ = 480 42 | 43 | /// Array of file types for use with `NSOpenPanel` / `NSSavePanel`. 44 | public static let fileTypes = ["public.xml", "xml"] 45 | 46 | /// Frame rates and their numeric identifier as stored in the XML. 47 | internal static let frameRateTable: [Int: TimecodeFrameRate] = 48 | [ 49 | 02: .fps24, 50 | 03: .fps25, 51 | 04: .fps29_97, 52 | 05: .fps30, 53 | 06: .fps29_97d, 54 | 07: .fps30d, 55 | 12: .fps23_976, 56 | 13: .fps24_98, 57 | 14: .fps50, 58 | 15: .fps59_94, 59 | 16: .fps60 60 | ] 61 | 62 | internal static let trackTypeTable: [String: CubaseTrackArchiveTrack.Type] = [ 63 | "MMarkerTrackEvent": MarkerTrack.self 64 | // TODO: add additional track types in future 65 | ] 66 | } 67 | 68 | #endif 69 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/DAW Agnostic Types/DAWMarker Storage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DAWMarker Storage.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | import TimecodeKit 8 | 9 | extension DAWMarker { 10 | public struct Storage: Equatable, Hashable, Codable { 11 | /// Time value. 12 | public let value: Value 13 | 14 | /// The original frame rate that was associated with the `timeStorage` value. 15 | public let frameRate: TimecodeFrameRate 16 | 17 | /// The original timecode subframes divisor. 18 | public let base: Timecode.SubFramesBase 19 | 20 | public init( 21 | value: Value, 22 | frameRate: TimecodeFrameRate, 23 | base: Timecode.SubFramesBase 24 | ) { 25 | self.value = value 26 | self.frameRate = frameRate 27 | self.base = base 28 | } 29 | } 30 | } 31 | 32 | extension DAWMarker.Storage: Sendable { } 33 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/DAW Agnostic Types/DAWMarker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DAWMarker.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | import Foundation 8 | 9 | /// DAW-agnostic timeline marker. 10 | public struct DAWMarker: Codable { 11 | // MARK: Contents 12 | 13 | /// The core time value storage. 14 | /// Regardless of type, the value must always represent time elapsed from zero (00:00:00:00). 15 | public var timeStorage: Storage? = nil 16 | 17 | /// Main text of the marker. 18 | public var name: String = "" 19 | 20 | /// Comment associated with marker. Not all DAWs support comments; mainly Pro Tools. 21 | public var comment: String? 22 | 23 | // MARK: init 24 | 25 | public init() { } 26 | 27 | public init( 28 | storage: Storage? = nil, 29 | name: String = "", 30 | comment: String? = nil 31 | ) { 32 | timeStorage = storage 33 | self.name = name 34 | self.comment = comment 35 | } 36 | } 37 | 38 | extension DAWMarker: Sendable { } 39 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/DAW Agnostic Types/DAWMarkerTrack.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DAWMarkerTrack.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2024 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | import Foundation 8 | 9 | /// DAW-agnostic timeline capable of containing markers only. 10 | public struct DAWMarkerTrack: Codable { 11 | // MARK: Contents 12 | 13 | /// Track name. 14 | public var name: String = "" 15 | 16 | public var trackType: DAWTrackType 17 | 18 | /// Markers contained in the track. 19 | public var markers: [DAWMarker] 20 | 21 | // MARK: init 22 | 23 | public init( 24 | trackType: DAWTrackType, 25 | name: String = "", 26 | markers: [DAWMarker] = [] 27 | ) { 28 | self.trackType = trackType 29 | self.name = name 30 | self.markers = markers 31 | } 32 | } 33 | 34 | extension DAWMarkerTrack: Sendable { } 35 | 36 | // MARK: - Collection Methods 37 | 38 | extension Collection where Element == DAWMarkerTrack { 39 | public func first(trackNamed name: String, trackType: DAWTrackType) -> Element? { 40 | guard let index = firstIndex(trackNamed: name, trackType: trackType) else { return nil } 41 | return self[index] 42 | } 43 | 44 | public func firstIndex(trackNamed name: String, trackType: DAWTrackType) -> Index? { 45 | firstIndex { 46 | $0.name == name && $0.trackType == trackType 47 | } 48 | } 49 | 50 | public func first(trackNamed name: String) -> Element? { 51 | guard let index = firstIndex(trackNamed: name) else { return nil } 52 | return self[index] 53 | } 54 | 55 | public func firstIndex(trackNamed name: String) -> Index? { 56 | firstIndex { $0.name == name } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/DAW Agnostic Types/DAWTrackType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DAWTrackType.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2024 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | import Foundation 8 | 9 | public enum DAWTrackType: Equatable, Hashable, Codable { 10 | /// Ruler. 11 | /// Typically pinned to the GUI's top edge of the timeline. 12 | case ruler 13 | 14 | /// Track. 15 | /// A track used in a timeline. Usually reorder-able, deletable, and duplicatable. 16 | case track 17 | } 18 | 19 | extension DAWTrackType: Identifiable { 20 | public var id: Self { self } 21 | } 22 | 23 | extension DAWTrackType: Sendable { } 24 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Context/FCPXML ElementContext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXML ElementContext.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | import OTCore 11 | 12 | extension FinalCutPro.FCPXML { 13 | /// Element context identity and value builder to produce strongly-typed information gathered 14 | /// from an element. 15 | public struct ElementContext { 16 | public let valueBuilder: ValueBuilder 17 | 18 | public init( 19 | value: @escaping ValueBuilder 20 | ) { 21 | valueBuilder = value 22 | } 23 | 24 | /// Returns the context value using the associated context value builder. 25 | /// 26 | /// - Parameters: 27 | /// - element: The element. 28 | /// - breadcrumbs: All ancestors traversed including resource jumps. 29 | /// Ordered nearest to furthest ancestor. 30 | /// - resources: The document's `resources` container element. 31 | /// If `nil`, the `resources` found in the document will be used if present. 32 | public func value( 33 | from element: XMLElement, 34 | breadcrumbs: [XMLElement], 35 | resources: XMLElement? // `resources` container element 36 | ) -> Value { 37 | let tools = Tools( 38 | element: element, 39 | breadcrumbs: breadcrumbs, 40 | resources: resources 41 | ) 42 | 43 | let resources = resources 44 | ?? element.fcpRootResources 45 | ?? XMLElement(name: ElementType.resources.rawValue) 46 | 47 | return valueBuilder( 48 | element, 49 | breadcrumbs, 50 | resources, 51 | tools 52 | ) 53 | } 54 | } 55 | } 56 | 57 | extension FinalCutPro.FCPXML.ElementContext { 58 | /// Context value builder closure for an element. 59 | /// 60 | /// - Parameters: 61 | /// - element: The element. 62 | /// - breadcrumbs: All ancestors traversed including resource jumps. 63 | /// Ordered nearest to furthest ancestor. 64 | /// - resources: The document's `resources` container element provided for convenience. 65 | /// - tools: Convenience methods for building context. 66 | public typealias ValueBuilder = ( 67 | _ element: XMLElement, 68 | _ breadcrumbs: [XMLElement], 69 | _ resources: XMLElement, // `resources` container element 70 | _ tools: Tools 71 | ) -> Value 72 | } 73 | 74 | #endif 75 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Context/FCPXML FrameRateSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXML FrameRateSource.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | import TimecodeKit 11 | import OTCore 12 | 13 | extension FinalCutPro.FCPXML { 14 | /// Frame rate source for an extracted element. 15 | public enum FrameRateSource { 16 | /// Derive frame rate from main timeline. 17 | case mainTimeline 18 | 19 | /// Derive frame rate from the element's local timeline or closest parent timeline. 20 | case localToElement 21 | 22 | /// Provide an arbitrary frame rate to use. 23 | /// 24 | /// This is generally not recommended unless conversion to a different frame rate than the 25 | /// one used is desired. 26 | case rate(_ rate: TimecodeFrameRate) 27 | } 28 | } 29 | 30 | extension FinalCutPro.FCPXML.FrameRateSource: Sendable { } 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Element Types/ElementModelType/FCPXML AnyElementModelType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXML AnyElementModelType.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2023 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | 11 | // MARK: - AnyElementModelType 12 | 13 | extension FinalCutPro.FCPXML { 14 | public struct AnyElementModelType: Sendable { 15 | public var base: any FCPXMLElementModelTypeProtocol 16 | 17 | public var supportedElementTypes: Set { 18 | base.supportedElementTypes 19 | } 20 | 21 | public init(base: T) { 22 | self.base = base 23 | } 24 | } 25 | } 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Element Types/ElementModelType/FCPXML ElementModelType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXML ElementModelType.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2023 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | 11 | // MARK: - ElementModelType 12 | 13 | extension FinalCutPro.FCPXML { 14 | public struct ElementModelType: FCPXMLElementModelTypeProtocol { 15 | public var supportedElementTypes: Set { 16 | ModelType.supportedElementTypes 17 | } 18 | 19 | init() { } 20 | } 21 | } 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Element Types/ElementModelType/FCPXMLElementModelTypeProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXMLElementModelTypeProtocol.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2023 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | 11 | public protocol FCPXMLElementModelTypeProtocol 12 | where Self: Equatable, Self: Hashable, Self: Sendable 13 | { 14 | associatedtype ModelType: FCPXMLElement 15 | var supportedElementTypes: Set { get } 16 | } 17 | 18 | extension FCPXMLElementModelTypeProtocol { 19 | public var supportedElementTypes: Set { 20 | ModelType.supportedElementTypes 21 | } 22 | } 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Errors/FCPXML ParseError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXML ParseError.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | extension FinalCutPro.FCPXML { 10 | /// Final Cut Pro FCPXML file parsing error. 11 | public enum ParseError: Error { 12 | case general(String) 13 | } 14 | } 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Extraction/FCPXML Extract.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXML Extraction.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | import OTCore 11 | import TimecodeKit 12 | 13 | extension XMLElement { 14 | /// Extract the element with timeline context. 15 | /// This provides additional computed information such as absolute start timecode, occlusion, 16 | /// and more. 17 | /// 18 | /// > Note: 19 | /// > This can only contain as much context as is available in its XML scope. 20 | /// > Which means calling this on an element within a resource (media, multicam, etc.) will only 21 | /// > be able to provide context for the resource's scope and is not able to reach outside 22 | /// > to any parent timelines above it. 23 | /// > 24 | /// > If full context is required, do not use this method, but use 25 | /// > ``fcpExtract(types:scope:)`` instead. 26 | /// 27 | /// - Parameters: 28 | /// - constrainToLocalTimeline: If `true`, calculations for interior elements that involve the 29 | /// outermost timeline (such as absolute start timecode and occlusion) will be constrained to 30 | /// the initiating element's local timeline. If the element has no implicit local timeline, 31 | /// the local timeline of the first nested container will be used. 32 | public func fcpExtract( 33 | constrainToLocalTimeline: Bool = false 34 | ) async -> FinalCutPro.FCPXML.ExtractedElement { 35 | let scope = FinalCutPro.FCPXML.ExtractionScope( 36 | constrainToLocalTimeline: constrainToLocalTimeline, 37 | maxContainerDepth: nil, 38 | auditions: .active, 39 | mcClipAngles: .active, 40 | occlusions: .allCases, 41 | filteredTraversalTypes: [], 42 | excludedTraversalTypes: [], 43 | excludedExtractionTypes: [], 44 | traversalPredicate: { _ in false }, 45 | extractionPredicate: nil 46 | ) 47 | 48 | guard let elementType = fcpElementType, 49 | let extractedElement = await fcpExtract( 50 | types: [elementType], 51 | scope: scope 52 | ) 53 | .first 54 | else { 55 | assertionFailure("Element extraction did not return self.") 56 | return FinalCutPro.FCPXML.ExtractedElement( 57 | element: self, 58 | breadcrumbs: Array(ancestorElements(includingSelf: false)), 59 | resources: nil 60 | ) 61 | } 62 | 63 | return extractedElement 64 | } 65 | } 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Extraction/FCPXML ExtractedElement.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXML ExtractedElement.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | import TimecodeKit 11 | import OTCore 12 | 13 | extension FinalCutPro.FCPXML { 14 | // TODO: XMLElement is not Sendable 15 | 16 | /// Extracted element and its context. 17 | public struct ExtractedElement: @unchecked Sendable { 18 | public let element: XMLElement 19 | public let breadcrumbs: [XMLElement] 20 | public let resources: XMLElement? 21 | 22 | init( 23 | element: XMLElement, 24 | breadcrumbs: [XMLElement], 25 | resources: XMLElement? 26 | ) { 27 | self.element = element 28 | self.breadcrumbs = breadcrumbs 29 | self.resources = resources 30 | } 31 | 32 | /// Return the a context value for the element. 33 | public func value( 34 | forContext contextKey: FinalCutPro.FCPXML.ElementContext 35 | ) -> Value { 36 | contextKey.value(from: element, breadcrumbs: breadcrumbs, resources: resources) 37 | } 38 | } 39 | } 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Extraction/FCPXMLExtractedModelElement.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXMLExtractedModelElement.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | import TimecodeKit 11 | import OTCore 12 | 13 | /// Protocol for extracted elements that adds contextual properties. 14 | public protocol FCPXMLExtractedModelElement: FCPXMLExtractedElement { 15 | /// Concrete model type associated with the extracted element. 16 | associatedtype Model: FCPXMLElement 17 | } 18 | 19 | // MARK: - Default Implementation 20 | 21 | extension FCPXMLExtractedModelElement { 22 | /// Returns the XML element wrapped in a model struct. 23 | public var model: Model { 24 | // this guard only necessary because this returns an Optional 25 | guard let model = Model(element: element) else { 26 | assertionFailure("Could not form \(Model.self) model struct.") 27 | return Model() 28 | } 29 | return model 30 | } 31 | } 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Extraction/Presets/FCPXML CaptionsExtractionPreset.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXML CaptionsExtractionPreset.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | 11 | extension FinalCutPro.FCPXML { 12 | /// FCPXML extraction preset that extracts closed captions. 13 | public struct CaptionsExtractionPreset: FCPXMLExtractionPreset { 14 | public init() { } 15 | 16 | public func perform( 17 | on extractable: XMLElement, 18 | scope: FinalCutPro.FCPXML.ExtractionScope 19 | ) async -> [FinalCutPro.FCPXML.ExtractedCaption] { 20 | let extracted = await extractable.fcpExtract( 21 | types: [.caption], 22 | scope: scope 23 | ) 24 | 25 | let wrapped = extracted 26 | .map { ExtractedCaption($0) } 27 | 28 | return wrapped 29 | } 30 | } 31 | } 32 | 33 | extension FCPXMLExtractionPreset where Self == FinalCutPro.FCPXML.CaptionsExtractionPreset { 34 | /// FCPXML extraction preset that extracts closed captions. 35 | public static var captions: FinalCutPro.FCPXML.CaptionsExtractionPreset { 36 | FinalCutPro.FCPXML.CaptionsExtractionPreset() 37 | } 38 | } 39 | 40 | extension FinalCutPro.FCPXML { 41 | // TODO: XMLElement is not Sendable 42 | 43 | /// An extracted caption element with pertinent data. 44 | public struct ExtractedCaption: FCPXMLExtractedModelElement, @unchecked Sendable { 45 | public typealias Model = Caption 46 | public let element: XMLElement 47 | public let breadcrumbs: [XMLElement] 48 | public let resources: XMLElement? 49 | 50 | init(_ extractedElement: ExtractedElement) { 51 | element = extractedElement.element 52 | breadcrumbs = extractedElement.breadcrumbs 53 | resources = extractedElement.resources 54 | } 55 | 56 | /// Return the a context value for the element. 57 | public func value( 58 | forContext contextKey: FinalCutPro.FCPXML.ElementContext 59 | ) -> Value { 60 | contextKey.value(from: element, breadcrumbs: breadcrumbs, resources: resources) 61 | } 62 | 63 | // Convenience getters 64 | 65 | /// Caption name. 66 | public var name: String? { 67 | model.name 68 | } 69 | 70 | /// Caption note, if any. 71 | public var note: String? { 72 | model.note 73 | } 74 | 75 | /// Inherited roles from container(s). 76 | public var roles: [AnyInterpolatedRole] { 77 | value(forContext: .inheritedRoles) 78 | } 79 | } 80 | } 81 | 82 | #endif 83 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Extraction/Presets/FCPXML RolesExtractionPreset.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXML RolesExtractionPreset.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | import OTCore 11 | 12 | extension FinalCutPro.FCPXML { 13 | /// FCPXML extraction preset that extracts roles within a specified scope. 14 | /// Results are sorted by type, then by name. 15 | public struct RolesExtractionPreset: FCPXMLExtractionPreset { 16 | public var roleTypes: Set 17 | 18 | public init( 19 | roleTypes: Set 20 | ) { 21 | self.roleTypes = roleTypes 22 | } 23 | 24 | public func perform( 25 | on extractable: XMLElement, 26 | scope: FinalCutPro.FCPXML.ExtractionScope 27 | ) async -> [FinalCutPro.FCPXML.AnyRole] { 28 | // early return in case no types are specified 29 | guard !roleTypes.isEmpty else { return [] } 30 | 31 | let extracted = await extractable.fcpExtract(scope: scope) { element in 32 | element 33 | .value(forContext: .inheritedRoles) 34 | .filter(roleTypes: roleTypes) 35 | .map(\.wrapped) 36 | } 37 | 38 | let output = extracted 39 | .flatMap { $0 } 40 | .removingDuplicates() 41 | .sortedByRoleTypeThenByName() 42 | 43 | return output 44 | } 45 | } 46 | } 47 | 48 | extension FCPXMLExtractionPreset where Self == FinalCutPro.FCPXML.RolesExtractionPreset { 49 | /// FCPXML extraction preset that extracts roles within a specified scope. 50 | /// Results are sorted by type, then by name. 51 | public static func roles( 52 | roleTypes: Set = .allCases 53 | ) -> FinalCutPro.FCPXML.RolesExtractionPreset { 54 | FinalCutPro.FCPXML.RolesExtractionPreset( 55 | roleTypes: roleTypes 56 | ) 57 | } 58 | } 59 | 60 | #endif 61 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Extraction/Presets/FCPXMLExtractionPreset.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXMLExtractionPreset.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | 11 | /// Protocol describing an element extraction preset for FCPXML. 12 | public protocol FCPXMLExtractionPreset where Self: Sendable { 13 | associatedtype Result 14 | 15 | func perform( 16 | on extractable: XMLElement, 17 | scope: FinalCutPro.FCPXML.ExtractionScope 18 | ) async -> Result 19 | } 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/FCPXML init.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXML init.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | import TimecodeKit 11 | 12 | extension FinalCutPro.FCPXML { 13 | /// Parse FCPXML/FCPXMLD file contents exported from Final Cut Pro. 14 | public init(fileContent data: Data) throws { 15 | let xmlDocument = try XMLDocument(data: data) 16 | self.init(fileContent: xmlDocument) 17 | } 18 | 19 | /// Initialize from FCPXML file that has been loaded into an `XMLDocument`. 20 | /// 21 | /// For fcpxml v1.10+ .fcpxmld bundles, load the .fcpxml file that is inside the bundle. 22 | public init(fileContent xml: XMLDocument) { 23 | self.xml = xml 24 | } 25 | 26 | // TODO: Add init for a new empty FCPXML file. 27 | } 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/FCPXML.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXML.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | 11 | extension FinalCutPro { 12 | /// Final Cut Pro XML file (FCPXML/FCPXMLD) 13 | /// 14 | /// General structure when exporting from Final Cut Pro: 15 | /// 16 | /// ```xml 17 | /// 18 | /// 19 | /// 20 | /// 21 | /// 22 | /// 23 | /// 24 | /// 25 | /// 26 | /// 27 | /// 28 | /// 29 | /// 30 | /// 31 | /// 32 | /// 33 | /// ``` 34 | /// 35 | /// > Note: Starting in FCPXML 1.9, the elements that describe how to organize and use media assets are optional. 36 | /// > The only required element in the `fcpxml` root element is the `resources` element. 37 | /// > 38 | /// > ```xml 39 | /// > 40 | /// > ... 41 | /// > 42 | /// > ... 43 | /// > 44 | /// > ... 45 | /// > 46 | /// > 47 | /// > ``` 48 | /// 49 | /// > Final Cut Pro FCPXML 1.11 Reference: 50 | /// > 51 | /// > The root element in an FCPXML document is `fcpxml`, which can contain the following elements: 52 | /// > - A `resources` element, that contains descriptions of media assets and other resources. 53 | /// > - An optional `import-options` element, that controls how Final Cut Pro imports the FCPXML document. 54 | /// > - One of the following optional elements that describe how to organize and use media assets: 55 | /// > - a `library` element that contains a list of event elements; 56 | /// > - a series of `event` elements that contain story elements and project elements; or 57 | /// > - a combination of story elements and `project` elements. 58 | /// > 59 | /// > Note: Starting in FCPXML 1.9, the elements that describe how to organize and use media assets are optional. 60 | /// > The only required element in the `fcpxml` root element is the `resources` element. 61 | /// 62 | /// [Official FCPXML Apple docs]( 63 | /// https://developer.apple.com/documentation/professional_video_applications/fcpxml_reference/ 64 | /// ) 65 | public struct FCPXML { 66 | /// The FCPXML document. 67 | public var xml: XMLDocument 68 | } 69 | } 70 | 71 | #endif 72 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Model/Common/Attributes/FCPXML AudioLayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXML AudioLayout.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | 11 | extension FinalCutPro.FCPXML { 12 | /// `audioLayout` attribute value. 13 | public enum AudioLayout: String, Equatable, Hashable, CaseIterable, Sendable { 14 | case mono 15 | case stereo 16 | case surround 17 | } 18 | } 19 | 20 | extension FinalCutPro.FCPXML.AudioLayout: FCPXMLAttribute { 21 | public static let attributeName: String = "audioLayout" 22 | } 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Model/Common/Attributes/FCPXML AudioRate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXML AudioRate.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | 11 | extension FinalCutPro.FCPXML { 12 | /// `audioRate` attribute value. 13 | /// These are all of the rates that are selectable within Final Cut Pro 10.6.10. 14 | public enum AudioRate: Equatable, Hashable, CaseIterable, Sendable { 15 | case rate32kHz 16 | case rate44_1kHz 17 | case rate48kHz 18 | case rate88_2kHz 19 | case rate96kHz 20 | case rate176_4kHz 21 | case rate192kHz 22 | } 23 | } 24 | 25 | extension FinalCutPro.FCPXML.AudioRate: FCPXMLAttribute { 26 | public static let attributeName: String = "audioRate" 27 | } 28 | 29 | // `audioRate` attribute is used by two FCPXML elements: `asset` and `sequence`. 30 | // The two encode it differently however. 31 | // - `asset` encodes the value as "48000" 32 | // - `sequence` encodes the value as "48k" 33 | 34 | extension FinalCutPro.FCPXML.AudioRate { 35 | /// Attribute raw value for use in a `sequence` element. 36 | public var rawValueForSequence: String { 37 | switch self { 38 | case .rate32kHz: return "32k" 39 | case .rate44_1kHz: return "44.1k" 40 | case .rate48kHz: return "48k" 41 | case .rate88_2kHz: return "88.2k" 42 | case .rate96kHz: return "96k" 43 | case .rate176_4kHz: return "176.4k" 44 | case .rate192kHz: return "192k" 45 | } 46 | } 47 | 48 | /// Initialize using attribute's raw value from a `sequence` element. 49 | public init?(rawValueForSequence rawValue: String) { 50 | guard let match = Self.allCases 51 | .first(where: { $0.rawValueForSequence == rawValue }) 52 | else { return nil } 53 | self = match 54 | } 55 | } 56 | 57 | extension FinalCutPro.FCPXML.AudioRate { 58 | /// Attribute raw value for use in an `asset` element. 59 | public var rawValueForAsset: String { 60 | switch self { 61 | case .rate32kHz: return "32000" 62 | case .rate44_1kHz: return "44100" 63 | case .rate48kHz: return "48000" 64 | case .rate88_2kHz: return "88200" 65 | case .rate96kHz: return "96000" 66 | case .rate176_4kHz: return "176400" 67 | case .rate192kHz: return "192000" 68 | } 69 | } 70 | 71 | /// Initialize using attribute's raw value from an `asset` element. 72 | public init?(rawValueForAsset rawValue: String) { 73 | guard let match = Self.allCases 74 | .first(where: { $0.rawValueForAsset == rawValue }) 75 | else { return nil } 76 | 77 | self = match 78 | } 79 | } 80 | 81 | #endif 82 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Model/Common/Attributes/FCPXML ClipSourceEnable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXML ClipSourceEnable.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | 11 | extension FinalCutPro.FCPXML { 12 | /// Clip source enable value. (Used with `asset-clip` and `mc-clip`) 13 | public enum ClipSourceEnable: String, Equatable, Hashable, CaseIterable, Sendable { 14 | /// Audio and Video. 15 | case all 16 | 17 | /// Audio source. 18 | case audio 19 | 20 | /// Video source. 21 | case video 22 | } 23 | } 24 | 25 | extension FinalCutPro.FCPXML.ClipSourceEnable: FCPXMLAttribute { 26 | public static let attributeName: String = "srcEnable" 27 | } 28 | 29 | extension XMLElement { 30 | /// FCPXML: Returns value for attribute `srcEnable`. (Default: `.all`) 31 | /// Call on a `asset-clip` or `mc-clip` element only. 32 | public var fcpClipSourceEnable: FinalCutPro.FCPXML.ClipSourceEnable { 33 | get { 34 | let defaultValue: FinalCutPro.FCPXML.ClipSourceEnable = .all 35 | 36 | guard let value = stringValue(forAttributeNamed: FinalCutPro.FCPXML.ClipSourceEnable.attributeName) 37 | else { return defaultValue } 38 | 39 | return FinalCutPro.FCPXML.ClipSourceEnable(rawValue: value) ?? defaultValue 40 | } 41 | set { 42 | addAttribute(withName: FinalCutPro.FCPXML.ClipSourceEnable.attributeName, 43 | value: newValue.rawValue) 44 | } 45 | } 46 | } 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Model/Common/Attributes/FCPXML FrameSampling.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXML FrameSampling.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2023 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | 11 | extension FinalCutPro.FCPXML { 12 | /// `frameSampling` attribute value. 13 | /// Used in `conform-rate` and `timeMap` elements. 14 | public enum FrameSampling: String, Equatable, Hashable, CaseIterable, Sendable { 15 | case floor 16 | case nearestNeighbor = "nearest-neighbor" 17 | case frameBlending = "frame-blending" 18 | case opticalFlowClassic = "optical-flow-classic" 19 | case opticalFlow = "optical-flow" 20 | case opticalFlowFRC = "optical-flow-frc" 21 | } 22 | } 23 | 24 | extension FinalCutPro.FCPXML.FrameSampling: FCPXMLAttribute { 25 | public static let attributeName: String = "frameSampling" 26 | } 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Model/Common/Attributes/FCPXML TimecodeFormat.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXML TimecodeFormat.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | import TimecodeKit 11 | 12 | extension FinalCutPro.FCPXML { 13 | /// `tcFormat` attribute value. 14 | public enum TimecodeFormat: String, Equatable, Hashable, CaseIterable, Sendable { 15 | case dropFrame = "DF" 16 | case nonDropFrame = "NDF" 17 | } 18 | } 19 | 20 | extension FinalCutPro.FCPXML.TimecodeFormat: FCPXMLAttribute { 21 | public static let attributeName: String = "tcFormat" 22 | } 23 | 24 | extension FinalCutPro.FCPXML.TimecodeFormat { 25 | /// Returns `true` if format is drop-frame. 26 | public var isDrop: Bool { 27 | switch self { 28 | case .dropFrame: return true 29 | case .nonDropFrame: return false 30 | } 31 | } 32 | } 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Model/Protocols/FCPXMLAttribute.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXMLAttribute.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2023 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | import TimecodeKit 11 | 12 | public protocol FCPXMLAttribute { 13 | /// The XML attribute name. 14 | static var attributeName: String { get } 15 | } 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Model/Protocols/FCPXMLElement Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXMLElement Extensions.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2023 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | import TimecodeKit 11 | 12 | extension FCPXMLElement { 13 | /// Returns the timecode frame rate for the local timeline. 14 | public func localTimecodeFrameRate() -> TimecodeFrameRate? { 15 | // `sequence` has a `format` attribute, 16 | // and a tcFormat attribute determining drop or non-drop frame timecode 17 | element._fcpTimecodeFrameRate() 18 | } 19 | } 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Model/Protocols/FCPXMLElement.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXMLElement.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2023 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | import TimecodeKit 11 | 12 | /// Protocol which all FCPXML wrapper model objects conform. 13 | public protocol FCPXMLElement where Self: Equatable, Self: Hashable { 14 | /// The wrapped XML element object. 15 | var element: XMLElement { get } 16 | 17 | /// The FCPXML element type of the model instance. 18 | var elementType: FinalCutPro.FCPXML.ElementType { get } 19 | 20 | /// All FCPXML element types the model object is capable of handling. 21 | /// 22 | /// Most model objects only handle a single type. 23 | /// However some model objects are 'meta types' and can handle more than one, such as 24 | /// ``FinalCutPro/FCPXML/Marker`` which handles both `marker` and `chapter-marker`. 25 | static var supportedElementTypes: Set { get } 26 | 27 | /// Initialize a new empty element with defaults. 28 | init() 29 | 30 | /// Wrap a FCPXML element. 31 | /// Returns `nil` if the element does not match the model element type. 32 | init?(element: XMLElement) 33 | } 34 | 35 | extension FCPXMLElement /* : Equatable */ { 36 | public static func == (lhs: Self, rhs: O) -> Bool { 37 | lhs.element == rhs.element 38 | } 39 | 40 | public static func == (lhs: XMLElement, rhs: Self) -> Bool { 41 | lhs == rhs.element 42 | } 43 | 44 | public static func == (lhs: Self, rhs: XMLElement) -> Bool { 45 | lhs.element == rhs 46 | } 47 | } 48 | 49 | extension FCPXMLElement /* : Hashable */ { 50 | public func hash(into hasher: inout Hasher) { 51 | hasher.combine(element) 52 | } 53 | } 54 | 55 | // MARK: - Utilities 56 | 57 | extension FCPXMLElement { 58 | func _isElementTypeSupported(element: XMLElement? = nil) -> Bool { 59 | let e = element ?? self.element 60 | guard let et = e.fcpElementType, 61 | Self.supportedElementTypes.contains(et) 62 | else { return false } 63 | return true 64 | } 65 | } 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Model/Protocols/Single Attributes/FCPXMLElementDuration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXMLElementDuration.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2023 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | import TimecodeKit 11 | 12 | public protocol FCPXMLElementRequiredDuration: FCPXMLElement { 13 | /// Local timeline duration. (Required) 14 | var duration: Fraction { get nonmutating set } 15 | } 16 | 17 | extension FCPXMLElementRequiredDuration { 18 | public var duration: Fraction { 19 | get { element.fcpDuration ?? .zero } 20 | nonmutating set { element.fcpDuration = newValue } 21 | } 22 | 23 | /// Returns the local timeline duration of the element as timecode. 24 | public func durationAsTimecode( 25 | frameRateSource: FinalCutPro.FCPXML.FrameRateSource = .localToElement 26 | ) -> Timecode? { 27 | element._fcpDurationAsTimecode( 28 | frameRateSource: frameRateSource, 29 | default: .zero 30 | ) 31 | } 32 | } 33 | 34 | public protocol FCPXMLElementOptionalDuration: FCPXMLElement { 35 | /// Local timeline duration. 36 | var duration: Fraction? { get nonmutating set } 37 | } 38 | 39 | extension FCPXMLElementOptionalDuration { 40 | public var duration: Fraction? { 41 | get { element.fcpDuration } 42 | nonmutating set { element.fcpDuration = newValue } 43 | } 44 | 45 | /// Returns the start time of the element as timecode. 46 | public func durationAsTimecode( 47 | frameRateSource: FinalCutPro.FCPXML.FrameRateSource = .localToElement 48 | ) -> Timecode? { 49 | guard duration != nil else { return nil } 50 | return element._fcpDurationAsTimecode( 51 | frameRateSource: frameRateSource, 52 | default: nil 53 | ) 54 | } 55 | } 56 | 57 | // MARK: - XML Utils 58 | 59 | extension XMLElement { 60 | func _fcpDurationAsTimecode( 61 | frameRateSource: FinalCutPro.FCPXML.FrameRateSource = .localToElement, 62 | default defaultDuration: Fraction? = .zero 63 | ) -> Timecode? { 64 | guard let dur = fcpDuration ?? defaultDuration else { return nil } 65 | 66 | return try? _fcpTimecode( 67 | fromRational: dur, 68 | frameRateSource: frameRateSource 69 | ) 70 | } 71 | } 72 | 73 | #endif 74 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Model/Protocols/Single Attributes/FCPXMLElementFrameSampling.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXMLElementFrameSampling.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2023 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | import TimecodeKit 11 | 12 | public protocol FCPXMLElementFrameSampling: FCPXMLElement { 13 | /// Frame sampling. (Default: floor) 14 | var frameSampling: FinalCutPro.FCPXML.FrameSampling { get nonmutating set } 15 | } 16 | 17 | extension FCPXMLElementFrameSampling { 18 | private var _frameSamplingDefault: FinalCutPro.FCPXML.FrameSampling { .floor } 19 | 20 | public var frameSampling: FinalCutPro.FCPXML.FrameSampling { 21 | get { 22 | guard let value = element.stringValue(forAttributeNamed: "frameSampling") 23 | else { return _frameSamplingDefault } 24 | 25 | return FinalCutPro.FCPXML.FrameSampling(rawValue: value) ?? _frameSamplingDefault 26 | } 27 | nonmutating set { 28 | if newValue == _frameSamplingDefault { 29 | // can remove attribute if value is default 30 | element.removeAttribute(forName: "frameSampling") 31 | } else { 32 | element.addAttribute(withName: "frameSampling", value: newValue.rawValue) 33 | } 34 | } 35 | } 36 | } 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Model/Protocols/Single Attributes/FCPXMLElementModDate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXMLElementModDate.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2023 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | import TimecodeKit 11 | 12 | public protocol FCPXMLElementOptionalModDate: FCPXMLElement { 13 | /// Modification date. 14 | var modDate: String? { get nonmutating set } 15 | } 16 | 17 | extension FCPXMLElementOptionalModDate { 18 | public var modDate: String? { 19 | get { element.stringValue(forAttributeNamed: "modDate") } 20 | nonmutating set { element.addAttribute(withName: "modDate", value: newValue) } 21 | } 22 | } 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Model/Protocols/Single Attributes/FCPXMLElementOffset.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXMLElementOffset.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2023 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | import TimecodeKit 11 | 12 | public protocol FCPXMLElementRequiredOffset: FCPXMLElement { 13 | /// Local timeline offset. (Required) 14 | var offset: Fraction { get nonmutating set } 15 | } 16 | 17 | extension FCPXMLElementRequiredOffset { 18 | public var offset: Fraction { 19 | get { element.fcpOffset ?? .zero } 20 | nonmutating set { element.fcpOffset = newValue } 21 | } 22 | 23 | /// Returns the local timeline offset of the element as timecode. 24 | public func offsetAsTimecode( 25 | frameRateSource: FinalCutPro.FCPXML.FrameRateSource = .localToElement 26 | ) -> Timecode? { 27 | element._fcpOffsetAsTimecode( 28 | frameRateSource: frameRateSource, 29 | default: .zero 30 | ) 31 | } 32 | } 33 | 34 | public protocol FCPXMLElementOptionalOffset: FCPXMLElement { 35 | /// Local timeline offset. 36 | var offset: Fraction? { get nonmutating set } 37 | } 38 | 39 | extension FCPXMLElementOptionalOffset { 40 | public var offset: Fraction? { 41 | get { element.fcpOffset } 42 | nonmutating set { element.fcpOffset = newValue } 43 | } 44 | 45 | /// Returns the offset of the element as timecode. 46 | public func offsetAsTimecode( 47 | frameRateSource: FinalCutPro.FCPXML.FrameRateSource = .localToElement 48 | ) -> Timecode? { 49 | guard offset != nil else { return nil } 50 | return element._fcpOffsetAsTimecode( 51 | frameRateSource: frameRateSource, 52 | default: nil 53 | ) 54 | } 55 | } 56 | 57 | // MARK: - XML Utils 58 | 59 | extension XMLElement { 60 | func _fcpOffsetAsTimecode( 61 | frameRateSource: FinalCutPro.FCPXML.FrameRateSource = .localToElement, 62 | default defaultOffset: Fraction? = .zero 63 | ) -> Timecode? { 64 | guard let dur = fcpOffset ?? defaultOffset else { return nil } 65 | 66 | return try? _fcpTimecode( 67 | fromRational: dur, 68 | frameRateSource: frameRateSource 69 | ) 70 | } 71 | } 72 | 73 | #endif 74 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Model/Protocols/Single Attributes/FCPXMLElementStart.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXMLElementStart.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2023 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | import TimecodeKit 11 | 12 | public protocol FCPXMLElementRequiredStart: FCPXMLElement { 13 | /// Local timeline start. (Required) 14 | var start: Fraction { get nonmutating set } 15 | } 16 | 17 | extension FCPXMLElementRequiredStart { 18 | public var start: Fraction { 19 | get { element.fcpStart ?? .zero } 20 | nonmutating set { element.fcpStart = newValue } 21 | } 22 | 23 | /// Returns the start time of the element as timecode. 24 | public func startAsTimecode( 25 | frameRateSource: FinalCutPro.FCPXML.FrameRateSource = .localToElement 26 | ) -> Timecode? { 27 | element._fcpStartAsTimecode( 28 | frameRateSource: frameRateSource, 29 | default: .zero 30 | ) 31 | } 32 | } 33 | 34 | public protocol FCPXMLElementOptionalStart: FCPXMLElement { 35 | /// Local timeline start. 36 | var start: Fraction? { get nonmutating set } 37 | } 38 | 39 | extension FCPXMLElementOptionalStart { 40 | public var start: Fraction? { 41 | get { element.fcpStart } 42 | nonmutating set { element.fcpStart = newValue } 43 | } 44 | 45 | /// Returns the start time of the element as timecode. 46 | public func startAsTimecode( 47 | frameRateSource: FinalCutPro.FCPXML.FrameRateSource = .localToElement 48 | ) -> Timecode? { 49 | guard start != nil else { return nil } 50 | return element._fcpStartAsTimecode( 51 | frameRateSource: frameRateSource, 52 | default: .zero 53 | ) 54 | } 55 | } 56 | 57 | // MARK: - XML Utils 58 | 59 | extension XMLElement { 60 | func _fcpStartAsTimecode( 61 | frameRateSource: FinalCutPro.FCPXML.FrameRateSource = .localToElement, 62 | default defaultStart: Fraction? = .zero 63 | ) -> Timecode? { 64 | guard let dur = fcpStart ?? defaultStart else { return nil } 65 | 66 | return try? _fcpTimecode( 67 | fromRational: dur, 68 | frameRateSource: frameRateSource 69 | ) 70 | } 71 | } 72 | 73 | #endif 74 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Model/Protocols/Single Attributes/FCPXMLElementTCFormat.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXMLElementTCFormat.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2023 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | import TimecodeKit 11 | 12 | public protocol FCPXMLElementOptionalTCFormat: FCPXMLElement { 13 | /// Local timeline timecode format. 14 | var tcFormat: FinalCutPro.FCPXML.TimecodeFormat? { get nonmutating set } 15 | } 16 | 17 | extension FCPXMLElementOptionalTCFormat { 18 | public var tcFormat: FinalCutPro.FCPXML.TimecodeFormat? { 19 | get { element.fcpTCFormat } 20 | nonmutating set { element.fcpTCFormat = newValue } 21 | } 22 | } 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Model/Protocols/Single Attributes/FCPXMLElementTCStart.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXMLElementTCStart.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2023 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | import TimecodeKit 11 | 12 | public protocol FCPXMLElementOptionalTCStart: FCPXMLElement { 13 | /// Local timeline origin time. 14 | var tcStart: Fraction? { get nonmutating set } 15 | } 16 | 17 | extension FCPXMLElementOptionalTCStart { 18 | public var tcStart: Fraction? { 19 | get { element.fcpTCStart } 20 | nonmutating set { element.fcpTCStart = newValue } 21 | } 22 | 23 | /// Returns the start time of the element as timecode. 24 | public func tcStartAsTimecode( 25 | frameRateSource: FinalCutPro.FCPXML.FrameRateSource = .localToElement 26 | ) -> Timecode? { 27 | element._fcpTCStartAsTimecode( 28 | frameRateSource: frameRateSource 29 | ) 30 | } 31 | } 32 | 33 | // MARK: - XML Utils 34 | 35 | extension XMLElement { 36 | func _fcpTCStartAsTimecode( 37 | frameRateSource: FinalCutPro.FCPXML.FrameRateSource = .localToElement 38 | ) -> Timecode? { 39 | guard let tcStart = fcpTCStart else { return nil } 40 | return try? _fcpTimecode( 41 | fromRational: tcStart, 42 | frameRateSource: frameRateSource 43 | ) 44 | } 45 | } 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Model/Protocols/Single Children/FCPXMLElementAudioChannelSourceChildren.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXMLElementAudioChannelSourceChildren.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2023 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | import OTCore 11 | import TimecodeKit 12 | 13 | public protocol FCPXMLElementAudioChannelSourceChildren: FCPXMLElement { 14 | /// Child `audio-channel-source` elements. 15 | var audioChannelSources: LazyFCPXMLChildrenSequence { get nonmutating set } 16 | } 17 | 18 | extension FCPXMLElementAudioChannelSourceChildren { 19 | public var audioChannelSources: LazyFCPXMLChildrenSequence { 20 | get { element.fcpAudioChannelSources } 21 | nonmutating set { element.fcpAudioChannelSources = newValue } 22 | } 23 | } 24 | 25 | extension XMLElement { 26 | /// FCPXML: Returns child `audio-channel-source` elements. 27 | /// Use on `clip` or `asset-clip` elements. 28 | public var fcpAudioChannelSources: LazyFCPXMLChildrenSequence { 29 | get { children(whereFCPElement: .audioChannelSource) } 30 | set { _updateChildElements(ofType: .audioChannelSource, with: newValue) } 31 | } 32 | } 33 | #endif 34 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Model/Protocols/Single Children/FCPXMLElementAudioRoleSourceChildren.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXMLElementAudioRoleSourceChildren.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2023 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | import OTCore 11 | import TimecodeKit 12 | 13 | public protocol FCPXMLElementAudioRoleSourceChildren: FCPXMLElement { 14 | /// Child `audio-role-source` elements. 15 | var audioRoleSources: LazyFCPXMLChildrenSequence { get nonmutating set } 16 | } 17 | 18 | extension FCPXMLElementAudioRoleSourceChildren { 19 | public var audioRoleSources: LazyFCPXMLChildrenSequence { 20 | get { element.fcpAudioRoleSources } 21 | nonmutating set { element.fcpAudioRoleSources = newValue } 22 | } 23 | } 24 | 25 | extension XMLElement { 26 | /// FCPXML: Returns child `audio-role-source` elements. 27 | /// Use on `ref-clip`, `sync-source`, or `mc-source` elements. 28 | public var fcpAudioRoleSources: LazyFCPXMLChildrenSequence { 29 | get { children(whereFCPElement: .audioRoleSource) } 30 | set { _updateChildElements(ofType: .audioRoleSource, with: newValue) } 31 | } 32 | } 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Model/Protocols/Single Children/FCPXMLElementBookmarkChild.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXMLElementBookmarkChild.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2023 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | import TimecodeKit 11 | 12 | public protocol FCPXMLElementBookmarkChild: FCPXMLElement { 13 | /// Security-scoped bookmark data in a base64-encoded string. 14 | /// Access the `stringValue` property on the returned element. 15 | var bookmark: XMLElement? { get nonmutating set } 16 | 17 | /// Security-scoped bookmark data. 18 | /// Returns the decoded ``bookmark`` base64-encoded string as `Data`. 19 | var bookmarkData: Data? { get nonmutating set } 20 | } 21 | 22 | extension FCPXMLElementBookmarkChild { 23 | public var bookmark: XMLElement? { 24 | get { 25 | element.firstChildElement(named: FinalCutPro.FCPXML.ElementType.bookmark.rawValue) 26 | } 27 | nonmutating set { 28 | element._updateChildElements(ofType: .bookmark, withChild: newValue) 29 | } 30 | } 31 | 32 | public var bookmarkData: Data? { 33 | get { 34 | guard let value = element 35 | .firstChildElement(whereFCPElementType: .bookmark)? 36 | .stringValue 37 | else { return nil } 38 | 39 | return Data(base64Encoded: value) 40 | } 41 | nonmutating set { 42 | let v = newValue?.base64EncodedString() 43 | element._updateChildElement(named: "bookmark", newStringValue: v) 44 | } 45 | } 46 | } 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Model/Protocols/Single Children/FCPXMLElementMetadataChild.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXMLElementMetadataChild.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2023 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | import TimecodeKit 11 | 12 | public protocol FCPXMLElementMetadataChild: FCPXMLElement { 13 | /// Metadata for the element. 14 | var metadata: FinalCutPro.FCPXML.Metadata? { get nonmutating set } 15 | } 16 | 17 | extension FCPXMLElementMetadataChild { 18 | public var metadata: FinalCutPro.FCPXML.Metadata? { 19 | get { element.firstChild(whereFCPElement: .metadata) } 20 | nonmutating set { element._updateChildElements(ofType: .metadata, withChild: newValue) } 21 | } 22 | } 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Model/Protocols/Single Children/FCPXMLElementNoteChild.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXMLElementNoteChild.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2023 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | import TimecodeKit 11 | 12 | public protocol FCPXMLElementNoteChild: FCPXMLElement { 13 | /// Optional note text. 14 | var note: String? { get nonmutating set } 15 | } 16 | 17 | extension FCPXMLElementNoteChild { 18 | public var note: String? { 19 | get { 20 | element 21 | .firstChildElement(whereFCPElementType: .note)? 22 | .stringValue 23 | } 24 | nonmutating set { 25 | element 26 | ._updateFirstChildElement(ofType: .note, newStringValue: newValue) 27 | } 28 | } 29 | } 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Model/Protocols/Single Children/FCPXMLElementTextChildren.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXMLElementTextChildren.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2023 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | import OTCore 11 | import TimecodeKit 12 | 13 | public protocol FCPXMLElementTextChildren: FCPXMLElement { 14 | /// Child `text` elements. 15 | var texts: LazyFCPXMLChildrenSequence { get nonmutating set } 16 | } 17 | 18 | extension FCPXMLElementTextChildren { 19 | public var texts: LazyFCPXMLChildrenSequence { 20 | get { element.fcpTexts } 21 | nonmutating set { element.fcpTexts = newValue } 22 | } 23 | } 24 | 25 | extension XMLElement { 26 | /// FCPXML: Returns child `text` elements. 27 | public var fcpTexts: LazyFCPXMLChildrenSequence { 28 | get { children(whereFCPElement: .text) } 29 | set { _updateChildElements(ofType: .text, with: newValue) } 30 | } 31 | } 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Model/Protocols/Single Children/FCPXMLElementTextStyleDefinitionChildren.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXMLElementTextStyleDefinitionChildren.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2023 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | import TimecodeKit 11 | import OTCore 12 | 13 | public protocol FCPXMLElementTextStyleDefinitionChildren: FCPXMLElement { 14 | /// Child `text-style-def` elements. 15 | var fcpTextStyleDefinitions: LazyFilteredCompactMapSequence<[XMLNode], XMLElement> { get nonmutating set } 16 | } 17 | 18 | extension FCPXMLElementTextStyleDefinitionChildren { 19 | public var fcpTextStyleDefinitions: LazyFilteredCompactMapSequence<[XMLNode], XMLElement> { 20 | get { element.fcpTextStyleDefinitions } 21 | nonmutating set { element.fcpTextStyleDefinitions = newValue } 22 | } 23 | } 24 | 25 | extension XMLElement { 26 | // TODO: no model objects yet, so just return the bare XML 27 | 28 | /// FCPXML: Returns child `text-style-def` elements. 29 | public var fcpTextStyleDefinitions: LazyFilteredCompactMapSequence<[XMLNode], XMLElement> { 30 | get { 31 | childElements 32 | .filter(whereFCPElementType: .textStyleDef) 33 | } 34 | set { 35 | _updateChildElements(ofType: .textStyleDef, with: newValue) 36 | } 37 | } 38 | } 39 | #endif 40 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Model/Protocols/Story Elements/FCPXMLElementAnchorableAttributes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXMLElementAnchorableAttributes.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2023 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | import TimecodeKit 11 | 12 | /// FCPXML 1.11 DTD: 13 | /// 14 | /// ```xml 15 | /// 16 | /// 17 | /// 18 | /// 19 | /// 20 | /// 21 | /// 25 | /// ``` 26 | public protocol FCPXMLElementAnchorableAttributes: FCPXMLElement { 27 | /// Lane. 28 | /// Specifies where the object is contained/anchored relative to its parent. 29 | /// 30 | /// - `0` = contained inside its parent (default) 31 | /// - `>0` = anchored above its parent 32 | /// - `<0` = anchored below its parent 33 | var lane: Int? { get nonmutating set } 34 | 35 | /// Offset within parent timeline. (Default: 0) 36 | var offset: Fraction? { get nonmutating set } 37 | } 38 | 39 | extension FCPXMLElementAnchorableAttributes { 40 | public var lane: Int? { 41 | get { element.fcpLane } 42 | nonmutating set { element.fcpLane = newValue } 43 | } 44 | 45 | public var offset: Fraction? { 46 | get { element.fcpOffset } 47 | nonmutating set { element.fcpOffset = newValue } 48 | } 49 | } 50 | 51 | extension FCPXMLElementAnchorableAttributes { 52 | /// Returns the offset of the element as timecode. 53 | public func offsetAsTimecode( 54 | frameRateSource: FinalCutPro.FCPXML.FrameRateSource = .localToElement 55 | ) -> Timecode? { 56 | guard let offset = offset else { return nil } 57 | return try? element._fcpTimecode( 58 | fromRational: offset, 59 | frameRateSource: frameRateSource 60 | ) 61 | } 62 | } 63 | 64 | #endif 65 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Model/Protocols/Story Elements/FCPXMLElementAudioStartAndDuration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXMLElementAudioStartAndDuration.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2023 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | import TimecodeKit 11 | 12 | /// FCPXML 1.11 DTD: 13 | /// 14 | /// Use `audioStart` and `audioDuration` attributes to define J/L cuts (i.e., split edits) on 15 | /// composite A/V clips. 16 | public protocol FCPXMLElementAudioStartAndDuration: FCPXMLElement { 17 | var audioStart: Fraction? { get nonmutating set } 18 | var audioDuration: Fraction? { get nonmutating set } 19 | } 20 | 21 | extension FCPXMLElementAudioStartAndDuration { 22 | public var audioStart: Fraction? { 23 | get { element.fcpAudioStart } 24 | nonmutating set { element.fcpAudioStart = newValue } 25 | } 26 | 27 | public var audioDuration: Fraction? { 28 | get { element.fcpAudioDuration } 29 | nonmutating set { element.fcpAudioDuration = newValue } 30 | } 31 | } 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Model/Protocols/Story Elements/FCPXMLElementClipAttributes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXMLElementClipAttributes.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2023 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | import TimecodeKit 11 | 12 | /// FCPXML 1.11 DTD: 13 | /// 14 | /// ```xml 15 | /// 16 | /// 17 | /// 18 | /// 25 | /// ``` 26 | public protocol FCPXMLElementClipAttributes: FCPXMLElement, 27 | FCPXMLElementAnchorableAttributes, 28 | FCPXMLElementOptionalStart, FCPXMLElementRequiredDuration 29 | { 30 | /// Clip name. 31 | var name: String? { get nonmutating set } 32 | 33 | // FCPXMLElementStart 34 | /// Clip local timeline start time. 35 | var start: Fraction? { get nonmutating set } 36 | 37 | // FCPXMLElementRequiredDuration 38 | /// Clip duration. 39 | var duration: Fraction { get nonmutating set } 40 | 41 | /// Clip enabled state. (Default: `true`) 42 | var enabled: Bool { get nonmutating set } 43 | } 44 | 45 | extension FCPXMLElementClipAttributes { 46 | public var name: String? { 47 | get { element.fcpName } 48 | nonmutating set { element.fcpName = newValue } 49 | } 50 | 51 | // implemented by FCPXMLElementOptionalStart 52 | // public var start: Fraction? 53 | 54 | // implemented by FCPXMLElementRequiredDuration 55 | // public var duration: Fraction 56 | 57 | public var enabled: Bool { 58 | get { element.fcpGetEnabled(default: true) } 59 | nonmutating set { element.fcpSet(enabled: newValue, default: true) } 60 | } 61 | } 62 | 63 | #endif 64 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Model/Protocols/Story Elements/FCPXMLElementClipAttributesOptionalDuration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXMLElementClipAttributesOptionalDuration.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2023 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | import TimecodeKit 11 | 12 | /// FCPXML 1.11 DTD: 13 | /// 14 | /// ```xml 15 | /// 16 | /// 23 | /// ``` 24 | public protocol FCPXMLElementClipAttributesOptionalDuration: FCPXMLElement, 25 | FCPXMLElementAnchorableAttributes, 26 | FCPXMLElementOptionalStart, FCPXMLElementOptionalDuration 27 | { 28 | /// Clip name. 29 | var name: String? { get nonmutating set } 30 | 31 | // FCPXMLElementStart 32 | /// Clip local timeline start time. 33 | var start: Fraction? { get nonmutating set } 34 | 35 | // FCPXMLElementOptionalDuration 36 | /// Clip duration. 37 | var duration: Fraction? { get nonmutating set } 38 | 39 | /// Clip enabled state. (Default: `true`) 40 | var enabled: Bool { get nonmutating set } 41 | } 42 | 43 | extension FCPXMLElementClipAttributesOptionalDuration { 44 | public var name: String? { 45 | get { element.fcpName } 46 | nonmutating set { element.fcpName = newValue } 47 | } 48 | 49 | // implemented by FCPXMLElementOptionalStart 50 | // public var start: Fraction? 51 | 52 | // implemented by FCPXMLElementOptionalDuration 53 | // public var duration: Fraction? 54 | 55 | public var enabled: Bool { 56 | get { element.fcpGetEnabled(default: true) } 57 | nonmutating set { element.fcpSet(enabled: newValue, default: true) } 58 | } 59 | } 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Model/Protocols/Story Elements/FCPXMLElementMediaAttributes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXMLElementMediaAttributes.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2023 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | import TimecodeKit 11 | 12 | /// FCPXML 1.11 DTD: 13 | /// 14 | /// ```xml 15 | /// 16 | /// 17 | /// 18 | /// 19 | /// 25 | /// ``` 26 | public protocol FCPXMLElementMediaAttributes: FCPXMLElement, 27 | FCPXMLElementOptionalDuration, 28 | FCPXMLElementOptionalTCStart, 29 | FCPXMLElementOptionalTCFormat 30 | { 31 | /// Format resource ID. (Required) 32 | var format: String { get nonmutating set } 33 | 34 | // FCPXMLElementOptionalDuration 35 | /// Local timeline duration. 36 | var duration: Fraction? { get nonmutating set } 37 | 38 | // FCPXMLElementTCStart 39 | /// Local timeline start time. 40 | var tcStart: Fraction? { get nonmutating set } 41 | 42 | // FCPXMLElementTCFormat 43 | /// Local timeline timecode format. 44 | var tcFormat: FinalCutPro.FCPXML.TimecodeFormat? { get nonmutating set } 45 | } 46 | 47 | extension FCPXMLElementMediaAttributes { 48 | public var format: String { 49 | get { element.fcpFormat ?? "" } 50 | nonmutating set { element.fcpFormat = newValue } 51 | } 52 | 53 | // implemented by FCPXMLElementOptionalDuration 54 | // public var duration: Fraction? 55 | 56 | // implemented by FCPXMLElementOptionalTCStart 57 | // public var tcStart: Fraction? 58 | 59 | // implemented by FCPXMLElementOptionalTCFormat 60 | // public var tcFormat: FinalCutPro.FCPXML.TimecodeFormat? 61 | } 62 | 63 | #endif 64 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Model/Protocols/Story Elements/FCPXMLElementTimingParams.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXMLElementTimingParams.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2023 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | import OTCore 11 | import TimecodeKit 12 | 13 | /// FCPXML 1.11 DTD: 14 | /// 15 | /// ```xml 16 | /// 17 | /// 18 | /// ``` 19 | public protocol FCPXMLElementTimingParams: FCPXMLElement { 20 | /// Clip conform rate. 21 | /// 22 | /// > FCPXML 1.11 DTD: 23 | /// > 24 | /// > "A `conform-rate` defines how the clip's frame rate should be conformed to the sequence frame rate". 25 | var conformRate: FinalCutPro.FCPXML.ConformRate? { get nonmutating set } 26 | 27 | /// Clip time map. 28 | /// 29 | /// > FCPXML 1.11 DTD: 30 | /// > 31 | /// > "A `timeMap` is a container for `timept` elements that change the output speed of the clip's local timeline. 32 | /// > When present, a `timeMap` defines a new adjusted time range for the clip using the first and last `timept` 33 | /// > elements. All other time values are interpolated from the specified `timept` elements." 34 | var timeMap: FinalCutPro.FCPXML.TimeMap? { get nonmutating set } 35 | } 36 | 37 | extension FCPXMLElementTimingParams { 38 | public var conformRate: FinalCutPro.FCPXML.ConformRate? { 39 | get { 40 | element.firstChild(whereFCPElement: .conformRate) 41 | } 42 | nonmutating set { 43 | element._updateFirstChildElement(ofType: .conformRate, withChild: newValue) 44 | } 45 | } 46 | 47 | public var timeMap: FinalCutPro.FCPXML.TimeMap? { 48 | get { 49 | element.firstChild(whereFCPElement: .timeMap) 50 | } 51 | nonmutating set { 52 | element._updateFirstChildElement(ofType: .timeMap, withChild: newValue) 53 | } 54 | } 55 | } 56 | 57 | #endif 58 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Model/Resources/FCPXML Locator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXML Locator.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | 11 | extension FinalCutPro.FCPXML { 12 | /// Locator shared resource. 13 | /// 14 | /// > Final Cut Pro FCPXML 1.11 Reference: 15 | /// > 16 | /// > Describe a URL-based resource. 17 | /// > 18 | /// > Use the `locator` element to describe the location of data files associated with another 19 | /// > FCPXML element. 20 | /// > 21 | /// > See [`locator`](https://developer.apple.com/documentation/professional_video_applications/fcpxml_reference/locator). 22 | public struct Locator: FCPXMLElement { 23 | public let element: XMLElement 24 | 25 | public let elementType: ElementType = .locator 26 | 27 | public static let supportedElementTypes: Set = [.locator] 28 | 29 | public init() { 30 | element = XMLElement(name: elementType.rawValue) 31 | } 32 | 33 | public init?(element: XMLElement) { 34 | self.element = element 35 | guard _isElementTypeSupported(element: element) else { return nil } 36 | } 37 | } 38 | } 39 | 40 | // MARK: - Parameterized init 41 | 42 | extension FinalCutPro.FCPXML.Locator { 43 | public init( 44 | id: String, 45 | url: URL? = nil 46 | ) { 47 | self.init() 48 | 49 | self.id = id 50 | self.url = url 51 | } 52 | } 53 | 54 | // MARK: - Structure 55 | 56 | extension FinalCutPro.FCPXML.Locator { 57 | public enum Attributes: String { 58 | /// Required. 59 | /// Identifier. 60 | case id 61 | 62 | /// Required. 63 | /// Absolute URL or relative URL to library path. 64 | case url 65 | } 66 | } 67 | 68 | // MARK: - Attributes 69 | 70 | extension FinalCutPro.FCPXML.Locator { 71 | /// Required. 72 | /// Identifier. 73 | public var id: String { 74 | get { element.fcpID ?? "" } 75 | nonmutating set { element.fcpID = newValue } 76 | } 77 | 78 | /// Required. 79 | /// Absolute URL or relative URL to library path. 80 | public var url: URL? { 81 | get { element.getURL(forAttribute: Attributes.url.rawValue) } 82 | nonmutating set { element.set(url: newValue, forAttribute: Attributes.url.rawValue) } 83 | } 84 | } 85 | 86 | // MARK: - Children 87 | 88 | extension FinalCutPro.FCPXML.Locator: FCPXMLElementBookmarkChild { } 89 | 90 | // MARK: - Typing 91 | 92 | // Locator 93 | extension XMLElement { 94 | /// FCPXML: Returns the element wrapped in a ``FinalCutPro/FCPXML/Locator`` model object. 95 | /// Call this on a `locator` element only. 96 | public var fcpAsLocator: FinalCutPro.FCPXML.Locator? { 97 | .init(element: self) 98 | } 99 | } 100 | 101 | #endif 102 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Model/Resources/FCPXML ObjectTracker TrackingShape.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXML ObjectTracker TrackingShape.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | 11 | extension FinalCutPro.FCPXML.ObjectTracker { 12 | /// Tracking shape used by an object tracker resource. 13 | /// 14 | /// > Final Cut Pro FCPXML 1.11 Reference: 15 | /// > 16 | /// > Define a shape that the `object-tracker` uses to match the movement of an object. 17 | /// > 18 | /// > In Final Cut Pro, users can track the `shape-mask` of a video effect such as a blur, 19 | /// > highlight, or color effect to a moving object in a video clip. 20 | /// > 21 | /// > Use the `tracking-shape` element to define the shape that the `object-tracker` element 22 | /// > uses to match the movement of a moving object in a video clip. Each `object-tracker` 23 | /// > element consists of one or more tracking shapes. 24 | /// > 25 | /// > See [`tracking-shape`]( 26 | /// > https://developer.apple.com/documentation/professional_video_applications/fcpxml_reference/tracking-shape 27 | /// > ). 28 | public struct TrackingShape: FCPXMLElement { 29 | public let element: XMLElement 30 | 31 | public let elementType: FinalCutPro.FCPXML.ElementType = .trackingShape 32 | 33 | public static let supportedElementTypes: Set = [.trackingShape] 34 | 35 | public init() { 36 | element = XMLElement(name: elementType.rawValue) 37 | } 38 | 39 | public init?(element: XMLElement) { 40 | self.element = element 41 | guard _isElementTypeSupported(element: element) else { return nil } 42 | } 43 | } 44 | } 45 | 46 | // MARK: - Parameterized init 47 | 48 | extension FinalCutPro.FCPXML.ObjectTracker.TrackingShape { 49 | // TODO: add init after adding attribute properties 50 | } 51 | 52 | // MARK: - Structure 53 | 54 | extension FinalCutPro.FCPXML.ObjectTracker.TrackingShape { 55 | public enum Attributes: String { 56 | case id // required 57 | case name 58 | case offsetEnabled // 0 or 1, Default: 0 59 | case analysisMethod // enum case 60 | case dataLocator 61 | } 62 | 63 | } 64 | 65 | // MARK: - Attributes 66 | 67 | // TODO: Add attributes etc. 68 | 69 | // MARK: - Typing 70 | 71 | // ObjectTracker.TrackingShape 72 | extension XMLElement { 73 | /// FCPXML: Returns the element wrapped in a ``FinalCutPro/FCPXML/ObjectTracker/TrackingShape`` 74 | /// model object. 75 | /// Call this on a `tracking-shape` element only. 76 | public var fcpAsTrackingShape: FinalCutPro.FCPXML.ObjectTracker.TrackingShape? { 77 | .init(element: self) 78 | } 79 | } 80 | 81 | #endif 82 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Model/Resources/FCPXML ObjectTracker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXML ObjectTracker.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | import OTCore 11 | 12 | extension FinalCutPro.FCPXML { 13 | /// Object tracker shared resource. 14 | /// 15 | /// > Final Cut Pro FCPXML 1.11 Reference: 16 | /// > 17 | /// > Describe a tracked object such as a face or other moving object in a video clip. 18 | /// > 19 | /// > Users can track moving objects in video clips to match their movement to a clip, title, 20 | /// > logo, generator, or a still image, by using the object-tracker feature in Final Cut Pro. 21 | /// > They can also track the shape mask of a video effect, for example a blur, highlight, or 22 | /// > color effect, to a moving object in a video clip. 23 | /// > 24 | /// > See [`object-tracker`](https://developer.apple.com/documentation/professional_video_applications/fcpxml_reference/object-tracker). 25 | public struct ObjectTracker: FCPXMLElement { 26 | public let element: XMLElement 27 | 28 | public let elementType: ElementType = .objectTracker 29 | 30 | public static let supportedElementTypes: Set = [.objectTracker] 31 | 32 | public init() { 33 | element = XMLElement(name: elementType.rawValue) 34 | } 35 | 36 | public init?(element: XMLElement) { 37 | self.element = element 38 | guard _isElementTypeSupported(element: element) else { return nil } 39 | } 40 | } 41 | } 42 | 43 | // MARK: - Parameterized init 44 | 45 | extension FinalCutPro.FCPXML.ObjectTracker { 46 | public init( 47 | trackingShapes: [TrackingShape] 48 | ) { 49 | self.init() 50 | 51 | element._addChildren(trackingShapes) 52 | } 53 | } 54 | 55 | // MARK: - Structure 56 | 57 | extension FinalCutPro.FCPXML.ObjectTracker { 58 | // no Attributes 59 | 60 | // contains 1 or more `tracking-shape` 61 | } 62 | 63 | // MARK: - Children 64 | 65 | extension FinalCutPro.FCPXML.ObjectTracker { 66 | /// Returns child `tracking-shape` elements. 67 | public var trackingShapes: LazyFCPXMLChildrenSequence { 68 | element.children(whereFCPElement: .trackingShape) 69 | } 70 | } 71 | 72 | // MARK: - Typing 73 | 74 | // ObjectTracker 75 | extension XMLElement { 76 | /// FCPXML: Returns the element wrapped in a ``FinalCutPro/FCPXML/ObjectTracker`` model object. 77 | /// Call this on a `object-tracker` element only. 78 | public var fcpAsObjectTracker: FinalCutPro.FCPXML.ObjectTracker? { 79 | .init(element: self) 80 | } 81 | } 82 | 83 | #endif 84 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Model/Roles/Meta/FCPXML RoleType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXML RoleType.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | import TimecodeKit 11 | 12 | extension FinalCutPro.FCPXML { 13 | /// Role type/classification. 14 | public enum RoleType: String, Equatable, Hashable, CaseIterable, Sendable { 15 | /// Audio role. 16 | case audio 17 | 18 | /// Video role. 19 | case video 20 | 21 | /// Closed caption role. 22 | case caption 23 | } 24 | } 25 | 26 | extension Set { 27 | public static let allCases: Self = Set(Element.allCases) 28 | } 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Model/Roles/Meta/FCPXMLCollapsibleRole.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXMLRole.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | 11 | public protocol FCPXMLCollapsibleRole: FCPXMLRole { 12 | /// Returns the role with a collapsed sub-role, if the sub-role is derivative of the main role. 13 | /// Only applies to audio and video roles. 14 | /// Has no effect on closed caption roles since they do not contain sub-roles. 15 | /// 16 | /// The sub-role is considered collapsible if it is identical to the main role with a trailing 17 | /// dash and number. 18 | /// 19 | /// For example: 20 | /// 21 | /// - The raw role string `Role` has no sub-role, and is therefore already collapsed. 22 | /// - The raw role string `Role.Role-1` is considered collapsible and would return 23 | /// just the main `Role`, removing the sub-role. 24 | /// - The raw role string `Role.SubRole` is not considered collapsible since the sub-role is not 25 | /// derived from the main role. The main and sub-role would be returned unchanged. 26 | /// 27 | /// > Note: 28 | /// > 29 | /// > This is provided merely as a convenience for representing simplified role names to the 30 | /// > user. It does not play a direct role in encoding or decoding FCPXML. 31 | func collapsingSubRole() -> Self 32 | } 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/Occlusion/FCPXML ElementOcclusion.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXML ElementOcclusion.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2023 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | 11 | extension FinalCutPro.FCPXML { 12 | public enum ElementOcclusion: Equatable, Hashable, CaseIterable, Sendable { 13 | /// The element is not occluded at all by its parent. 14 | case notOccluded 15 | 16 | /// The element is partially occluded by its parent. 17 | case partiallyOccluded 18 | 19 | /// The element is fully occluded by its parent. 20 | case fullyOccluded 21 | } 22 | } 23 | 24 | extension Set { 25 | public static let allCases: Self = Set(Element.allCases) 26 | } 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/XML/FCPXML Root Parsing.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXML Root Parsing.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | import OTCore 11 | import TimecodeKit 12 | 13 | extension XMLElement { 14 | /// FCPXML: Returns the root-level `fcpxml` element. 15 | /// This may be called on any element within a FCPXML. 16 | public var fcpRoot: XMLElement? { 17 | rootDocument? 18 | .rootElement() // `fcpxml` element 19 | } 20 | } 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FCPXML/XML/XMLParsableAttributesKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XMLParsableAttributesKey.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import Foundation 10 | 11 | // not used after refactoring 12 | 13 | // protocol XMLParsableAttributesKey: Hashable, RawRepresentable, CaseIterable 14 | // where RawValue == String { } 15 | 16 | // extension XMLElement { 17 | // /// Utility: 18 | // /// Parse an XML element's attributes and return a key-value dictionary, 19 | // /// parsing only the keys contained in the `key` type passed. 20 | // /// Any missing keys will simply be omitted from the returned dictionary. 21 | // func parseRawAttributeValues( 22 | // key: K.Type 23 | // ) -> [K: String] where K: XMLParsableAttributesKey { 24 | // K.allCases.reduce(into: [:]) { dict, key in 25 | // dict[key] = stringValue(forAttributeNamed: key.rawValue) 26 | // } 27 | // } 28 | 29 | // /// Utility: 30 | // /// Returns attribute string value for the given attribute key name. 31 | // subscript(_ key: some XMLParsableAttributesKey) -> String? { 32 | // self.attributeStringValue(forName: key.rawValue) 33 | // } 34 | // } 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/FinalCutPro/FinalCutPro.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FinalCutPro.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | import Foundation 8 | import TimecodeKit 9 | 10 | /// Collection of methods and structures related to Final Cut Pro. 11 | /// Do not instance; use methods within directly. 12 | public enum FinalCutPro { 13 | /// `Timecode` setting for `.subFramesBase`. 14 | /// Final Cut Pro uses 80 subframes per frame. 15 | public static let timecodeSubFramesBase: Timecode.SubFramesBase = .max80SubFrames 16 | 17 | /// `Timecode` setting for `.upperLimit`. 18 | /// Final Cut Pro is confined to a 24-hour SMPTE timecode clock. 19 | public static let timecodeUpperLimit: Timecode.UpperLimit = .max24Hours 20 | 21 | /// `Timecode` setting for `.stringFormat`. 22 | public static let timecodeStringFormat: Timecode.StringFormat = [] 23 | } 24 | 25 | extension FinalCutPro { 26 | /// `Timecode` template. 27 | public static func formTimecode( 28 | at rate: TimecodeFrameRate 29 | ) -> Timecode { 30 | Timecode( 31 | .zero, 32 | at: rate, 33 | base: timecodeSubFramesBase, 34 | limit: timecodeUpperLimit 35 | ) 36 | } 37 | 38 | /// `Timecode` template. 39 | public static func formTimecode( 40 | rational: Fraction, 41 | at rate: TimecodeFrameRate 42 | ) throws -> Timecode { 43 | try Timecode( 44 | .rational(rational), 45 | at: rate, 46 | base: timecodeSubFramesBase, 47 | limit: timecodeUpperLimit 48 | ) 49 | } 50 | 51 | /// `Timecode` template. 52 | public static func formTimecode( 53 | realTime seconds: TimeInterval, 54 | at rate: TimecodeFrameRate 55 | ) throws -> Timecode { 56 | try Timecode( 57 | .realTime(seconds: seconds), 58 | at: rate, 59 | base: timecodeSubFramesBase, 60 | limit: timecodeUpperLimit 61 | ) 62 | } 63 | 64 | /// `TimecodeInterval` template. 65 | public static func formTimecodeInterval( 66 | at rate: TimecodeFrameRate 67 | ) -> TimecodeInterval { 68 | let tc = formTimecode(at: rate) 69 | return TimecodeInterval(tc) 70 | } 71 | 72 | /// `TimecodeInterval` template. 73 | public static func formTimecodeInterval( 74 | realTime: TimeInterval, 75 | at rate: TimecodeFrameRate 76 | ) throws -> TimecodeInterval { 77 | 78 | try TimecodeInterval( 79 | realTime: realTime, 80 | at: rate, 81 | base: timecodeSubFramesBase, 82 | limit: timecodeUpperLimit 83 | ) 84 | } 85 | 86 | /// `TimecodeInterval` template. 87 | public static func formTimecodeInterval( 88 | rational: Fraction, 89 | at rate: TimecodeFrameRate 90 | ) throws -> TimecodeInterval { 91 | try TimecodeInterval( 92 | rational, 93 | at: rate, 94 | base: timecodeSubFramesBase, 95 | limit: timecodeUpperLimit 96 | ) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/MIDIFile/Errors/MIDIFile BuildError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MIDIFile BuildError.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | import Foundation 8 | import MIDIKitSMF 9 | 10 | extension MIDIFile { 11 | /// Cubase track archive XML parsing error. 12 | public enum BuildError: Error { 13 | case general(String) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/ProTools/ProTools.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProTools.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | import Foundation 8 | import TimecodeKit 9 | 10 | /// Collection of methods and structures related to Pro Tools. 11 | public enum ProTools { 12 | /// `Timecode` setting for `.subFramesBase`. 13 | /// Pro Tools uses 100 subframes per frame. 14 | public static let timecodeSubFramesBase: Timecode.SubFramesBase = .max100SubFrames 15 | 16 | /// `Timecode` setting for `.upperLimit`. 17 | /// Pro Tools uses a 24-hour SMPTE timecode clock. 18 | public static let timecodeUpperLimit: Timecode.UpperLimit = .max24Hours 19 | 20 | /// `Timecode` setting for `.stringFormat`. 21 | public static let timecodeStringFormat: Timecode.StringFormat = [] 22 | } 23 | 24 | extension ProTools { 25 | /// `Timecode` struct template. 26 | public static func formTimecode( 27 | _ exactly: Timecode.Components, 28 | at rate: TimecodeFrameRate 29 | ) throws -> Timecode { 30 | try Timecode( 31 | .components(exactly), 32 | at: rate, 33 | base: timecodeSubFramesBase, 34 | limit: timecodeUpperLimit 35 | ) 36 | } 37 | 38 | /// `Timecode` struct template. 39 | public static func formTimecode( 40 | _ exactly: String, 41 | at rate: TimecodeFrameRate 42 | ) throws -> Timecode { 43 | try Timecode( 44 | .string(exactly), 45 | at: rate, 46 | base: timecodeSubFramesBase, 47 | limit: timecodeUpperLimit 48 | ) 49 | } 50 | 51 | /// `Timecode` struct template. 52 | public static func formTimecode( 53 | realTimeValue: TimeInterval, 54 | at rate: TimecodeFrameRate 55 | ) throws -> Timecode { 56 | try Timecode( 57 | .realTime(seconds: realTimeValue), 58 | at: rate, 59 | base: timecodeSubFramesBase, 60 | limit: timecodeUpperLimit 61 | ) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/ProTools/SessionInfo/Errors/SessionInfo ParseError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SessionInfo ParseError.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | import Foundation 8 | 9 | extension ProTools.SessionInfo { 10 | /// Pro Tools session info text file parsing error. 11 | public enum ParseError: Error { 12 | case general(String) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/ProTools/SessionInfo/Messages/SessionInfo ParseMessage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SessionInfo ParseMessage.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | import Foundation 8 | 9 | extension ProTools.SessionInfo { 10 | public enum ParseMessage { 11 | /// Info message. 12 | /// Can be disregarded and only useful for debugging. 13 | case info(String) 14 | 15 | /// Error message. 16 | /// Something was malformed or data format was not expected. 17 | case error(String) 18 | } 19 | } 20 | 21 | // MARK: - Extensions 22 | 23 | extension Collection where Element == ProTools.SessionInfo.ParseMessage { 24 | /// Returns all `.info` cases as enum-unwrapped Strings. 25 | public var infos: [String] { 26 | reduce(into: [String]()) { 27 | switch $1 { 28 | case let .info(message): 29 | $0.append(message) 30 | default: 31 | break 32 | } 33 | } 34 | } 35 | 36 | /// Returns all `.error` cases as enum-unwrapped Strings. 37 | public var errors: [String] { 38 | reduce(into: [String]()) { 39 | switch $1 { 40 | case let .error(message): 41 | $0.append(message) 42 | default: 43 | break 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/ProTools/SessionInfo/SessionInfo Clip.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SessionInfo Clip.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | import Foundation 8 | 9 | extension ProTools.SessionInfo { 10 | /// Represents a clip used in the session. 11 | public struct Clip: Equatable, Hashable { 12 | public internal(set) var name: String = "" 13 | public internal(set) var sourceFile: String = "" 14 | public internal(set) var channel: String? 15 | 16 | /// Flag determining if clip was online (true) or offline (false) 17 | public internal(set) var online: Bool = true 18 | } 19 | } 20 | 21 | extension ProTools.SessionInfo.Clip: Sendable { } 22 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/ProTools/SessionInfo/SessionInfo File.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SessionInfo File.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | import Foundation 8 | 9 | extension ProTools.SessionInfo { 10 | /// Represents a file used in the session 11 | public struct File: Equatable, Hashable { 12 | public internal(set) var filename: String = "" 13 | public internal(set) var path: String = "" 14 | 15 | /// Flag determining if file was online (true) or offline (false) 16 | public internal(set) var online: Bool = true 17 | } 18 | } 19 | 20 | extension ProTools.SessionInfo.File: Sendable { } 21 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/ProTools/SessionInfo/SessionInfo Main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SessionInfo Main.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | import Foundation 8 | import TimecodeKit 9 | 10 | extension ProTools.SessionInfo { 11 | /// Contains the global session meta data 12 | /// (from the Session Info Text file header) 13 | public struct Main: Equatable, Hashable { 14 | public internal(set) var name: String? 15 | 16 | public internal(set) var sampleRate: Double? 17 | public internal(set) var bitDepth: String? 18 | 19 | public internal(set) var startTimecode: Timecode? 20 | public internal(set) var frameRate: TimecodeFrameRate? 21 | 22 | public internal(set) var audioTrackCount: Int? 23 | public internal(set) var audioClipCount: Int? 24 | public internal(set) var audioFileCount: Int? 25 | } 26 | } 27 | 28 | extension ProTools.SessionInfo.Main: Sendable { } 29 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/ProTools/SessionInfo/SessionInfo OrphanData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SessionInfo OrphanData.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | import Foundation 8 | 9 | extension ProTools.SessionInfo { 10 | public struct OrphanData: Equatable, Hashable { 11 | public internal(set) var heading: String 12 | public internal(set) var content: [String] 13 | } 14 | } 15 | 16 | extension ProTools.SessionInfo.OrphanData: Sendable { } 17 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/ProTools/SessionInfo/SessionInfo Plugin.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SessionInfo Plugin.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | import Foundation 8 | 9 | extension ProTools.SessionInfo { 10 | /// Represents a plug-in used in the session 11 | public struct Plugin: Equatable, Hashable { 12 | public internal(set) var manufacturer: String = "" 13 | public internal(set) var name: String = "" 14 | public internal(set) var version: String = "" 15 | public internal(set) var format: String = "" 16 | public internal(set) var stems: String = "" 17 | public internal(set) var numberOfInstances: String = "" 18 | } 19 | } 20 | 21 | extension ProTools.SessionInfo.Plugin: Sendable { } 22 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/ProTools/SessionInfo/SessionInfo Track.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SessionInfo Track.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | import Foundation 8 | import TimecodeKit 9 | 10 | extension ProTools.SessionInfo { 11 | /// Represents a track and its contents. 12 | public struct Track: Equatable, Hashable { 13 | public internal(set) var name: String = "" 14 | public internal(set) var comments: String = "" 15 | public internal(set) var userDelay: Int = 0 16 | public internal(set) var state: Set = [] 17 | public internal(set) var plugins: [String] = [] 18 | public internal(set) var clips: [Clip] = [] 19 | } 20 | } 21 | 22 | extension ProTools.SessionInfo.Track: Sendable { } 23 | 24 | // MARK: - State 25 | 26 | extension ProTools.SessionInfo.Track { 27 | /// A track's state. 28 | public enum State: String { 29 | case inactive = "Inactive" 30 | case hidden = "Hidden" 31 | case muted = "Muted" 32 | case solo = "Solo" 33 | case soloSafe = "SoloSafe" 34 | } 35 | } 36 | 37 | extension ProTools.SessionInfo.Track.State: Sendable { } 38 | 39 | // MARK: - Clip 40 | 41 | extension ProTools.SessionInfo.Track { 42 | /// Represents a clip contained on a track. 43 | public struct Clip: Equatable, Hashable { 44 | public internal(set) var channel: Int = 0 45 | public internal(set) var event: Int = 0 46 | public internal(set) var name: String = "" 47 | public internal(set) var startTime: ProTools.SessionInfo.TimeValue? 48 | public internal(set) var endTime: ProTools.SessionInfo.TimeValue? 49 | public internal(set) var duration: ProTools.SessionInfo.TimeValue? 50 | public internal(set) var state: State = .unmuted 51 | } 52 | } 53 | 54 | extension ProTools.SessionInfo.Track.Clip: Sendable { } 55 | 56 | // MARK: - Clip State 57 | 58 | extension ProTools.SessionInfo.Track.Clip { 59 | /// A clip's state (such as 'Muted', 'Unmuted') 60 | public enum State: String { 61 | // TODO: there may be more states possible than this -- need to test 62 | case muted = "Muted" 63 | case unmuted = "Unmuted" 64 | } 65 | } 66 | 67 | extension ProTools.SessionInfo.Track.Clip.State: Sendable { } 68 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/ProTools/SessionInfo/SessionInfo Versions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SessionInfo Versions.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2023 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | import Foundation 8 | 9 | extension ProTools.SessionInfo { 10 | public enum MarkersListingVersion: Equatable, Hashable { 11 | /// Pro Tools versions prior to 2023.12. 12 | case legacy 13 | 14 | /// Pro Tools 2023.12 and up. 15 | case pt2023_12 16 | } 17 | } 18 | 19 | extension ProTools.SessionInfo.MarkersListingVersion: Sendable { } 20 | 21 | extension ProTools.SessionInfo.MarkersListingVersion { 22 | public var columnCount: Int { 23 | switch self { 24 | case .legacy: return 6 25 | case .pt2023_12: return 8 26 | } 27 | } 28 | 29 | public var commentColumnIndex: Int { 30 | switch self { 31 | case .legacy: return 5 32 | case .pt2023_12: return 7 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/ProTools/SessionInfo/SessionInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SessionInfo.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | import Foundation 8 | import TimecodeKit 9 | 10 | // MARK: - ProTools.SessionInfo 11 | 12 | extension ProTools { 13 | /// Contains parsed data after reading a Pro Tools Session Info text file. 14 | public struct SessionInfo: Equatable, Hashable { 15 | /// Meta data contained in the main header of the data file. 16 | public internal(set) var main = Main() 17 | 18 | /// Files listing (online). 19 | public internal(set) var onlineFiles: [File]? 20 | 21 | /// Files listing (offline). 22 | public internal(set) var offlineFiles: [File]? 23 | 24 | /// Clips listing (online). 25 | public internal(set) var onlineClips: [Clip]? 26 | 27 | /// Clips listing (offline). 28 | public internal(set) var offlineClips: [Clip]? 29 | 30 | /// Plugin listing. 31 | public internal(set) var plugins: [Plugin]? 32 | 33 | /// Tracks listing. 34 | public internal(set) var tracks: [Track]? 35 | 36 | /// Markers listing. 37 | public internal(set) var markers: [Marker]? 38 | 39 | /// Holds any extraneous sections or data that was not recognized while parsing the file. 40 | public internal(set) var orphanData: [OrphanData]? 41 | } 42 | } 43 | 44 | extension ProTools.SessionInfo: Sendable { } 45 | 46 | // MARK: - Constants 47 | 48 | extension ProTools.SessionInfo { 49 | /// Array of file types for use with NSOpenPanel / NSSavePanel 50 | public static let fileTypes = ["public.txt", "txt"] 51 | } 52 | -------------------------------------------------------------------------------- /Sources/DAWFileKit/Utilities/Utilities.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Utilities.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | import Foundation 8 | 9 | @propertyWrapper 10 | public struct EquatableExempt: Equatable { 11 | public var wrappedValue: Value 12 | 13 | public static func == (lhs: EquatableExempt, 14 | rhs: EquatableExempt) -> Bool { 15 | true 16 | } 17 | 18 | public init(wrappedValue: Value) { 19 | self.wrappedValue = wrappedValue 20 | } 21 | } 22 | 23 | @propertyWrapper 24 | public struct EquatableAndHashableExempt: Equatable, Hashable { 25 | public var wrappedValue: Value 26 | 27 | public static func == (lhs: EquatableAndHashableExempt, 28 | rhs: EquatableAndHashableExempt) -> Bool { 29 | true 30 | } 31 | 32 | public func hash(into hasher: inout Hasher) { } 33 | 34 | public init(wrappedValue: Value) { 35 | self.wrappedValue = wrappedValue 36 | } 37 | } 38 | 39 | extension Sequence { 40 | /// Wraps the sequence in a `AnySequence` instance. 41 | var asAnySequence: AnySequence { 42 | AnySequence(self) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/Cubase/Cubase TrackArchive Helper Tests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Cubase TrackArchive Helper Tests.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import XCTest 10 | @testable import DAWFileKit 11 | import OTCore 12 | import TimecodeKit 13 | 14 | class Cubase_Helper_Tests: XCTestCase { 15 | override func setUp() { } 16 | override func tearDown() { } 17 | 18 | func testCollection_XMLElement_FilterAttribute() throws { 19 | // prep 20 | 21 | let nodes = [ 22 | try XMLElement(xmlString: ""), 23 | try XMLElement(xmlString: ""), 24 | try XMLElement(xmlString: ""), 25 | try XMLElement(xmlString: "") 26 | ] 27 | 28 | // test 29 | 30 | let filteredA = nodes.filter(whereNameAttributeValue: "name2").zeroIndexed 31 | XCTAssertEqual(filteredA[0], nodes[1]) 32 | 33 | let filteredB = nodes.filter(whereClassAttributeValue: "classA").zeroIndexed 34 | XCTAssertEqual(filteredB[0], nodes[0]) 35 | XCTAssertEqual(filteredB[1], nodes[1]) 36 | } 37 | 38 | func testCollection_XMLElement_FirstAttribute() throws { 39 | // prep 40 | 41 | let nodes = [ 42 | try XMLElement(xmlString: ""), 43 | try XMLElement(xmlString: ""), 44 | try XMLElement(xmlString: ""), 45 | try XMLElement(xmlString: "") 46 | ] 47 | 48 | // test 49 | 50 | let firstA = nodes.first(whereNameAttributeValue: "name2") 51 | XCTAssertEqual(firstA, nodes[1]) 52 | 53 | let firstB = nodes.first(whereClassAttributeValue: "classA") 54 | XCTAssertEqual(firstB, nodes[0]) 55 | } 56 | } 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/DAWFileKitTests Constants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DAWFileKitTests Constants.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | import XCTest 8 | 9 | // MARK: - Constants 10 | 11 | enum ResourcesFolder: String { 12 | case cubaseTrackArchiveXML = "Cubase TrackArchive XML Exports" 13 | case ptSessionTextExports = "PT Session Text Exports" 14 | case fcpxmlExports = "FCPXML Exports" 15 | } 16 | 17 | // MARK: - Utilities 18 | 19 | func loadFileContents( 20 | forResource: String, 21 | withExtension: String, 22 | subFolder: ResourcesFolder? 23 | ) -> Data? { 24 | guard let url = Bundle.module.url( 25 | forResource: forResource, 26 | withExtension: withExtension, 27 | subdirectory: subFolder?.rawValue 28 | ) 29 | else { 30 | XCTFail("Could not form URL, possibly could not find file.") 31 | return nil 32 | } 33 | 34 | guard let data = try? Data(contentsOf: url) 35 | else { XCTFail("Could not read file at URL \(url.absoluteString.quoted)."); return nil } 36 | 37 | return data 38 | } 39 | -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/FinalCutPro/FCPXMLTestCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FCPXMLTestCase.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import XCTest 10 | @testable import DAWFileKit 11 | import OTCore 12 | import TimecodeKit 13 | 14 | protocol FCPXMLUtilities { } 15 | 16 | extension FCPXMLUtilities { 17 | static func tc(_ timecodeString: String, _ fr: TimecodeFrameRate) -> Timecode { 18 | try! Timecode(.string(timecodeString), at: fr, base: .max80SubFrames) 19 | } 20 | 21 | static func tc(frames: Int, _ fr: TimecodeFrameRate) -> Timecode { 22 | try! Timecode(.frames(frames), at: fr, base: .max80SubFrames) 23 | } 24 | 25 | static func tc(_ rational: Fraction, _ fr: TimecodeFrameRate) -> Timecode { 26 | try! Timecode(.rational(rational), at: fr, base: .max80SubFrames) 27 | } 28 | 29 | static func tc(seconds: TimeInterval, _ fr: TimecodeFrameRate) -> Timecode { 30 | try! Timecode(.realTime(seconds: seconds), at: fr, base: .max80SubFrames) 31 | } 32 | 33 | static func tcInterval(frames: Int, _ fr: TimecodeFrameRate) -> TimecodeInterval { 34 | if frames < 0 { 35 | return .negative(tc(frames: abs(frames), fr)) 36 | } else { 37 | return .positive(tc(frames: frames, fr)) 38 | } 39 | } 40 | 41 | static func fraction(frames: Int, _ fr: TimecodeFrameRate) -> Fraction { 42 | tcInterval(frames: frames, fr).rationalValue 43 | } 44 | 45 | static func debugString(for em: FinalCutPro.FCPXML.ExtractedMarker) -> String { 46 | let absTC: String 47 | if let absoluteStart = em.timecode() { 48 | absTC = absoluteStart.stringValue(format: [.showSubFrames]) + " @ " + absoluteStart.frameRate.stringValue 49 | } else { 50 | absTC = "??:??:??:??.?? @ ??" 51 | } 52 | 53 | let name = em.name.quoted 54 | let note = em.note != nil ? " note:\(em.note!.quoted)" : "" 55 | let durTC = em.duration()?.stringValue(format: [.showSubFrames]) ?? "?" 56 | 57 | let parentName = em.value(forContext: .parentName)?.quoted ?? "<>" 58 | return "\(absTC): \(name)\(note) dur:\(durTC) parent:\(parentName)" 59 | } 60 | 61 | static func debugString(for extractedMarkers: some Collection) -> String { 62 | extractedMarkers.map { debugString(for: $0) }.joined(separator: "\n") 63 | } 64 | } 65 | 66 | class FCPXMLTestCase: XCTestCase, FCPXMLUtilities { } 67 | 68 | #endif 69 | -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/FinalCutPro/File Tests/FinalCutPro FCPXML AudioOnly.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FinalCutPro FCPXML AudioOnly.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import XCTest 10 | @testable import DAWFileKit 11 | import OTCore 12 | import TimecodeKit 13 | 14 | final class FinalCutPro_FCPXML_AudioOnly: FCPXMLTestCase { 15 | override func setUp() { } 16 | override func tearDown() { } 17 | 18 | // MARK: - Test Data 19 | 20 | var fileContents: Data { get throws { 21 | try XCTUnwrap(loadFileContents( 22 | forResource: "AudioOnly", 23 | withExtension: "fcpxml", 24 | subFolder: .fcpxmlExports 25 | )) 26 | } } 27 | 28 | /// Project @ 24fps. 29 | let projectFrameRate: TimecodeFrameRate = .fps24 30 | 31 | func testParse() throws { 32 | // load 33 | let rawData = try fileContents 34 | let fcpxml = try FinalCutPro.FCPXML(fileContent: rawData) 35 | 36 | // version 37 | XCTAssertEqual(fcpxml.version, .ver1_11) 38 | 39 | // skip testing file contents 40 | } 41 | 42 | func testExtractMarkers() async throws { 43 | // load file 44 | let rawData = try fileContents 45 | 46 | // load 47 | let fcpxml = try FinalCutPro.FCPXML(fileContent: rawData) 48 | 49 | // project 50 | let project = try XCTUnwrap(fcpxml.allProjects().first) 51 | 52 | let extractedMarkers = await project 53 | .extract(preset: .markers, scope: .mainTimeline) 54 | .sortedByAbsoluteStartTimecode() 55 | // .zeroIndexed // not necessary after sorting - sort returns new array 56 | 57 | let markers = extractedMarkers 58 | 59 | let expectedMarkerCount = 10 60 | XCTAssertEqual(markers.count, expectedMarkerCount) 61 | 62 | // print("Markers sorted by absolute timecode:") 63 | // print(Self.debugString(for: markers)) 64 | } 65 | 66 | func testExtractRoles() async throws { 67 | // load file 68 | let rawData = try fileContents 69 | 70 | // load 71 | let fcpxml = try FinalCutPro.FCPXML(fileContent: rawData) 72 | 73 | // project 74 | let project = try XCTUnwrap(fcpxml.allProjects().first) 75 | 76 | let roles = await project.extract( 77 | preset: .roles(roleTypes: .allCases), 78 | scope: .deep(auditions: .active, mcClipAngles: .active) 79 | ) 80 | 81 | dump(roles) 82 | 83 | XCTAssertEqual(roles.count, 2) 84 | XCTAssertTrue(roles.contains(.audio(raw: "music.music-1")!)) 85 | XCTAssertTrue(roles.contains(.audio(raw: "effects")!)) 86 | } 87 | } 88 | 89 | #endif 90 | -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/FinalCutPro/File Tests/FinalCutPro FCPXML Keywords.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FinalCutPro FCPXML Keywords.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import XCTest 10 | @testable import DAWFileKit 11 | import OTCore 12 | import TimecodeKit 13 | 14 | final class FinalCutPro_FCPXML_Keywords: FCPXMLTestCase { 15 | override func setUp() { } 16 | override func tearDown() { } 17 | 18 | // MARK: - Test Data 19 | 20 | var fileContents: Data { get throws { 21 | try XCTUnwrap(loadFileContents( 22 | forResource: "Keywords", 23 | withExtension: "fcpxml", 24 | subFolder: .fcpxmlExports 25 | )) 26 | } } 27 | 28 | // MARK: - Tests 29 | 30 | func testParse() throws { 31 | // load file 32 | let rawData = try fileContents 33 | 34 | // parse file 35 | let fcpxml = try FinalCutPro.FCPXML(fileContent: rawData) 36 | 37 | // version 38 | XCTAssertEqual(fcpxml.version, .ver1_11) 39 | } 40 | 41 | /// Test keywords that apply to each marker. 42 | func testExtractMarkers() async throws { 43 | // load file 44 | let rawData = try fileContents 45 | 46 | // load 47 | let fcpxml = try FinalCutPro.FCPXML(fileContent: rawData) 48 | 49 | // project 50 | let project = try XCTUnwrap(fcpxml.allProjects().first) 51 | 52 | let extractedMarkers = await project 53 | .extract(preset: .markers, scope: .mainTimeline) 54 | .sortedByAbsoluteStartTimecode() 55 | // .zeroIndexed // not necessary after sorting - sort returns new array 56 | 57 | let markers = extractedMarkers 58 | 59 | let expectedMarkerCount = 6 60 | XCTAssertEqual(markers.count, expectedMarkerCount) 61 | 62 | print("Markers sorted by absolute timecode:") 63 | print(Self.debugString(for: markers)) 64 | 65 | // markers 66 | 67 | let marker1 = try XCTUnwrap(markers[safe: 0]) 68 | let marker2 = try XCTUnwrap(markers[safe: 1]) 69 | let marker3 = try XCTUnwrap(markers[safe: 2]) 70 | let marker4 = try XCTUnwrap(markers[safe: 3]) 71 | let marker5 = try XCTUnwrap(markers[safe: 4]) 72 | let marker6 = try XCTUnwrap(markers[safe: 5]) 73 | 74 | // Check keywords while constraining to keyword ranges 75 | XCTAssertEqual(marker1.keywords(constrainToKeywordRanges: true), ["flower", "nature"]) 76 | XCTAssertEqual(marker2.keywords(constrainToKeywordRanges: true), ["birds"]) 77 | XCTAssertEqual(marker3.keywords(constrainToKeywordRanges: true), ["flower", "nature"]) 78 | XCTAssertEqual(marker4.keywords(constrainToKeywordRanges: true), ["lava", "nature"]) 79 | XCTAssertEqual(marker5.keywords(constrainToKeywordRanges: true), ["penguin"]) 80 | XCTAssertEqual(marker6.keywords(constrainToKeywordRanges: true), ["noStartOrDuration", "penguin"]) 81 | } 82 | } 83 | 84 | #endif 85 | -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/FinalCutPro/File Tests/FinalCutPro FCPXML RolesList.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FinalCutPro FCPXML RolesList.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import XCTest 10 | @testable import DAWFileKit 11 | import OTCore 12 | import TimecodeKit 13 | 14 | final class FinalCutPro_FCPXML_RolesList: FCPXMLTestCase { 15 | override func setUp() { } 16 | override func tearDown() { } 17 | 18 | // MARK: - Test Data 19 | 20 | var fileContents: Data { get throws { 21 | try XCTUnwrap(loadFileContents( 22 | forResource: "RolesList", 23 | withExtension: "fcpxml", 24 | subFolder: .fcpxmlExports 25 | )) 26 | } } 27 | 28 | /// Project @ 24fps. 29 | let projectFrameRate: TimecodeFrameRate = .fps24 30 | 31 | func testParse() throws { 32 | // load 33 | let rawData = try fileContents 34 | let fcpxml = try FinalCutPro.FCPXML(fileContent: rawData) 35 | 36 | // version 37 | XCTAssertEqual(fcpxml.version, .ver1_11) 38 | 39 | // skip testing file contents, we only care about roles extraction 40 | } 41 | 42 | func testExtractRoles() async throws { 43 | // load file 44 | let rawData = try fileContents 45 | 46 | // load 47 | let fcpxml = try FinalCutPro.FCPXML(fileContent: rawData) 48 | 49 | // project 50 | let project = try XCTUnwrap(fcpxml.allProjects().first) 51 | 52 | let roles = await project.extract( 53 | preset: .roles(roleTypes: .allCases), 54 | scope: .deep(auditions: .active, mcClipAngles: .active) 55 | ) 56 | 57 | // dump(roles) 58 | 59 | XCTAssertEqual(roles.count, 4) 60 | XCTAssertTrue(roles.contains(.video(raw: "Video")!)) 61 | XCTAssertTrue(roles.contains(.video(raw: "FIXING.FIXING-1")!)) 62 | XCTAssertTrue(roles.contains(.video(raw: "TO-DO.TO-DO-1")!)) 63 | XCTAssertTrue(roles.contains(.video(raw: "VFX.VFX-1")!)) 64 | } 65 | } 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/FinalCutPro/File Tests/FinalCutPro FCPXML StandaloneAssetClip.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FinalCutPro FCPXML StandaloneAssetClip.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import XCTest 10 | @testable import DAWFileKit 11 | import OTCore 12 | import TimecodeKit 13 | 14 | final class FinalCutPro_FCPXML_StandaloneAssetClip: FCPXMLTestCase { 15 | override func setUp() { } 16 | override func tearDown() { } 17 | 18 | // MARK: - Test Data 19 | 20 | var fileContents: Data { get throws { 21 | try XCTUnwrap(loadFileContents( 22 | forResource: "StandaloneAssetClip", 23 | withExtension: "fcpxml", 24 | subFolder: .fcpxmlExports 25 | )) 26 | } } 27 | 28 | // MARK: - Tests 29 | 30 | func testParse() throws { 31 | // load file 32 | let rawData = try fileContents 33 | 34 | // parse file 35 | let fcpxml = try FinalCutPro.FCPXML(fileContent: rawData) 36 | 37 | // version 38 | XCTAssertEqual(fcpxml.version, .ver1_11) 39 | } 40 | 41 | /// Test that FCPXML that doesn't contain a project is still able to extract standalone clips. 42 | func testExtract() async throws { 43 | // load file 44 | let rawData = try fileContents 45 | 46 | // load 47 | let fcpxml = try FinalCutPro.FCPXML(fileContent: rawData) 48 | 49 | // timelines 50 | let timelines = fcpxml.allTimelines() 51 | XCTAssertEqual(timelines.count, 1) 52 | 53 | let anyTimeline = try XCTUnwrap(timelines.first) 54 | 55 | // AnyTimeline 56 | 57 | let timelineStartTC = try XCTUnwrap(anyTimeline.timelineStartAsTimecode()) 58 | XCTAssertEqual(timelineStartTC.components, .init(h: 00, m: 59, s: 50, f: 00)) 59 | XCTAssertEqual(timelineStartTC.frameRate, .fps29_97) 60 | let timelineDurTC = try XCTUnwrap(anyTimeline.timelineDurationAsTimecode()) 61 | XCTAssertEqual(timelineDurTC.components, .init(h: 00, m: 00, s: 10, f: 00)) 62 | XCTAssertEqual(timelineDurTC.frameRate, .fps29_97) 63 | 64 | // unwrap AssetClip 65 | 66 | guard case .assetClip(let assetClip) = anyTimeline else { XCTFail() ; return } 67 | 68 | // FCPXMLElementMetaTimeline 69 | let assetClipStartTC = try XCTUnwrap(anyTimeline.timelineStartAsTimecode()) 70 | XCTAssertEqual(assetClipStartTC.components, .init(h: 00, m: 59, s: 50, f: 00)) 71 | XCTAssertEqual(assetClipStartTC.frameRate, .fps29_97) 72 | let assetClipDurTC = try XCTUnwrap(anyTimeline.timelineDurationAsTimecode()) 73 | XCTAssertEqual(assetClipDurTC.components, .init(h: 00, m: 00, s: 10, f: 00)) 74 | XCTAssertEqual(assetClipDurTC.frameRate, .fps29_97) 75 | 76 | // local XML attributes 77 | let clipStartTC = try XCTUnwrap(assetClip.startAsTimecode()) 78 | XCTAssertEqual(clipStartTC.components, .init(h: 00, m: 59, s: 50, f: 00)) 79 | XCTAssertEqual(clipStartTC.frameRate, .fps29_97) 80 | let clipDurTC = try XCTUnwrap(assetClip.durationAsTimecode()) 81 | XCTAssertEqual(clipDurTC.components, .init(h: 00, m: 00, s: 10, f: 00)) 82 | XCTAssertEqual(clipDurTC.frameRate, .fps29_97) 83 | } 84 | } 85 | 86 | #endif 87 | -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/FinalCutPro/File Tests/FinalCutPro FCPXML StandaloneLibraryEventClip.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FinalCutPro FCPXML StandaloneLibraryEventClip.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import XCTest 10 | @testable import DAWFileKit 11 | import OTCore 12 | import TimecodeKit 13 | 14 | final class FinalCutPro_FCPXML_StandaloneLibraryEventClip: FCPXMLTestCase { 15 | override func setUp() { } 16 | override func tearDown() { } 17 | 18 | // MARK: - Test Data 19 | 20 | var fileContents: Data { get throws { 21 | try XCTUnwrap(loadFileContents( 22 | forResource: "StandaloneLibraryEventClip", 23 | withExtension: "fcpxml", 24 | subFolder: .fcpxmlExports 25 | )) 26 | } } 27 | 28 | // MARK: - Tests 29 | 30 | func testParse() throws { 31 | // load file 32 | let rawData = try fileContents 33 | 34 | // parse file 35 | let fcpxml = try FinalCutPro.FCPXML(fileContent: rawData) 36 | 37 | // version 38 | XCTAssertEqual(fcpxml.version, .ver1_11) 39 | } 40 | 41 | /// Test that FCPXML that doesn't contain a project is still able to extract standalone clips. 42 | func testExtract() async throws { 43 | // load file 44 | let rawData = try fileContents 45 | 46 | // load 47 | let fcpxml = try FinalCutPro.FCPXML(fileContent: rawData) 48 | 49 | // timelines 50 | let timelines = fcpxml.allTimelines() 51 | XCTAssertEqual(timelines.count, 1) 52 | 53 | let anyTimeline = try XCTUnwrap(timelines.first) 54 | 55 | // AnyTimeline 56 | 57 | let timelineStartTC = try XCTUnwrap(anyTimeline.timelineStartAsTimecode()) 58 | XCTAssertEqual(timelineStartTC.components, .init(h: 00, m: 00, s: 00, f: 00)) 59 | XCTAssertEqual(timelineStartTC.frameRate, .fps25) 60 | let timelineDurTC = try XCTUnwrap(anyTimeline.timelineDurationAsTimecode()) 61 | XCTAssertEqual(timelineDurTC.components, .init(h: 00, m: 01, s: 31, f: 12)) 62 | XCTAssertEqual(timelineDurTC.frameRate, .fps25) 63 | 64 | // unwrap RefClip 65 | 66 | guard case .refClip(let refClip) = anyTimeline else { XCTFail() ; return } 67 | 68 | // FCPXMLElementMetaTimeline 69 | let refClipStartTC = try XCTUnwrap(refClip.timelineStartAsTimecode()) 70 | XCTAssertEqual(refClipStartTC.components, .init(h: 00, m: 00, s: 00, f: 00)) 71 | XCTAssertEqual(refClipStartTC.frameRate, .fps25) 72 | let refClipDurTC = try XCTUnwrap(refClip.timelineDurationAsTimecode()) 73 | XCTAssertEqual(refClipDurTC.components, .init(h: 00, m: 01, s: 31, f: 12)) 74 | XCTAssertEqual(refClipDurTC.frameRate, .fps25) 75 | 76 | // local XML attributes 77 | // `ref-clip` itself doesn't have a start time, but its resource does 78 | XCTAssertNil(refClip.startAsTimecode()) 79 | // `ref-clip` itself doesn't have a duration time, but its resource does 80 | XCTAssertNil(refClip.durationAsTimecode()) 81 | 82 | // test markers 83 | 84 | let markers = await refClip.extract(preset: .markers, scope: .mainTimeline) 85 | XCTAssertEqual(markers.count, 10) 86 | } 87 | } 88 | 89 | #endif 90 | -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/FinalCutPro/File Tests/FinalCutPro FCPXML StandaloneRefClip.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FinalCutPro FCPXML StandaloneRefClip.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import XCTest 10 | @testable import DAWFileKit 11 | import OTCore 12 | import TimecodeKit 13 | 14 | final class FinalCutPro_FCPXML_StandaloneRefClip: FCPXMLTestCase { 15 | override func setUp() { } 16 | override func tearDown() { } 17 | 18 | // MARK: - Test Data 19 | 20 | var fileContents: Data { get throws { 21 | try XCTUnwrap(loadFileContents( 22 | forResource: "StandaloneRefClip", 23 | withExtension: "fcpxml", 24 | subFolder: .fcpxmlExports 25 | )) 26 | } } 27 | 28 | // MARK: - Tests 29 | 30 | func testParse() throws { 31 | // load file 32 | let rawData = try fileContents 33 | 34 | // parse file 35 | let fcpxml = try FinalCutPro.FCPXML(fileContent: rawData) 36 | 37 | // version 38 | XCTAssertEqual(fcpxml.version, .ver1_11) 39 | } 40 | 41 | /// Test that FCPXML that doesn't contain a project is still able to extract standalone clips. 42 | func testExtract() async throws { 43 | // load file 44 | let rawData = try fileContents 45 | 46 | // load 47 | let fcpxml = try FinalCutPro.FCPXML(fileContent: rawData) 48 | 49 | // timelines 50 | let timelines = fcpxml.allTimelines() 51 | XCTAssertEqual(timelines.count, 1) 52 | 53 | let anyTimeline = try XCTUnwrap(timelines.first) 54 | 55 | // AnyTimeline 56 | 57 | let timelineStartTC = try XCTUnwrap(anyTimeline.timelineStartAsTimecode()) 58 | XCTAssertEqual(timelineStartTC.components, .init(h: 00, m: 00, s: 00, f: 00)) 59 | XCTAssertEqual(timelineStartTC.frameRate, .fps29_97) 60 | let timelineDurTC = try XCTUnwrap(anyTimeline.timelineDurationAsTimecode()) 61 | XCTAssertEqual(timelineDurTC.components, .init(h: 00, m: 00, s: 09, f: 00)) 62 | XCTAssertEqual(timelineDurTC.frameRate, .fps29_97) 63 | 64 | // unwrap RefClip 65 | 66 | guard case .refClip(let refClip) = anyTimeline else { XCTFail() ; return } 67 | 68 | // FCPXMLElementMetaTimeline 69 | let refClipStartTC = try XCTUnwrap(refClip.timelineStartAsTimecode()) 70 | XCTAssertEqual(refClipStartTC.components, .init(h: 00, m: 00, s: 00, f: 00)) 71 | XCTAssertEqual(refClipStartTC.frameRate, .fps29_97) 72 | let refClipDurTC = try XCTUnwrap(refClip.timelineDurationAsTimecode()) 73 | XCTAssertEqual(refClipDurTC.components, .init(h: 00, m: 00, s: 09, f: 00)) 74 | XCTAssertEqual(refClipDurTC.frameRate, .fps29_97) 75 | 76 | // local XML attributes 77 | // `ref-clip` itself doesn't have a start time, but its resource does 78 | XCTAssertNil(refClip.startAsTimecode()) 79 | // `ref-clip` itself doesn't have a duration time, but its resource does 80 | XCTAssertNil(refClip.durationAsTimecode()) 81 | 82 | // test markers 83 | 84 | let markers = await refClip.extract(preset: .markers, scope: .mainTimeline) 85 | XCTAssertEqual(markers.count, 1) 86 | } 87 | } 88 | 89 | #endif 90 | -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/FinalCutPro/File Tests/FinalCutPro FCPXML SyncClipRoles2.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FinalCutPro FCPXML SyncClipRoles2.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import XCTest 10 | /* @testable */ import DAWFileKit 11 | import OTCore 12 | import TimecodeKit 13 | 14 | final class FinalCutPro_FCPXML_SyncClipRoles2: FCPXMLTestCase { 15 | override func setUp() { } 16 | override func tearDown() { } 17 | 18 | // MARK: - Test Data 19 | 20 | var fileContents: Data { get throws { 21 | try XCTUnwrap(loadFileContents( 22 | forResource: "SyncClipRoles2", 23 | withExtension: "fcpxml", 24 | subFolder: .fcpxmlExports 25 | )) 26 | } } 27 | 28 | /// Ensure that elements that can appear in various locations in the XML hierarchy are all found. 29 | func testParse() async throws { 30 | // load file 31 | let rawData = try fileContents 32 | 33 | // parse file 34 | let fcpxml = try FinalCutPro.FCPXML(fileContent: rawData) 35 | 36 | // events 37 | let events = fcpxml.allEvents() 38 | XCTAssertEqual(events.count, 1) 39 | 40 | let event = try XCTUnwrap(events[safe: 0]) 41 | 42 | // project 43 | let projects = event.projects.zeroIndexed 44 | XCTAssertEqual(projects.count, 1) 45 | 46 | let project = try XCTUnwrap(projects[safe: 0]) 47 | 48 | // sequence 49 | let sequence = try XCTUnwrap(project.sequence) 50 | 51 | // spine 52 | let spine = try XCTUnwrap(sequence.spine) 53 | 54 | let storyElements = spine.storyElements.zeroIndexed 55 | XCTAssertEqual(storyElements.count, 1) 56 | 57 | // story elements 58 | let clip1 = try XCTUnwrap(storyElements[safe: 0]?.fcpAsSyncClip) 59 | // confirm we have the right clip 60 | XCTAssertEqual(clip1.name, "5A-1-1") 61 | 62 | let markers = clip1.storyElements 63 | .filter(whereFCPElement: .marker) 64 | .zeroIndexed 65 | XCTAssertEqual(markers.count, 1) 66 | 67 | let marker = try XCTUnwrap(markers.first) 68 | // confirm we have the right marker 69 | XCTAssertEqual(marker.name, "Marker 1") 70 | let extractedMarker = await marker.element.fcpExtract() 71 | XCTAssertEqual( 72 | extractedMarker.value(forContext: .absoluteStartAsTimecode()), 73 | Self.tc("01:01:18:03", .fps25) 74 | ) 75 | XCTAssertEqual(extractedMarker.value(forContext: .inheritedRoles), [ 76 | .defaulted(.video(raw: "Video")!) // from first video asset in sync clip 77 | // no audio role 78 | ]) 79 | } 80 | } 81 | 82 | #endif 83 | -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/FinalCutPro/File Tests/FinalCutPro FCPXML TitlesRoles.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FinalCutPro FCPXML TitlesRoles.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import XCTest 10 | @testable import DAWFileKit 11 | import OTCore 12 | import TimecodeKit 13 | 14 | final class FinalCutPro_FCPXML_TitlesRoles: FCPXMLTestCase { 15 | override func setUp() { } 16 | override func tearDown() { } 17 | 18 | // MARK: - Test Data 19 | 20 | var fileContents: Data { get throws { 21 | try XCTUnwrap(loadFileContents( 22 | forResource: "TitlesRoles", 23 | withExtension: "fcpxml", 24 | subFolder: .fcpxmlExports 25 | )) 26 | } } 27 | 28 | /// Project @ 24fps. 29 | let projectFrameRate: TimecodeFrameRate = .fps24 30 | 31 | func testParse() throws { 32 | // load 33 | let rawData = try fileContents 34 | let fcpxml = try FinalCutPro.FCPXML(fileContent: rawData) 35 | 36 | // version 37 | XCTAssertEqual(fcpxml.version, .ver1_11) 38 | 39 | // skip testing file contents, we only care about roles assigned to markers for these tests 40 | } 41 | 42 | func testExtractMarkers() async throws { 43 | // load file 44 | let rawData = try fileContents 45 | 46 | // load 47 | let fcpxml = try FinalCutPro.FCPXML(fileContent: rawData) 48 | 49 | // project 50 | let project = try XCTUnwrap(fcpxml.allProjects().first) 51 | 52 | let extractedMarkers = await project 53 | .extract(preset: .markers, scope: .deep()) 54 | .sortedByAbsoluteStartTimecode() 55 | // .zeroIndexed // not necessary after sorting - sort returns new array 56 | 57 | let markers = extractedMarkers 58 | 59 | let expectedMarkerCount = 2 60 | XCTAssertEqual(markers.count, expectedMarkerCount) 61 | 62 | print("Markers sorted by absolute timecode:") 63 | print(Self.debugString(for: markers)) 64 | 65 | // Titles clips should never have an audio role 66 | 67 | let marker1 = try XCTUnwrap(markers[safe: 0]) 68 | 69 | XCTAssertEqual(marker1.roles, [ 70 | .defaulted(.video(.titlesRole)) 71 | ]) 72 | 73 | let marker2 = try XCTUnwrap(markers[safe: 1]) 74 | 75 | // In FCP, this Title clip anchored to has the role of Titles 76 | XCTAssertEqual(marker2.roles, [ 77 | .defaulted(.video(.titlesRole)) 78 | ]) 79 | } 80 | } 81 | 82 | #endif 83 | -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/FinalCutPro/Logic and Parsing/FinalCutPro FCPXML Library Tests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FinalCutPro FCPXML Library Tests.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import XCTest 10 | @testable import DAWFileKit 11 | import OTCore 12 | import TimecodeKit 13 | 14 | final class FinalCutPro_FCPXML_Library: FCPXMLTestCase { 15 | override func setUp() { } 16 | override func tearDown() { } 17 | 18 | func testLocation() throws { 19 | let url = try XCTUnwrap(URL(string: "file:///Users/user/Movies/MyLibrary.fcpbundle/")) 20 | let library = FinalCutPro.FCPXML.Library(location: url) 21 | 22 | XCTAssertEqual(library.location, url) 23 | } 24 | 25 | func testName() throws { 26 | let url = try XCTUnwrap(URL(string: "file:///Users/user/Movies/MyLibrary.fcpbundle/")) 27 | let library = FinalCutPro.FCPXML.Library(location: url) 28 | 29 | XCTAssertEqual(library.name, "MyLibrary") 30 | } 31 | } 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/FinalCutPro/Logic and Parsing/FinalCutPro FCPXML Root Version Tests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FinalCutPro FCPXML Root Version Tests.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import XCTest 10 | /* @testable */ import DAWFileKit 11 | import OTCore 12 | import TimecodeKit 13 | 14 | final class FinalCutPro_FCPXML_RootVersionTests: FCPXMLTestCase { 15 | override func setUp() { } 16 | override func tearDown() { } 17 | 18 | typealias Version = FinalCutPro.FCPXML.Version 19 | 20 | func testVersion() { 21 | let v = Version(major: 1, minor: 12) 22 | 23 | XCTAssertEqual(v.major, 1) 24 | XCTAssertEqual(v.minor, 12) 25 | } 26 | 27 | func testVersion_Equatable() { 28 | XCTAssertEqual( 29 | Version(major: 1, minor: 12), 30 | Version(major: 1, minor: 12) 31 | ) 32 | 33 | XCTAssertNotEqual( 34 | Version(major: 1, minor: 12), 35 | Version(major: 1, minor: 13) 36 | ) 37 | 38 | XCTAssertNotEqual( 39 | Version(major: 1, minor: 12), 40 | Version(major: 2, minor: 12) 41 | ) 42 | } 43 | 44 | func testVersion_Comparable() { 45 | XCTAssertFalse( 46 | Version(major: 1, minor: 12) < Version(major: 1, minor: 12) 47 | ) 48 | 49 | XCTAssertFalse( 50 | Version(major: 1, minor: 12) > Version(major: 1, minor: 12) 51 | ) 52 | 53 | XCTAssertTrue( 54 | Version(major: 1, minor: 11) < Version(major: 1, minor: 12) 55 | ) 56 | 57 | XCTAssertTrue( 58 | Version(major: 1, minor: 12) > Version(major: 1, minor: 11) 59 | ) 60 | 61 | XCTAssertTrue( 62 | Version(major: 1, minor: 10) < Version(major: 2, minor: 3) 63 | ) 64 | 65 | XCTAssertTrue( 66 | Version(major: 2, minor: 3) > Version(major: 1, minor: 10) 67 | ) 68 | } 69 | 70 | func testVersion_RawValue_Invalid() throws { 71 | XCTAssertNil(Version(rawValue: "")) 72 | XCTAssertNil(Version(rawValue: "1.")) 73 | XCTAssertNil(Version(rawValue: "1")) 74 | XCTAssertNil(Version(rawValue: "1.A")) 75 | XCTAssertNil(Version(rawValue: "A")) 76 | XCTAssertNil(Version(rawValue: "A.1")) 77 | XCTAssertNil(Version(rawValue: "A.A")) 78 | XCTAssertNil(Version(rawValue: "A.A.A")) 79 | XCTAssertNil(Version(rawValue: "1.12.")) 80 | XCTAssertNil(Version(rawValue: "1.12.A")) 81 | } 82 | 83 | func testVersion_Init_RawValue() throws { 84 | let v = try XCTUnwrap(Version(rawValue: "1.12")) 85 | 86 | XCTAssertEqual(v.major, 1) 87 | XCTAssertEqual(v.minor, 12) 88 | } 89 | 90 | func testVersion_RawValue() throws { 91 | let v = try XCTUnwrap(Version(rawValue: "1.12")) 92 | 93 | XCTAssertEqual(v.rawValue, "1.12") 94 | } 95 | } 96 | 97 | #endif 98 | -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/FinalCutPro/Logic and Parsing/FinalCutPro FCPXML Structure.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FinalCutPro FCPXML Structure.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | #if os(macOS) // XMLNode only works on macOS 8 | 9 | import XCTest 10 | /* @testable */ import DAWFileKit 11 | import OTCore 12 | import TimecodeKit 13 | 14 | final class FinalCutPro_FCPXML_Structure: FCPXMLTestCase { 15 | override func setUp() { } 16 | override func tearDown() { } 17 | 18 | /// Ensure that elements that can appear in various locations in the XML hierarchy are all found. 19 | func testParse() throws { 20 | // load file 21 | 22 | let rawData = try XCTUnwrap(loadFileContents( 23 | forResource: "Structure", 24 | withExtension: "fcpxml", 25 | subFolder: .fcpxmlExports 26 | )) 27 | 28 | // load 29 | 30 | let fcpxml = try FinalCutPro.FCPXML(fileContent: rawData) 31 | 32 | // events 33 | 34 | let events = Set(fcpxml.allEvents().map(\.name)) 35 | XCTAssertEqual(events, ["Test Event", "Test Event 2"]) 36 | 37 | // projects 38 | 39 | let projects = Set(fcpxml.allProjects().map(\.name)) 40 | XCTAssertEqual(projects, ["Test Project", "Test Project 2", "Test Project 3"]) 41 | 42 | // TODO: it may be possible for story elements (sequence, clips, etc.) to be in the root `fcpxml` element 43 | // the docs say that they can be there as browser elements 44 | // test parsing them? might need a new method to get them specifically like `FinalCutPro.FCPXML.parseStoryElementsInRoot()` 45 | } 46 | } 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/FinalCutPro/Resources/FCPXML Exports/AuditionMarkers.fcpxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | <text> 17 | <text-style ref="ts1">Title</text-style> 18 | </text> 19 | <text-style-def id="ts1"> 20 | <text-style font="Helvetica" fontSize="63" fontFace="Regular" fontColor="1 1 1 1" alignment="center"/> 21 | </text-style-def> 22 | <marker start="721721/200s" duration="1001/30000s" value="Marker 1"/> 23 | 24 | 25 | <text> 26 | <text-style ref="ts2">Title</text-style> 27 | </text> 28 | <text-style-def id="ts2"> 29 | <text-style font="Helvetica" fontSize="63" fontFace="Regular" fontColor="1 1 1 1" alignment="center"/> 30 | </text-style-def> 31 | <marker start="1802801/500s" duration="1001/30000s" value="Marker 2"/> 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/FinalCutPro/Resources/FCPXML Exports/BasicMarkers.fcpxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | <text> 16 | <text-style ref="ts1">Title</text-style> 17 | </text> 18 | <text-style-def id="ts1"> 19 | <text-style font="Helvetica" fontSize="63" fontFace="Regular" fontColor="1 1 1 1" alignment="center"/> 20 | </text-style-def> 21 | <marker start="27248221/7500s" duration="1001/30000s" value="Standard Marker" note="some notes here"/> 22 | <marker start="7266259/2000s" duration="1001/30000s" value="To Do Marker, Incomplete" completed="0" note="more notes here"/> 23 | <marker start="54497443/15000s" duration="1001/30000s" value="To Do Marker, Completed" completed="1" note="notes yay"/> 24 | <chapter-marker start="108995887/30000s" duration="1001/30000s" value="Chapter Marker" posterOffset="11/30s"/> 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/FinalCutPro/Resources/FCPXML Exports/BasicMarkers_1HourProjectStart.fcpxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | <text> 16 | <text-style ref="ts1">Title</text-style> 17 | </text> 18 | <text-style-def id="ts1"> 19 | <text-style font="Helvetica" fontSize="63" fontFace="Regular" fontColor="1 1 1 1" alignment="center"/> 20 | </text-style-def> 21 | <marker start="27248221/7500s" duration="1001/30000s" value="Standard Marker" note="some notes here"/> 22 | <marker start="7266259/2000s" duration="1001/30000s" value="To Do Marker, Incomplete" completed="0" note="more notes here"/> 23 | <marker start="54497443/15000s" duration="1001/30000s" value="To Do Marker, Completed" completed="1" note="notes yay"/> 24 | <chapter-marker start="108995887/30000s" duration="1001/30000s" value="Chapter Marker" posterOffset="11/30s"/> 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/FinalCutPro/Resources/FCPXML Exports/Occlusion2.fcpxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | <title ref="r2" lane="1" offset="3600s" name="Basic Title 2" start="3600s" duration="10123200/30000s"> 16 | <marker start="3611s" duration="100/2500s" value="Visible on Main Timeline" completed="0"/> 17 | <marker start="3700s" duration="100/2500s" value="Not Visible on Main Timeline" completed="0"/> 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/FinalCutPro/Resources/FCPXML Exports/StandaloneAssetClip.fcpxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | Lavc59.37.100 libx264 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/FinalCutPro/Resources/FCPXML Exports/StandaloneRefClip.fcpxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | Lavc59.37.100 libx264 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/FinalCutPro/Resources/FCPXML Exports/Structure.fcpxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/ProTools/ProTools SessionText EmptySession.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProTools SessionText EmptySession.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | import XCTest 8 | @testable import DAWFileKit 9 | import OTCore 10 | import TimecodeKit 11 | 12 | class ProTools_SessionText_EmptySession: XCTestCase { 13 | override func setUp() { } 14 | override func tearDown() { } 15 | 16 | func testSessionText_EmptySession() throws { 17 | // load file 18 | 19 | let filename = "SessionText_EmptySession_23-976fps_DefaultExportOptions_PT2020.3" 20 | guard let rawData = loadFileContents( 21 | forResource: filename, 22 | withExtension: "txt", 23 | subFolder: .ptSessionTextExports 24 | ) 25 | else { XCTFail("Could not form URL, possibly could not find file."); return } 26 | 27 | // parse 28 | 29 | var parseMessages: [ProTools.SessionInfo.ParseMessage] = [] 30 | let sessionInfo = try ProTools.SessionInfo( 31 | fileContent: rawData, 32 | // no time values present in the file but supply a time format anyway to suppress the 33 | // format auto-detect error 34 | timeValueFormat: .timecode, 35 | messages: &parseMessages 36 | ) 37 | 38 | // parse messages 39 | 40 | XCTAssertEqual(parseMessages.errors.count, 0) 41 | if !parseMessages.errors.isEmpty { 42 | dump(parseMessages.errors) 43 | } 44 | 45 | // main header 46 | 47 | XCTAssertEqual(sessionInfo.main.name, "SessionText_EmptySession") 48 | XCTAssertEqual(sessionInfo.main.sampleRate, 48000.0) 49 | XCTAssertEqual(sessionInfo.main.bitDepth, "24-bit") 50 | XCTAssertEqual( 51 | sessionInfo.main.startTimecode, 52 | try ProTools.formTimecode(.init(h: 0, m: 59, s: 55, f: 00), at: .fps23_976) 53 | ) 54 | XCTAssertEqual(sessionInfo.main.frameRate, .fps23_976) 55 | XCTAssertEqual(sessionInfo.main.audioTrackCount, 0) 56 | XCTAssertEqual(sessionInfo.main.audioClipCount, 0) 57 | XCTAssertEqual(sessionInfo.main.audioFileCount, 0) 58 | 59 | // files - online 60 | 61 | XCTAssertEqual(sessionInfo.onlineFiles, []) 62 | 63 | // files - offline 64 | 65 | XCTAssertEqual(sessionInfo.offlineFiles, []) 66 | 67 | // clips - online 68 | 69 | XCTAssertNil(sessionInfo.onlineClips) // missing section 70 | 71 | // clips - offline 72 | 73 | XCTAssertNil(sessionInfo.offlineClips) // missing section 74 | 75 | // plug-ins 76 | 77 | XCTAssertEqual(sessionInfo.plugins, []) 78 | 79 | // tracks 80 | 81 | XCTAssertEqual(sessionInfo.tracks, []) 82 | 83 | // markers 84 | 85 | XCTAssertEqual(sessionInfo.markers, []) 86 | 87 | // orphan data 88 | 89 | XCTAssertNil(sessionInfo.orphanData) // none 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/ProTools/ProTools SessionText OrphanData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProTools SessionText OrphanData.swift 3 | // DAWFileKit • https://github.com/orchetect/DAWFileKit 4 | // © 2022 Steffan Andrews • Licensed under MIT License 5 | // 6 | 7 | import XCTest 8 | @testable import DAWFileKit 9 | import OTCore 10 | import TimecodeKit 11 | 12 | class ProTools_SessionText_OrphanData: XCTestCase { 13 | override func setUp() { } 14 | override func tearDown() { } 15 | 16 | func testSessionText_OrphanData() throws { 17 | // load file 18 | 19 | let filename = "SessionText_UnrecognizedSection_23-976fps_DefaultExportOptions_PT2020.3" 20 | guard let rawData = loadFileContents( 21 | forResource: filename, 22 | withExtension: "txt", 23 | subFolder: .ptSessionTextExports 24 | ) 25 | else { XCTFail("Could not form URL, possibly could not find file."); return } 26 | 27 | // parse 28 | 29 | var parseMessages: [ProTools.SessionInfo.ParseMessage] = [] 30 | let sessionInfo = try ProTools.SessionInfo(fileContent: rawData, messages: &parseMessages) 31 | 32 | // parse messages 33 | 34 | XCTAssertEqual(parseMessages.errors.count, 0) 35 | if !parseMessages.errors.isEmpty { 36 | dump(parseMessages.errors) 37 | } 38 | 39 | // orphan data 40 | // just test for orphan sections (unrecognized - a hypothetical in case new sections get 41 | // added to Pro Tools in the future) 42 | 43 | XCTAssertEqual(sessionInfo.orphanData?.count, 1) 44 | 45 | XCTAssertEqual( 46 | sessionInfo.orphanData?.first?.heading, 47 | "U N R E C O G N I Z E D S E C T I O N" 48 | ) 49 | XCTAssertEqual(sessionInfo.orphanData?.first?.content, []) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/ProTools/Resources/PT Session Text Exports/SessionText_EmptySession_23-976fps_DefaultExportOptions_PT2020.3.txt: -------------------------------------------------------------------------------- 1 | SESSION NAME: SessionText_EmptySession 2 | SAMPLE RATE: 48000.000000 3 | BIT DEPTH: 24-bit 4 | SESSION START TIMECODE: 00:59:55:00 5 | TIMECODE FORMAT: 23.976 Frame 6 | # OF AUDIO TRACKS: 0 7 | # OF AUDIO CLIPS: 0 8 | # OF AUDIO FILES: 0 9 | 10 | 11 | O N L I N E F I L E S I N S E S S I O N 12 | Filename Location 13 | 14 | 15 | O F F L I N E F I L E S I N S E S S I O N 16 | Filename Location 17 | 18 | 19 | P L U G - I N S L I S T I N G 20 | MANUFACTURER PLUG-IN NAME VERSION FORMAT STEMS NUMBER OF INSTANCES 21 | 22 | 23 | T R A C K L I S T I N G 24 | M A R K E R S L I S T I N G 25 | # LOCATION TIME REFERENCE UNITS NAME COMMENTS 26 | -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/ProTools/Resources/PT Session Text Exports/SessionText_ExtendedChars_TextEditFormat_PT2023.3.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orchetect/DAWFileKit/e71576a436405e7a20650a3dd1be3f7f3d8fbb4d/Tests/DAWFileKitTests/ProTools/Resources/PT Session Text Exports/SessionText_ExtendedChars_TextEditFormat_PT2023.3.txt -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/ProTools/Resources/PT Session Text Exports/SessionText_ExtendedChars_UTF8Format_PT2023.3.txt: -------------------------------------------------------------------------------- 1 | SESSION NAME: SessionText_UTF8Chars 2 | SAMPLE RATE: 48000.000000 3 | BIT DEPTH: 24-bit 4 | SESSION START TIMECODE: 00:59:50:00 5 | TIMECODE FORMAT: 24 Frame 6 | # OF AUDIO TRACKS: 0 7 | # OF AUDIO CLIPS: 0 8 | # OF AUDIO FILES: 0 9 | 10 | 11 | O N L I N E F I L E S I N S E S S I O N 12 | Filename Location 13 | 14 | 15 | O F F L I N E F I L E S I N S E S S I O N 16 | Filename Location 17 | 18 | 19 | P L U G - I N S L I S T I N G 20 | MANUFACTURER PLUG-IN NAME VERSION FORMAT STEMS NUMBER OF INSTANCES 21 | 22 | 23 | T R A C K L I S T I N G 24 | M A R K E R S L I S T I N G 25 | # LOCATION TIME REFERENCE UNITS NAME COMMENTS 26 | 1 01:00:00:00 480000 Samples Test Ellipsis… 27 | 2 01:01:00:00 3360000 Samples Test Em Dash — 28 | 3 01:02:00:00 6240000 Samples Test En Dash – 29 | 4 01:03:00:00 9120000 Samples Right Side Quote’s Not An Apostrophe 30 | -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/ProTools/Resources/PT Session Text Exports/SessionText_FPPFinal_23-976fps_DefaultExportOptions_PT2020.3.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orchetect/DAWFileKit/e71576a436405e7a20650a3dd1be3f7f3d8fbb4d/Tests/DAWFileKitTests/ProTools/Resources/PT Session Text Exports/SessionText_FPPFinal_23-976fps_DefaultExportOptions_PT2020.3.txt -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/ProTools/Resources/PT Session Text Exports/SessionText_MarkerRulersAndTrackMarkers_PT2023.12.txt: -------------------------------------------------------------------------------- 1 | SESSION NAME: Test 2 | SAMPLE RATE: 48000.000000 3 | BIT DEPTH: 24-bit 4 | SESSION START TIMECODE: 00:59:50:00 5 | TIMECODE FORMAT: 24 Frame 6 | # OF AUDIO TRACKS: 2 7 | # OF AUDIO CLIPS: 0 8 | # OF AUDIO FILES: 0 9 | 10 | 11 | M A R K E R S L I S T I N G 12 | # LOCATION TIME REFERENCE UNITS NAME TRACK NAME TRACK TYPE COMMENTS 13 | 1 01:00:00:00 480000 Samples Marker 1 Markers Ruler 14 | 2 01:00:01:00 528000 Samples Marker 2 Markers 2 Ruler Some comments 15 | 3 01:00:02:00 576000 Samples Marker 3 Markers 3 Ruler 16 | 4 01:00:03:00 624000 Samples Marker 4 Markers 4 Ruler 17 | 5 01:00:04:00 672000 Samples Marker 5 Markers 5 Ruler 18 | 6 01:00:05:00 720000 Samples Marker 6 Audio 1 Track More comments 19 | 7 01:00:06:00 768000 Samples Marker 7 Audio 2 Track 20 | -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/ProTools/Resources/PT Session Text Exports/SessionText_NewLinesAndTabs_DefaultExportOptions_PT2023.6.txt: -------------------------------------------------------------------------------- 1 | SESSION NAME: SessionText_NewLinesAndTabs 2 | SAMPLE RATE: 48000.000000 3 | BIT DEPTH: 24-bit 4 | SESSION START TIMECODE: 00:59:55:00 5 | TIMECODE FORMAT: 23.976 Frame 6 | # OF AUDIO TRACKS: 0 7 | # OF AUDIO CLIPS: 0 8 | # OF AUDIO FILES: 0 9 | 10 | 11 | O N L I N E F I L E S I N S E S S I O N 12 | Filename Location 13 | 14 | 15 | O F F L I N E F I L E S I N S E S S I O N 16 | Filename Location 17 | 18 | 19 | P L U G - I N S L I S T I N G 20 | MANUFACTURER PLUG-IN NAME VERSION FORMAT STEMS NUMBER OF INSTANCES 21 | 22 | 23 | T R A C K L I S T I N G 24 | M A R K E R S L I S T I N G 25 | # LOCATION TIME REFERENCE UNITS NAME COMMENTS 26 | 1 01:00:00:00 240240 Samples Marker Name 27 | With New Line 28 | 2 01:00:01:00 288288 Samples Normal Marker Name Comment Here 29 | With New Line 30 | 3 01:00:02:00 336336 Samples Marker Name Again 31 | With New Line Again Comment Here Again 32 | With New Line Again 33 | 4 01:00:03:00 384384 Samples Normal Marker Name Again 34 | 5 01:00:04:00 432432 Samples Marker Name With Tab 35 | 6 01:00:05:00 480480 Samples Normal Marker Name Comments Here With Tab 36 | 7 01:00:06:00 528528 Samples Marker Name With Tab Comments Here With Tab 37 | 8 01:00:07:00 576576 Samples Marker Name With Tab And Another Tab 38 | 9 01:00:08:00 624624 Samples Normal Marker Name Comment Here With Tab And Another Tab 39 | 10 01:00:09:00 672672 Samples Marker Name With Tab And Another Tab Comment Here With Tab And Another Tab 40 | 11 01:00:10:00 720720 Samples Marker Name With Tab 41 | And Newline 42 | 12 01:00:11:00 768768 Samples Normal Marker Name Comment Here With Tab 43 | And Newline 44 | -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/ProTools/Resources/PT Session Text Exports/SessionText_Plugins_23-976fps_DefaultExportOptions_PT2020.3.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orchetect/DAWFileKit/e71576a436405e7a20650a3dd1be3f7f3d8fbb4d/Tests/DAWFileKitTests/ProTools/Resources/PT Session Text Exports/SessionText_Plugins_23-976fps_DefaultExportOptions_PT2020.3.txt -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/ProTools/Resources/PT Session Text Exports/SessionText_SimpleTest_23-976fps_DefaultExportOptions_PT2020.3.txt: -------------------------------------------------------------------------------- 1 | SESSION NAME: SessionText_SimpleTest 2 | SAMPLE RATE: 48000.000000 3 | BIT DEPTH: 24-bit 4 | SESSION START TIMECODE: 00:59:55:00 5 | TIMECODE FORMAT: 23.976 Frame 6 | # OF AUDIO TRACKS: 1 7 | # OF AUDIO CLIPS: 1 8 | # OF AUDIO FILES: 1 9 | 10 | 11 | O N L I N E F I L E S I N S E S S I O N 12 | Filename Location 13 | Audio 1_01.wav Macintosh HD:Users:stef:Desktop:SessionText_SimpleTest:Audio Files: 14 | 15 | 16 | O F F L I N E F I L E S I N S E S S I O N 17 | Filename Location 18 | 19 | 20 | O N L I N E C L I P S I N S E S S I O N 21 | CLIP NAME Source File 22 | Audio 1_01 Audio 1_01.wav 23 | 24 | 25 | P L U G - I N S L I S T I N G 26 | MANUFACTURER PLUG-IN NAME VERSION FORMAT STEMS NUMBER OF INSTANCES 27 | 28 | 29 | T R A C K L I S T I N G 30 | TRACK NAME: Audio 1 31 | COMMENTS: 32 | USER DELAY: 0 Samples 33 | STATE: 34 | PLUG-INS: 35 | CHANNEL EVENT CLIP NAME START TIME END TIME DURATION STATE 36 | 1 1 Audio 1_01 01:00:00:00 01:00:05:00 00:00:05:00 Unmuted 37 | 38 | 39 | M A R K E R S L I S T I N G 40 | # LOCATION TIME REFERENCE UNITS NAME COMMENTS 41 | -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/ProTools/Resources/PT Session Text Exports/SessionText_TimeFormats_BarsBeats_PT2022.9.txt: -------------------------------------------------------------------------------- 1 | SESSION NAME: Test 2 | SAMPLE RATE: 48000.000000 3 | BIT DEPTH: 24-bit 4 | SESSION START TIMECODE: 23:57:00:00 5 | TIMECODE FORMAT: 23.976 Frame 6 | # OF AUDIO TRACKS: 2 7 | # OF AUDIO CLIPS: 0 8 | # OF AUDIO FILES: 0 9 | 10 | 11 | O N L I N E F I L E S I N S E S S I O N 12 | Filename Location 13 | 14 | 15 | O F F L I N E F I L E S I N S E S S I O N 16 | Filename Location 17 | 18 | 19 | P L U G - I N S L I S T I N G 20 | MANUFACTURER PLUG-IN NAME VERSION FORMAT STEMS NUMBER OF INSTANCES 21 | 22 | 23 | T R A C K L I S T I N G 24 | TRACK NAME: Audio A 25 | COMMENTS: 26 | USER DELAY: 0 Samples 27 | STATE: 28 | PLUG-INS: 29 | CHANNEL EVENT CLIP NAME START TIME END TIME DURATION STATE 30 | 1 1 Audio Clip 1 Name 13|3 20|1 6|2| 720 Unmuted 31 | 32 | 33 | TRACK NAME: Audio B 34 | COMMENTS: 35 | USER DELAY: 0 Samples 36 | STATE: 37 | PLUG-INS: 38 | CHANNEL EVENT CLIP NAME START TIME END TIME DURATION STATE 39 | 1 1 Audio Clip 2 Name 5|1 11|4 6|2| 720 Unmuted 40 | 41 | 42 | M A R K E R S L I S T I N G 43 | # LOCATION TIME REFERENCE UNITS NAME COMMENTS 44 | 1 29|1 2695168 Samples Marker Absolute Comment 45 | 2 58|4 58|4 Ticks Marker Bars-Beats Comment 46 | -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/ProTools/Resources/PT Session Text Exports/SessionText_TimeFormats_BarsBeats_ShowSubframes_PT2022.9.txt: -------------------------------------------------------------------------------- 1 | SESSION NAME: Test 2 | SAMPLE RATE: 48000.000000 3 | BIT DEPTH: 24-bit 4 | SESSION START TIMECODE: 23:57:00:00.00 5 | TIMECODE FORMAT: 23.976 Frame 6 | # OF AUDIO TRACKS: 2 7 | # OF AUDIO CLIPS: 0 8 | # OF AUDIO FILES: 0 9 | 10 | 11 | O N L I N E F I L E S I N S E S S I O N 12 | Filename Location 13 | 14 | 15 | O F F L I N E F I L E S I N S E S S I O N 16 | Filename Location 17 | 18 | 19 | P L U G - I N S L I S T I N G 20 | MANUFACTURER PLUG-IN NAME VERSION FORMAT STEMS NUMBER OF INSTANCES 21 | 22 | 23 | T R A C K L I S T I N G 24 | TRACK NAME: Audio A 25 | COMMENTS: 26 | USER DELAY: 0 Samples 27 | STATE: 28 | PLUG-INS: 29 | CHANNEL EVENT CLIP NAME START TIME END TIME DURATION STATE 30 | 1 1 Audio Clip 1 Name 13|3| 048 20|1| 768 6|2| 720 Unmuted 31 | 32 | 33 | TRACK NAME: Audio B 34 | COMMENTS: 35 | USER DELAY: 0 Samples 36 | STATE: 37 | PLUG-INS: 38 | CHANNEL EVENT CLIP NAME START TIME END TIME DURATION STATE 39 | 1 1 Audio Clip 2 Name 5|1| 696 11|4| 456 6|2| 720 Unmuted 40 | 41 | 42 | M A R K E R S L I S T I N G 43 | # LOCATION TIME REFERENCE UNITS NAME COMMENTS 44 | 1 29|1| 287 2695168 Samples Marker Absolute Comment 45 | 2 58|4| 735 58|4| 735 Ticks Marker Bars-Beats Comment 46 | -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/ProTools/Resources/PT Session Text Exports/SessionText_TimeFormats_FeetFrames_PT2022.9.txt: -------------------------------------------------------------------------------- 1 | SESSION NAME: Test 2 | SAMPLE RATE: 48000.000000 3 | BIT DEPTH: 24-bit 4 | SESSION START TIMECODE: 23:57:00:00 5 | TIMECODE FORMAT: 23.976 Frame 6 | # OF AUDIO TRACKS: 2 7 | # OF AUDIO CLIPS: 0 8 | # OF AUDIO FILES: 0 9 | 10 | 11 | O N L I N E F I L E S I N S E S S I O N 12 | Filename Location 13 | 14 | 15 | O F F L I N E F I L E S I N S E S S I O N 16 | Filename Location 17 | 18 | 19 | P L U G - I N S L I S T I N G 20 | MANUFACTURER PLUG-IN NAME VERSION FORMAT STEMS NUMBER OF INSTANCES 21 | 22 | 23 | T R A C K L I S T I N G 24 | TRACK NAME: Audio A 25 | COMMENTS: 26 | USER DELAY: 0 Samples 27 | STATE: 28 | PLUG-INS: 29 | CHANNEL EVENT CLIP NAME START TIME END TIME DURATION STATE 30 | 1 1 Audio Clip 1 Name 37+08 57+09 20+01 Unmuted 31 | 32 | 33 | TRACK NAME: Audio B 34 | COMMENTS: 35 | USER DELAY: 0 Samples 36 | STATE: 37 | PLUG-INS: 38 | CHANNEL EVENT CLIP NAME START TIME END TIME DURATION STATE 39 | 1 1 Audio Clip 2 Name 12+08 32+09 20+01 Unmuted 40 | 41 | 42 | M A R K E R S L I S T I N G 43 | # LOCATION TIME REFERENCE UNITS NAME COMMENTS 44 | 1 84+03 2695168 Samples Marker Absolute Comment 45 | 2 173+13 58|4 Ticks Marker Bars-Beats Comment 46 | -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/ProTools/Resources/PT Session Text Exports/SessionText_TimeFormats_FeetFrames_ShowSubframes_PT2022.9.txt: -------------------------------------------------------------------------------- 1 | SESSION NAME: Test 2 | SAMPLE RATE: 48000.000000 3 | BIT DEPTH: 24-bit 4 | SESSION START TIMECODE: 23:57:00:00.00 5 | TIMECODE FORMAT: 23.976 Frame 6 | # OF AUDIO TRACKS: 2 7 | # OF AUDIO CLIPS: 0 8 | # OF AUDIO FILES: 0 9 | 10 | 11 | O N L I N E F I L E S I N S E S S I O N 12 | Filename Location 13 | 14 | 15 | O F F L I N E F I L E S I N S E S S I O N 16 | Filename Location 17 | 18 | 19 | P L U G - I N S L I S T I N G 20 | MANUFACTURER PLUG-IN NAME VERSION FORMAT STEMS NUMBER OF INSTANCES 21 | 22 | 23 | T R A C K L I S T I N G 24 | TRACK NAME: Audio A 25 | COMMENTS: 26 | USER DELAY: 0 Samples 27 | STATE: 28 | PLUG-INS: 29 | CHANNEL EVENT CLIP NAME START TIME END TIME DURATION STATE 30 | 1 1 Audio Clip 1 Name 37+08.60 57+09.60 20+01.00 Unmuted 31 | 32 | 33 | TRACK NAME: Audio B 34 | COMMENTS: 35 | USER DELAY: 0 Samples 36 | STATE: 37 | PLUG-INS: 38 | CHANNEL EVENT CLIP NAME START TIME END TIME DURATION STATE 39 | 1 1 Audio Clip 2 Name 12+08.70 32+09.70 20+01.00 Unmuted 40 | 41 | 42 | M A R K E R S L I S T I N G 43 | # LOCATION TIME REFERENCE UNITS NAME COMMENTS 44 | 1 84+03.58 2695168 Samples Marker Absolute Comment 45 | 2 173+13.18 58|4| 735 Ticks Marker Bars-Beats Comment 46 | -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/ProTools/Resources/PT Session Text Exports/SessionText_TimeFormats_MinSecs_PT2022.9.txt: -------------------------------------------------------------------------------- 1 | SESSION NAME: Test 2 | SAMPLE RATE: 48000.000000 3 | BIT DEPTH: 24-bit 4 | SESSION START TIMECODE: 23:57:00:00 5 | TIMECODE FORMAT: 23.976 Frame 6 | # OF AUDIO TRACKS: 2 7 | # OF AUDIO CLIPS: 0 8 | # OF AUDIO FILES: 0 9 | 10 | 11 | O N L I N E F I L E S I N S E S S I O N 12 | Filename Location 13 | 14 | 15 | O F F L I N E F I L E S I N S E S S I O N 16 | Filename Location 17 | 18 | 19 | P L U G - I N S L I S T I N G 20 | MANUFACTURER PLUG-IN NAME VERSION FORMAT STEMS NUMBER OF INSTANCES 21 | 22 | 23 | T R A C K L I S T I N G 24 | TRACK NAME: Audio A 25 | COMMENTS: 26 | USER DELAY: 0 Samples 27 | STATE: 28 | PLUG-INS: 29 | CHANNEL EVENT CLIP NAME START TIME END TIME DURATION STATE 30 | 1 1 Audio Clip 1 Name 0:25 0:38 0:13 Unmuted 31 | 32 | 33 | TRACK NAME: Audio B 34 | COMMENTS: 35 | USER DELAY: 0 Samples 36 | STATE: 37 | PLUG-INS: 38 | CHANNEL EVENT CLIP NAME START TIME END TIME DURATION STATE 39 | 1 1 Audio Clip 2 Name 0:08 0:21 0:13 Unmuted 40 | 41 | 42 | M A R K E R S L I S T I N G 43 | # LOCATION TIME REFERENCE UNITS NAME COMMENTS 44 | 1 0:56 2695168 Samples Marker Absolute Comment 45 | 2 1:55 58|4 Ticks Marker Bars-Beats Comment 46 | -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/ProTools/Resources/PT Session Text Exports/SessionText_TimeFormats_MinSecs_ShowSubframes_PT2022.9.txt: -------------------------------------------------------------------------------- 1 | SESSION NAME: Test 2 | SAMPLE RATE: 48000.000000 3 | BIT DEPTH: 24-bit 4 | SESSION START TIMECODE: 23:57:00:00.00 5 | TIMECODE FORMAT: 23.976 Frame 6 | # OF AUDIO TRACKS: 2 7 | # OF AUDIO CLIPS: 0 8 | # OF AUDIO FILES: 0 9 | 10 | 11 | O N L I N E F I L E S I N S E S S I O N 12 | Filename Location 13 | 14 | 15 | O F F L I N E F I L E S I N S E S S I O N 16 | Filename Location 17 | 18 | 19 | P L U G - I N S L I S T I N G 20 | MANUFACTURER PLUG-IN NAME VERSION FORMAT STEMS NUMBER OF INSTANCES 21 | 22 | 23 | T R A C K L I S T I N G 24 | TRACK NAME: Audio A 25 | COMMENTS: 26 | USER DELAY: 0 Samples 27 | STATE: 28 | PLUG-INS: 29 | CHANNEL EVENT CLIP NAME START TIME END TIME DURATION STATE 30 | 1 1 Audio Clip 1 Name 0:25.025 0:38.400 0:13.375 Unmuted 31 | 32 | 33 | TRACK NAME: Audio B 34 | COMMENTS: 35 | USER DELAY: 0 Samples 36 | STATE: 37 | PLUG-INS: 38 | CHANNEL EVENT CLIP NAME START TIME END TIME DURATION STATE 39 | 1 1 Audio Clip 2 Name 0:08.362 0:21.737 0:13.375 Unmuted 40 | 41 | 42 | M A R K E R S L I S T I N G 43 | # LOCATION TIME REFERENCE UNITS NAME COMMENTS 44 | 1 0:56.149 2695168 Samples Marker Absolute Comment 45 | 2 1:55.882 58|4| 735 Ticks Marker Bars-Beats Comment 46 | -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/ProTools/Resources/PT Session Text Exports/SessionText_TimeFormats_Samples_PT2022.9.txt: -------------------------------------------------------------------------------- 1 | SESSION NAME: Test 2 | SAMPLE RATE: 48000.000000 3 | BIT DEPTH: 24-bit 4 | SESSION START TIMECODE: 23:57:00:00 5 | TIMECODE FORMAT: 23.976 Frame 6 | # OF AUDIO TRACKS: 2 7 | # OF AUDIO CLIPS: 0 8 | # OF AUDIO FILES: 0 9 | 10 | 11 | O N L I N E F I L E S I N S E S S I O N 12 | Filename Location 13 | 14 | 15 | O F F L I N E F I L E S I N S E S S I O N 16 | Filename Location 17 | 18 | 19 | P L U G - I N S L I S T I N G 20 | MANUFACTURER PLUG-IN NAME VERSION FORMAT STEMS NUMBER OF INSTANCES 21 | 22 | 23 | T R A C K L I S T I N G 24 | TRACK NAME: Audio A 25 | COMMENTS: 26 | USER DELAY: 0 Samples 27 | STATE: 28 | PLUG-INS: 29 | CHANNEL EVENT CLIP NAME START TIME END TIME DURATION STATE 30 | 1 1 Audio Clip 1 Name 1201200 1843200 642000 Unmuted 31 | 32 | 33 | TRACK NAME: Audio B 34 | COMMENTS: 35 | USER DELAY: 0 Samples 36 | STATE: 37 | PLUG-INS: 38 | CHANNEL EVENT CLIP NAME START TIME END TIME DURATION STATE 39 | 1 1 Audio Clip 2 Name 401408 1043408 642000 Unmuted 40 | 41 | 42 | M A R K E R S L I S T I N G 43 | # LOCATION TIME REFERENCE UNITS NAME COMMENTS 44 | 1 2695168 2695168 Samples Marker Absolute Comment 45 | 2 5562368 58|4 Ticks Marker Bars-Beats Comment 46 | -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/ProTools/Resources/PT Session Text Exports/SessionText_TimeFormats_Samples_ShowSubframes_PT2022.9.txt: -------------------------------------------------------------------------------- 1 | SESSION NAME: Test 2 | SAMPLE RATE: 48000.000000 3 | BIT DEPTH: 24-bit 4 | SESSION START TIMECODE: 23:57:00:00.00 5 | TIMECODE FORMAT: 23.976 Frame 6 | # OF AUDIO TRACKS: 2 7 | # OF AUDIO CLIPS: 0 8 | # OF AUDIO FILES: 0 9 | 10 | 11 | O N L I N E F I L E S I N S E S S I O N 12 | Filename Location 13 | 14 | 15 | O F F L I N E F I L E S I N S E S S I O N 16 | Filename Location 17 | 18 | 19 | P L U G - I N S L I S T I N G 20 | MANUFACTURER PLUG-IN NAME VERSION FORMAT STEMS NUMBER OF INSTANCES 21 | 22 | 23 | T R A C K L I S T I N G 24 | TRACK NAME: Audio A 25 | COMMENTS: 26 | USER DELAY: 0 Samples 27 | STATE: 28 | PLUG-INS: 29 | CHANNEL EVENT CLIP NAME START TIME END TIME DURATION STATE 30 | 1 1 Audio Clip 1 Name 1201200 1843200 642000 Unmuted 31 | 32 | 33 | TRACK NAME: Audio B 34 | COMMENTS: 35 | USER DELAY: 0 Samples 36 | STATE: 37 | PLUG-INS: 38 | CHANNEL EVENT CLIP NAME START TIME END TIME DURATION STATE 39 | 1 1 Audio Clip 2 Name 401408 1043408 642000 Unmuted 40 | 41 | 42 | M A R K E R S L I S T I N G 43 | # LOCATION TIME REFERENCE UNITS NAME COMMENTS 44 | 1 2695168 2695168 Samples Marker Absolute Comment 45 | 2 5562368 58|4| 735 Ticks Marker Bars-Beats Comment 46 | -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/ProTools/Resources/PT Session Text Exports/SessionText_TimeFormats_Timecode_PT2022.9.txt: -------------------------------------------------------------------------------- 1 | SESSION NAME: Test 2 | SAMPLE RATE: 48000.000000 3 | BIT DEPTH: 24-bit 4 | SESSION START TIMECODE: 23:57:00:00 5 | TIMECODE FORMAT: 23.976 Frame 6 | # OF AUDIO TRACKS: 2 7 | # OF AUDIO CLIPS: 0 8 | # OF AUDIO FILES: 0 9 | 10 | 11 | O N L I N E F I L E S I N S E S S I O N 12 | Filename Location 13 | 14 | 15 | O F F L I N E F I L E S I N S E S S I O N 16 | Filename Location 17 | 18 | 19 | P L U G - I N S L I S T I N G 20 | MANUFACTURER PLUG-IN NAME VERSION FORMAT STEMS NUMBER OF INSTANCES 21 | 22 | 23 | T R A C K L I S T I N G 24 | TRACK NAME: Audio A 25 | COMMENTS: 26 | USER DELAY: 0 Samples 27 | STATE: 28 | PLUG-INS: 29 | CHANNEL EVENT CLIP NAME START TIME END TIME DURATION STATE 30 | 1 1 Audio Clip 1 Name 23:57:25:00 23:57:38:08 00:00:13:08 Unmuted 31 | 32 | 33 | TRACK NAME: Audio B 34 | COMMENTS: 35 | USER DELAY: 0 Samples 36 | STATE: 37 | PLUG-INS: 38 | CHANNEL EVENT CLIP NAME START TIME END TIME DURATION STATE 39 | 1 1 Audio Clip 2 Name 23:57:08:08 23:57:21:17 00:00:13:08 Unmuted 40 | 41 | 42 | M A R K E R S L I S T I N G 43 | # LOCATION TIME REFERENCE UNITS NAME COMMENTS 44 | 1 23:57:56:02 2695168 Samples Marker Absolute Comment 45 | 2 23:58:55:18 58|4 Ticks Marker Bars-Beats Comment 46 | -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/ProTools/Resources/PT Session Text Exports/SessionText_TimeFormats_Timecode_ShowSubframes_PT2022.9.txt: -------------------------------------------------------------------------------- 1 | SESSION NAME: Test 2 | SAMPLE RATE: 48000.000000 3 | BIT DEPTH: 24-bit 4 | SESSION START TIMECODE: 23:57:00:00.00 5 | TIMECODE FORMAT: 23.976 Frame 6 | # OF AUDIO TRACKS: 2 7 | # OF AUDIO CLIPS: 0 8 | # OF AUDIO FILES: 0 9 | 10 | 11 | O N L I N E F I L E S I N S E S S I O N 12 | Filename Location 13 | 14 | 15 | O F F L I N E F I L E S I N S E S S I O N 16 | Filename Location 17 | 18 | 19 | P L U G - I N S L I S T I N G 20 | MANUFACTURER PLUG-IN NAME VERSION FORMAT STEMS NUMBER OF INSTANCES 21 | 22 | 23 | T R A C K L I S T I N G 24 | TRACK NAME: Audio A 25 | COMMENTS: 26 | USER DELAY: 0 Samples 27 | STATE: 28 | PLUG-INS: 29 | CHANNEL EVENT CLIP NAME START TIME END TIME DURATION STATE 30 | 1 1 Audio Clip 1 Name 23:57:25:00.00 23:57:38:08.68 00:00:13:08.68 Unmuted 31 | 32 | 33 | TRACK NAME: Audio B 34 | COMMENTS: 35 | USER DELAY: 0 Samples 36 | STATE: 37 | PLUG-INS: 38 | CHANNEL EVENT CLIP NAME START TIME END TIME DURATION STATE 39 | 1 1 Audio Clip 2 Name 23:57:08:08.50 23:57:21:17.18 00:00:13:08.68 Unmuted 40 | 41 | 42 | M A R K E R S L I S T I N G 43 | # LOCATION TIME REFERENCE UNITS NAME COMMENTS 44 | 1 23:57:56:02.24 2695168 Samples Marker Absolute Comment 45 | 2 23:58:55:18.41 58|4| 735 Ticks Marker Bars-Beats Comment 46 | -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/ProTools/Resources/PT Session Text Exports/SessionText_TracksOnly_OnlyTrackEDLs_PT2023.6.txt: -------------------------------------------------------------------------------- 1 | SESSION NAME: SessionText_TracksOnly 2 | SAMPLE RATE: 48000.000000 3 | BIT DEPTH: 24-bit 4 | SESSION START TIMECODE: 00:59:50:00 5 | TIMECODE FORMAT: 24 Frame 6 | # OF AUDIO TRACKS: 163 7 | # OF AUDIO CLIPS: 1541 8 | # OF AUDIO FILES: 247 9 | 10 | 11 | T R A C K L I S T I N G 12 | TRACK NAME: Audio 1 13 | COMMENTS: 14 | USER DELAY: 0 Samples 15 | STATE: 16 | CHANNEL EVENT CLIP NAME START TIME END TIME DURATION STATE 17 | 1 1 Warm Day in the City 01:00:15:06 01:01:05:13 00:00:50:07 Unmuted 18 | 1 2 Happy Go Lucky 01:01:05:13 01:01:57:23 00:00:52:09 Unmuted 19 | 20 | 21 | TRACK NAME: Audio 2 22 | COMMENTS: 23 | USER DELAY: 0 Samples 24 | STATE: 25 | CHANNEL EVENT CLIP NAME START TIME END TIME DURATION STATE 26 | 27 | 28 | TRACK NAME: Audio 3 29 | COMMENTS: 30 | USER DELAY: 0 Samples 31 | STATE: 32 | CHANNEL EVENT CLIP NAME START TIME END TIME DURATION STATE 33 | 34 | 35 | TRACK NAME: Audio 4 36 | COMMENTS: 37 | USER DELAY: 0 Samples 38 | STATE: 39 | CHANNEL EVENT CLIP NAME START TIME END TIME DURATION STATE 40 | 41 | 42 | TRACK NAME: Audio 5 43 | COMMENTS: 44 | USER DELAY: 0 Samples 45 | STATE: 46 | CHANNEL EVENT CLIP NAME START TIME END TIME DURATION STATE 47 | 48 | 49 | TRACK NAME: Audio 6 50 | COMMENTS: 51 | USER DELAY: 0 Samples 52 | STATE: 53 | CHANNEL EVENT CLIP NAME START TIME END TIME DURATION STATE 54 | 55 | 56 | TRACK NAME: Audio 7 57 | COMMENTS: 58 | USER DELAY: 0 Samples 59 | STATE: 60 | CHANNEL EVENT CLIP NAME START TIME END TIME DURATION STATE 61 | 62 | 63 | -------------------------------------------------------------------------------- /Tests/DAWFileKitTests/ProTools/Resources/PT Session Text Exports/SessionText_UnrecognizedSection_23-976fps_DefaultExportOptions_PT2020.3.txt: -------------------------------------------------------------------------------- 1 | SESSION NAME: SessionText_SimpleTest 2 | SAMPLE RATE: 48000.000000 3 | BIT DEPTH: 24-bit 4 | SESSION START TIMECODE: 00:59:55:00 5 | TIMECODE FORMAT: 23.976 Frame 6 | # OF AUDIO TRACKS: 1 7 | # OF AUDIO CLIPS: 1 8 | # OF AUDIO FILES: 1 9 | 10 | 11 | O N L I N E F I L E S I N S E S S I O N 12 | Filename Location 13 | Audio 1_01.wav Macintosh HD:Users:stef:Desktop:SessionText_SimpleTest:Audio Files: 14 | 15 | 16 | O F F L I N E F I L E S I N S E S S I O N 17 | Filename Location 18 | 19 | 20 | O N L I N E C L I P S I N S E S S I O N 21 | CLIP NAME Source File 22 | Audio 1_01 Audio 1_01.wav 23 | 24 | 25 | P L U G - I N S L I S T I N G 26 | MANUFACTURER PLUG-IN NAME VERSION FORMAT STEMS NUMBER OF INSTANCES 27 | 28 | 29 | U N R E C O G N I Z E D S E C T I O N 30 | 31 | 32 | T R A C K L I S T I N G 33 | TRACK NAME: Audio 1 34 | COMMENTS: 35 | USER DELAY: 0 Samples 36 | STATE: 37 | PLUG-INS: 38 | CHANNEL EVENT CLIP NAME START TIME END TIME DURATION STATE 39 | 1 1 Audio 1_01 01:00:00:00 01:00:05:00 00:00:05:00 Unmuted 40 | 41 | 42 | M A R K E R S L I S T I N G 43 | # LOCATION TIME REFERENCE UNITS NAME COMMENTS 44 | --------------------------------------------------------------------------------