100 lines
4.7 KiB
Swift
100 lines
4.7 KiB
Swift
|
|
import XCTest
|
||
|
|
import Foundation
|
||
|
|
import ForgeColor
|
||
|
|
import ForgeGrade
|
||
|
|
import ForgeLibrary
|
||
|
|
@testable import ForgeExport
|
||
|
|
|
||
|
|
final class PostExportTests: XCTestCase {
|
||
|
|
|
||
|
|
func makeShots() throws -> [Shot] {
|
||
|
|
let dayID = UUID()
|
||
|
|
let cdl = CDL(slope: SIMD3(1.2, 1.0, 0.85), offset: SIMD3(0.01, -0.02, 0), power: SIMD3(0.95, 1, 1.1), saturation: 1.25)
|
||
|
|
let snapshot = try GradeSnapshot.encode(GradeStack(nodes: [GradeNode(kind: .cdl(cdl))]))
|
||
|
|
return [
|
||
|
|
Shot(dayID: dayID, clipName: "A001C001", cameraSlot: "A-Cam",
|
||
|
|
timecodeIn: "10:00:00:00", timecodeOut: "10:00:30:00",
|
||
|
|
exposureIndex: 800, whiteBalance: 5600, tint: 0, fps: 24,
|
||
|
|
gradeSnapshot: snapshot),
|
||
|
|
Shot(dayID: dayID, clipName: "A001C002", cameraSlot: "A-Cam",
|
||
|
|
timecodeIn: "10:05:00:00", timecodeOut: "10:06:00:00",
|
||
|
|
exposureIndex: 1280, whiteBalance: 3200, tint: -2, fps: 24,
|
||
|
|
gradeSnapshot: snapshot),
|
||
|
|
]
|
||
|
|
}
|
||
|
|
|
||
|
|
// ALE: header block + tab-delimited columns.
|
||
|
|
func testALEGolden() throws {
|
||
|
|
let shots = try makeShots()
|
||
|
|
let ale = PostExport.writeALE(shots: shots, fps: 24)
|
||
|
|
let lines = ale.split(separator: "\n", omittingEmptySubsequences: false).map(String.init)
|
||
|
|
XCTAssertEqual(lines[0], "Heading")
|
||
|
|
XCTAssertTrue(ale.contains("FIELD_DELIM\tTABS"))
|
||
|
|
XCTAssertTrue(ale.contains("FPS\t24"))
|
||
|
|
XCTAssertTrue(ale.contains("Column"))
|
||
|
|
// Column row includes standard + metadata fields.
|
||
|
|
let columnLine = lines[lines.firstIndex(of: "Column")! + 1]
|
||
|
|
XCTAssertEqual(columnLine, "Name\tStart\tEnd\tCamera\tASC_SOP\tASC_SAT\tEI\tWB\tTint\tFPS")
|
||
|
|
// Data rows under "Data".
|
||
|
|
let dataIdx = lines.firstIndex(of: "Data")!
|
||
|
|
let row1 = lines[dataIdx + 1].split(separator: "\t").map(String.init)
|
||
|
|
XCTAssertEqual(row1[0], "A001C001")
|
||
|
|
XCTAssertEqual(row1[1], "10:00:00:00")
|
||
|
|
XCTAssertEqual(row1[2], "10:00:30:00")
|
||
|
|
XCTAssertEqual(row1[3], "A-Cam")
|
||
|
|
XCTAssertEqual(row1[4], "(1.200000 1.000000 0.850000)(0.010000 -0.020000 0.000000)(0.950000 1.000000 1.100000)")
|
||
|
|
XCTAssertEqual(row1[5], "1.250000")
|
||
|
|
XCTAssertEqual(row1[6], "800")
|
||
|
|
}
|
||
|
|
|
||
|
|
// CSV with proper escaping.
|
||
|
|
func testCSVEscaping() throws {
|
||
|
|
var shots = try makeShots()
|
||
|
|
shots[0].clipName = "clip,with\"quotes\""
|
||
|
|
let csv = PostExport.writeCSV(shots: shots)
|
||
|
|
let lines = csv.split(separator: "\n").map(String.init)
|
||
|
|
XCTAssertEqual(lines[0], "clip_name,camera_slot,tc_in,tc_out,ei,wb,tint,fps,asc_sop,asc_sat")
|
||
|
|
XCTAssertTrue(lines[1].hasPrefix("\"clip,with\"\"quotes\"\"\","))
|
||
|
|
}
|
||
|
|
|
||
|
|
// CMX3600 EDL with ASC_SOP/ASC_SAT comments.
|
||
|
|
func testEDLGolden() throws {
|
||
|
|
let shots = try makeShots()
|
||
|
|
let edl = PostExport.writeEDL(shots: shots, title: "DAY01", fps: 24)
|
||
|
|
let lines = edl.split(separator: "\n", omittingEmptySubsequences: false).map(String.init)
|
||
|
|
XCTAssertEqual(lines[0], "TITLE: DAY01")
|
||
|
|
XCTAssertEqual(lines[1], "FCM: NON-DROP FRAME")
|
||
|
|
// Event 001: record TC builds sequentially from 01:00:00:00.
|
||
|
|
XCTAssertTrue(edl.contains("001 A001C001 V C 10:00:00:00 10:00:30:00 01:00:00:00 01:00:30:00"))
|
||
|
|
XCTAssertTrue(edl.contains("* FROM CLIP NAME: A001C001"))
|
||
|
|
XCTAssertTrue(edl.contains("* ASC_SOP (1.200000 1.000000 0.850000)(0.010000 -0.020000 0.000000)(0.950000 1.000000 1.100000)"))
|
||
|
|
XCTAssertTrue(edl.contains("* ASC_SAT 1.250000"))
|
||
|
|
// Event 002 record-in continues from event 001 record-out.
|
||
|
|
XCTAssertTrue(edl.contains("002 A001C002 V C 10:05:00:00 10:06:00:00 01:00:30:00 01:01:30:00"))
|
||
|
|
}
|
||
|
|
|
||
|
|
// Shots without CDL node get no ASC lines but stay in EDL.
|
||
|
|
func testEDLNoGradeStillListed() throws {
|
||
|
|
var shots = try makeShots()
|
||
|
|
shots[1].gradeSnapshot = nil
|
||
|
|
let edl = PostExport.writeEDL(shots: shots, title: "D", fps: 24)
|
||
|
|
XCTAssertTrue(edl.contains("002 A001C002"))
|
||
|
|
// Only one ASC_SOP line (for shot 1).
|
||
|
|
let count = edl.components(separatedBy: "* ASC_SOP").count - 1
|
||
|
|
XCTAssertEqual(count, 1)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Timecode math: duration/offset arithmetic.
|
||
|
|
func testTimecodeMath() {
|
||
|
|
XCTAssertEqual(Timecode.parse("10:00:30:12", fps: 24)?.frames, ((10 * 3600 + 30) * 24) + 12)
|
||
|
|
let tc = Timecode(frames: 90000, fps: 24)
|
||
|
|
XCTAssertEqual(tc.description, "01:02:30:00")
|
||
|
|
// add
|
||
|
|
let sum = Timecode(frames: 10, fps: 24) + Timecode(frames: 20, fps: 24)
|
||
|
|
XCTAssertEqual(sum.frames, 30)
|
||
|
|
// invalid strings
|
||
|
|
XCTAssertNil(Timecode.parse("bogus", fps: 24))
|
||
|
|
XCTAssertNil(Timecode.parse("10:00:00", fps: 24))
|
||
|
|
}
|
||
|
|
}
|