Skip to content

Commit

Permalink
FCPXML: Added unit test
Browse files Browse the repository at this point in the history
  • Loading branch information
orchetect committed Jan 7, 2024
1 parent e49bc27 commit 2a97875
Show file tree
Hide file tree
Showing 3 changed files with 356 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
//
// FinalCutPro FCPXML 29.97d.swift
// DAWFileKit • https://github.com/orchetect/DAWFileKit
// © 2022 Steffan Andrews • Licensed under MIT License
//

#if os(macOS) // XMLNode only works on macOS

import XCTest
@testable import DAWFileKit
import OTCore
import TimecodeKit

final class FinalCutPro_FCPXML_29_97d: FCPXMLTestCase {
override func setUp() { }
override func tearDown() { }

// MARK: - Test Data

var fileContents: Data { get throws {
try XCTUnwrap(loadFileContents(
forResource: "29.97d",
withExtension: "fcpxml",
subFolder: .fcpxmlExports
))
} }

/// Project @ 29.97d fps.
/// Contains media @ 23.976fps and 29.97fps.
let projectFrameRate: TimecodeFrameRate = .fps29_97d

func testParse() throws {
// load
let rawData = try fileContents
let fcpxml = try FinalCutPro.FCPXML(fileContent: rawData)

// version
XCTAssertEqual(fcpxml.version, .ver1_11)

// event
let events = fcpxml.allEvents()
let event = try XCTUnwrap(events[safe: 0])

// project
let projects = event.projects.zeroIndexed
let project = try XCTUnwrap(projects[safe: 0])
XCTAssertEqual(project.name, "29.97d_V1")
XCTAssertEqual(
project.startTimecode(),
try Timecode(.rational(0, 1), at: projectFrameRate, base: .max80SubFrames)
)

// sequence
let sequence = try XCTUnwrap(projects[safe: 0]).sequence
XCTAssertEqual(sequence.format, "r1")
XCTAssertEqual(sequence.tcStartAsTimecode(), Self.tc("00:00:00;00", projectFrameRate))
XCTAssertEqual(sequence.tcStartAsTimecode()?.frameRate, projectFrameRate)
XCTAssertEqual(sequence.tcStartAsTimecode()?.subFramesBase, .max80SubFrames)
XCTAssertEqual(sequence.durationAsTimecode(), Self.tc("00:00:29;17", projectFrameRate))
XCTAssertEqual(sequence.audioLayout, .stereo)
XCTAssertEqual(sequence.audioRate, .rate48kHz)

// spine
let spine = try XCTUnwrap(sequence.spine)

let storyElements = spine.storyElements.zeroIndexed
XCTAssertEqual(storyElements.count, 7)
}

func testExtractMarkers() async throws {
// load file
let rawData = try fileContents

// load
let fcpxml = try FinalCutPro.FCPXML(fileContent: rawData)

// project
let project = try XCTUnwrap(fcpxml.allProjects().first)

let extractedMarkers = await project
.extract(preset: .markers, scope: .deep())
.sortedByAbsoluteStartTimecode()
// .zeroIndexed // not necessary after sorting - sort returns new array

let markers = extractedMarkers

// 3 x markers in r3 media resource (resource is used twice which produces 6 markers)
// 20 x markers in sequence

struct MarkerData {
let absTC: String // Absolute timecode, as seen in FCP
let name: String
let config: FinalCutPro.FCPXML.Marker.Configuration
let occ: FinalCutPro.FCPXML.ElementOcclusion
}

// swiftformat:disable all
let markerList: [MarkerData] = [
MarkerData(absTC: "00:00:01;14.00", name: "Marker 2", config: .standard, occ: .notOccluded),
MarkerData(absTC: "00:00:04;08.00", name: "Marker 3", config: .standard, occ: .notOccluded),
MarkerData(absTC: "00:00:05;26.00", name: "Marker 4", config: .standard, occ: .notOccluded),

// r3 media markers
MarkerData(absTC: "00:00:08;11.00", name: "INSIDE 1", config: .standard, occ: .notOccluded), // 00:00:06;29 @ 29.97 + 00:00:01;12 @ 29.97
MarkerData(absTC: "00:00:10;00.00", name: "INSIDE 2", config: .standard, occ: .notOccluded), // 00:00:06;29 @ 29.97 + 00:00:03;01 @ 29.97
MarkerData(absTC: "00:00:11;15.00", name: "INSIDE 3", config: .standard, occ: .fullyOccluded), // 00:00:06;29 @ 29.97 + 00:00:04;16 @ 29.97

MarkerData(absTC: "00:00:11;23.00", name: "Marker 8", config: .standard, occ: .notOccluded),
MarkerData(absTC: "00:00:13;01.00", name: "Marker 9", config: .standard, occ: .notOccluded),
MarkerData(absTC: "00:00:14;05.00", name: "Marker 1", config: .standard, occ: .notOccluded),
MarkerData(absTC: "00:00:14;10.00", name: "Marker 10", config: .standard, occ: .notOccluded),
MarkerData(absTC: "00:00:14;17.00", name: "Marker 11", config: .standard, occ: .notOccluded),
MarkerData(absTC: "00:00:14;24.00", name: "Marker 12", config: .standard, occ: .notOccluded),
MarkerData(absTC: "00:00:14;24.00", name: "Marker 13", config: .standard, occ: .notOccluded),
MarkerData(absTC: "00:00:15;00.00", name: "Marker 14", config: .standard, occ: .notOccluded),
MarkerData(absTC: "00:00:15;04.00", name: "Marker 15", config: .standard, occ: .notOccluded),
MarkerData(absTC: "00:00:15;13.00", name: "Marker 16", config: .standard, occ: .notOccluded),
MarkerData(absTC: "00:00:15;20.00", name: "Marker 17", config: .standard, occ: .notOccluded),
MarkerData(absTC: "00:00:19;25.00", name: "Marker 18", config: .standard, occ: .notOccluded),
MarkerData(absTC: "00:00:21;21.00", name: "Marker 19", config: .standard, occ: .notOccluded),
MarkerData(absTC: "00:00:24;08.00", name: "Marker 20", config: .standard, occ: .notOccluded),
MarkerData(absTC: "00:00:24;08.00", name: "Marker 23", config: .standard, occ: .notOccluded),

// r3 media markers
MarkerData(absTC: "00:00:24;23.00", name: "INSIDE 1", config: .standard, occ: .notOccluded), // 00:00:23;11 @ 29.97 + 00:00:01;12 @ 29.97
MarkerData(absTC: "00:00:26;12.00", name: "INSIDE 2", config: .standard, occ: .fullyOccluded), // 00:00:23;11 @ 29.97 + 00:00:03;01 @ 29.97

MarkerData(absTC: "00:00:26;29.00", name: "Marker 21", config: .standard, occ: .notOccluded),

// r3 media markers
MarkerData(absTC: "00:00:27;27.00", name: "INSIDE 3", config: .standard, occ: .fullyOccluded), // 00:00:23;11 @ 29.97 + 00:00:04;16 @ 29.97

MarkerData(absTC: "00:00:28;23.00", name: "Marker 22", config: .standard, occ: .notOccluded)
]
// swiftformat:enable all
let expectedMarkerCount = 20 + (2 * 3)
assert(markerList.count == expectedMarkerCount) // unit test sanity check

XCTAssertEqual(markers.count, expectedMarkerCount)

print("Markers sorted by absolute timecode:")
print(Self.debugString(for: markers))

for (index, markerData) in markerList.enumerated() {
let marker = try XCTUnwrap(markers[safe: index])
let desc = marker.name

// name
guard marker.name == markerData.name else {
XCTFail(
"Fail: marker name mismatch at index \(index). "
+ "Expected \(markerData.name.quoted) but found \(marker.name.quoted)."
)
continue
}

// config
XCTAssertEqual(marker.configuration, markerData.config, desc)

// absolute timecode
let tc = try XCTUnwrap(marker.timecode(), marker.name)
XCTAssertEqual(tc, Self.tc(markerData.absTC, projectFrameRate), desc)
XCTAssertEqual(tc.frameRate, projectFrameRate, desc)

// occlusion
XCTAssertEqual(marker.value(forContext: .effectiveOcclusion), markerData.occ, desc)
}
}

/// Just check that the correct number of markers are extracted for main timeline.
func testExtractMarkers_MainTimeline() async throws {
// load file
let rawData = try fileContents

// load
let fcpxml = try FinalCutPro.FCPXML(fileContent: rawData)

// project
let project = try XCTUnwrap(fcpxml.allProjects().first)

let extractedMarkers = await project
.extract(preset: .markers, scope: .mainTimeline)
.sortedByAbsoluteStartTimecode()
// .zeroIndexed // not necessary after sorting - sort returns new array

XCTAssertEqual(extractedMarkers.count, 20)
}
}

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
<fcpxml version="1.11">
<resources>
<format id="r1" name="FFVideoFormat1080p2997" frameDuration="1001/30000s" width="1920" height="1080" colorSpace="1-1-1 (Rec. 709)"/>
<asset id="r2" name="TestVideo2" uid="554B59605B289ECE8057E7FECBC3D3D0" start="0s" duration="101869/1000s" hasVideo="1" format="r1" hasAudio="1" videoSources="1" audioSources="1" audioChannels="2" audioRate="48000">
<media-rep kind="original-media" sig="554B59605B289ECE8057E7FECBC3D3D0" src="file:///Users/user/Movies/TestVideo2.mp4"/>
<asset id="r2" name="TestVideo" uid="554B59605B289ECE8057E7FECBC3D3D0" start="0s" duration="101869/1000s" hasVideo="1" format="r1" hasAudio="1" videoSources="1" audioSources="1" audioChannels="2" audioRate="48000">
<media-rep kind="original-media" sig="554B59605B289ECE8057E7FECBC3D3D0" src="file:///Users/user/Movies/TestVideo.mp4"/>
<metadata>
<md key="com.apple.proapps.studio.rawToLogConversion" value="0"/>
<md key="com.apple.proapps.spotlight.kMDItemProfileName" value="HD (1-1-1)"/>
Expand All @@ -23,7 +23,7 @@
<media id="r3" name="29.97_CC" uid="GYR/OKBAQ/2tErV+GGXCuA" modDate="2023-11-22 03:29:21 -0800">
<sequence format="r1" duration="174174/30000s" tcStart="0s" tcFormat="NDF" audioLayout="stereo" audioRate="48k">
<spine>
<asset-clip ref="r2" offset="0s" name="TestVideo2" start="452452/30000s" duration="174174/30000s" tcFormat="NDF" audioRole="dialogue">
<asset-clip ref="r2" offset="0s" name="TestVideo" start="452452/30000s" duration="174174/30000s" tcFormat="NDF" audioRole="dialogue">
<adjust-colorConform enabled="1" autoOrManual="manual" conformType="conformNone" peakNitsOfPQSource="1000" peakNitsOfSDRToPQSource="203"/>
<marker start="247247/15000s" duration="1001/30000s" value="INSIDE 1"/>
<marker start="181181/10000s" duration="1001/30000s" value="INSIDE 2"/>
Expand All @@ -43,19 +43,19 @@
<project name="29.97_V1" uid="80610E15-6330-4219-B9B4-5AD38FD37508" modDate="2023-11-22 03:29:21 -0800">
<sequence format="r1" duration="887887/30000s" tcStart="0s" tcFormat="NDF" audioLayout="stereo" audioRate="48k">
<spine>
<asset-clip ref="r2" offset="0s" name="TestVideo2" duration="103103/30000s" tcFormat="NDF" audioRole="dialogue">
<asset-clip ref="r2" offset="0s" name="TestVideo" duration="103103/30000s" tcFormat="NDF" audioRole="dialogue">
<adjust-colorConform enabled="1" autoOrManual="manual" conformType="conformNone" peakNitsOfPQSource="1000" peakNitsOfSDRToPQSource="203"/>
<marker start="11011/7500s" duration="1001/30000s" value="Marker 2"/>
</asset-clip>
<asset-clip ref="r2" offset="103103/30000s" name="TestVideo2" start="214214/30000s" duration="106106/30000s" tcFormat="NDF" audioRole="dialogue">
<asset-clip ref="r2" offset="103103/30000s" name="TestVideo" start="214214/30000s" duration="106106/30000s" tcFormat="NDF" audioRole="dialogue">
<adjust-colorConform enabled="1" autoOrManual="manual" conformType="conformNone" peakNitsOfPQSource="1000" peakNitsOfSDRToPQSource="203"/>
<marker start="239239/30000s" duration="1001/30000s" value="Marker 3"/>
<marker start="287287/30000s" duration="1001/30000s" value="Marker 4"/>
</asset-clip>
<ref-clip ref="r3" offset="209209/30000s" name="29.97_CC" duration="108108/30000s">
<adjust-colorConform enabled="1" autoOrManual="manual" conformType="conformNone" peakNitsOfPQSource="1000" peakNitsOfSDRToPQSource="203"/>
</ref-clip>
<asset-clip ref="r2" offset="317317/30000s" name="TestVideo2" start="427683/30000s" duration="100100/30000s" tcFormat="NDF" audioRole="dialogue">
<asset-clip ref="r2" offset="317317/30000s" name="TestVideo" start="427683/30000s" duration="100100/30000s" tcFormat="NDF" audioRole="dialogue">
<timeMap>
<timept time="0s" value="0s" interp="smooth2"/>
<timept time="4436211/90000s" value="9168210/90000s" interp="smooth2"/>
Expand All @@ -64,21 +64,21 @@
<marker start="154573/10000s" duration="1001/30000s" value="Marker 8"/>
<marker start="501757/30000s" duration="1001/30000s" value="Marker 9"/>
</asset-clip>
<asset-clip ref="r2" offset="417417/30000s" name="TestVideo2" start="1301300/30000s" duration="137137/30000s" tcFormat="NDF" audioRole="dialogue">
<asset-clip ref="r2" offset="417417/30000s" name="TestVideo" start="1301300/30000s" duration="137137/30000s" tcFormat="NDF" audioRole="dialogue">
<adjust-colorConform enabled="1" autoOrManual="manual" conformType="conformNone" peakNitsOfPQSource="1000" peakNitsOfSDRToPQSource="203"/>
<spine lane="1" offset="53053/1200s">
<asset-clip ref="r2" offset="0s" name="TestVideo2" start="1690689/30000s" duration="6006/30000s" tcFormat="NDF" audioRole="dialogue">
<asset-clip ref="r2" offset="0s" name="TestVideo" start="1690689/30000s" duration="6006/30000s" tcFormat="NDF" audioRole="dialogue">
<adjust-colorConform enabled="1" autoOrManual="manual" conformType="conformNone" peakNitsOfPQSource="1000" peakNitsOfSDRToPQSource="203"/>
<marker start="1692691/30000s" duration="1001/30000s" value="Marker 13"/>
<filter-video ref="r4" name="50s TV"/>
</asset-clip>
<asset-clip ref="r2" offset="6006/30000s" name="TestVideo2" start="1701700/30000s" duration="9009/30000s" tcFormat="NDF" audioRole="dialogue">
<asset-clip ref="r2" offset="6006/30000s" name="TestVideo" start="1701700/30000s" duration="9009/30000s" tcFormat="NDF" audioRole="dialogue">
<adjust-colorConform enabled="1" autoOrManual="manual" conformType="conformNone" peakNitsOfPQSource="1000" peakNitsOfSDRToPQSource="203"/>
<marker start="851851/15000s" duration="1001/30000s" value="Marker 14"/>
<marker start="853853/15000s" duration="1001/30000s" value="Marker 15"/>
<filter-video ref="r5" name="Aged Paper"/>
</asset-clip>
<asset-clip ref="r2" offset="15015/30000s" name="TestVideo2" start="1720719/30000s" duration="9009/30000s" tcFormat="NDF" audioRole="dialogue">
<asset-clip ref="r2" offset="15015/30000s" name="TestVideo" start="1720719/30000s" duration="9009/30000s" tcFormat="NDF" audioRole="dialogue">
<adjust-colorConform enabled="1" autoOrManual="manual" conformType="conformNone" peakNitsOfPQSource="1000" peakNitsOfSDRToPQSource="203"/>
<filter-video ref="r6" name="Color Correction">
<!-- info-asc-cdl: slope="1.68526 1.78081 1.38709" offset="-0.218905 -0.192776 -0.0995025" power="0.989591 0.762099 0.917057" -->
Expand All @@ -95,7 +95,7 @@
<param name="Exposure Shadows" key="2011" value="0.420398"/>
</filter-video>
</asset-clip>
<asset-clip ref="r2" offset="24024/30000s" name="TestVideo2" start="1739738/30000s" duration="8008/30000s" tcFormat="NDF" audioRole="dialogue">
<asset-clip ref="r2" offset="24024/30000s" name="TestVideo" start="1739738/30000s" duration="8008/30000s" tcFormat="NDF" audioRole="dialogue">
<adjust-colorConform enabled="1" autoOrManual="manual" conformType="conformNone" peakNitsOfPQSource="1000" peakNitsOfSDRToPQSource="203"/>
<marker start="871871/15000s" duration="1001/30000s" value="Marker 17"/>
<filter-video ref="r7" name="Background Squares"/>
Expand Down Expand Up @@ -125,7 +125,7 @@
<param name="Auto Adjustment" key="18" value="0"/>
</filter-video>
</asset-clip>
<asset-clip ref="r2" offset="554554/30000s" name="TestVideo2" start="1109174/30000s" duration="124124/30000s" tcFormat="NDF" audioRole="dialogue">
<asset-clip ref="r2" offset="554554/30000s" name="TestVideo" start="1109174/30000s" duration="124124/30000s" tcFormat="NDF" audioRole="dialogue">
<timeMap>
<timept time="0s" value="0s" interp="smooth2"/>
<timept time="4681557/90000s" value="9168210/90000s" interp="smooth2"/>
Expand All @@ -134,7 +134,7 @@
<marker start="76681/2000s" duration="1001/30000s" value="Marker 18"/>
<marker start="1206271/30000s" duration="1001/30000s" value="Marker 19"/>
</asset-clip>
<asset-clip ref="r2" offset="678678/30000s" name="TestVideo2" start="2676674/30000s" duration="209209/30000s" tcFormat="NDF" audioRole="dialogue">
<asset-clip ref="r2" offset="678678/30000s" name="TestVideo" start="2676674/30000s" duration="209209/30000s" tcFormat="NDF" audioRole="dialogue">
<adjust-colorConform enabled="1" autoOrManual="manual" conformType="conformNone" peakNitsOfPQSource="1000" peakNitsOfSDRToPQSource="203"/>
<ref-clip ref="r3" lane="1" offset="899899/10000s" name="29.97_CC" duration="61061/30000s">
<adjust-colorConform enabled="1" autoOrManual="manual" conformType="conformNone" peakNitsOfPQSource="1000" peakNitsOfSDRToPQSource="203"/>
Expand All @@ -149,4 +149,4 @@
</project>
</event>
</library>
</fcpxml>
</fcpxml>
Loading

0 comments on commit 2a97875

Please sign in to comment.