You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
142 lines
5.6 KiB
142 lines
5.6 KiB
import CoreMedia
|
|
import XCTest
|
|
@testable import DropTwoCore
|
|
|
|
final class CropEngineTests: XCTestCase {
|
|
private var tempDirectory: URL!
|
|
|
|
override func setUpWithError() throws {
|
|
tempDirectory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
|
|
try FileManager.default.createDirectory(at: tempDirectory, withIntermediateDirectories: true)
|
|
}
|
|
|
|
override func tearDownWithError() throws {
|
|
try? FileManager.default.removeItem(at: tempDirectory)
|
|
}
|
|
|
|
// MARK: - Geometrie (rein arithmetisch, kein Video nötig)
|
|
|
|
func testTargetSizeMatchesReadmeExamples() {
|
|
XCTAssertEqual(
|
|
CropEngine.targetSize(forSourceSize: CGSize(width: 1080, height: 1920)),
|
|
CGSize(width: 1080, height: 1350)
|
|
)
|
|
XCTAssertEqual(
|
|
CropEngine.targetSize(forSourceSize: CGSize(width: 2160, height: 3840)),
|
|
CGSize(width: 2160, height: 2700)
|
|
)
|
|
}
|
|
|
|
func testTargetSizeRoundsHeightToEvenNumber() {
|
|
let size = CropEngine.targetSize(forSourceSize: CGSize(width: 217, height: 386))
|
|
|
|
XCTAssertEqual(size.width, 217)
|
|
XCTAssertEqual(size.height.truncatingRemainder(dividingBy: 2), 0)
|
|
}
|
|
|
|
// MARK: - End-to-End gegen einen synthetischen Clip
|
|
|
|
func testCropsToTargetSizeAndKeepsAudioAndDuration() async throws {
|
|
let clip = try TestVideoFactory.makeNineBySixteenClip(in: tempDirectory)
|
|
let destination = tempDirectory.appendingPathComponent("out.mp4")
|
|
|
|
try await CropEngine.crop(sourceURL: clip.url, destinationURL: destination)
|
|
|
|
let resultInfo = try await VideoInspector.inspect(destination)
|
|
let expectedSize = CropEngine.targetSize(forSourceSize: clip.size)
|
|
XCTAssertEqual(resultInfo.pixelSize, expectedSize)
|
|
XCTAssertEqual(resultInfo.codec, .h264)
|
|
XCTAssertTrue(resultInfo.hasAudioTrack)
|
|
XCTAssertEqual(CMTimeGetSeconds(resultInfo.duration), clip.seconds, accuracy: 0.3)
|
|
}
|
|
|
|
func testCropsWithoutAudioTrack() async throws {
|
|
let clip = try TestVideoFactory.makeNineBySixteenClip(includeAudio: false, in: tempDirectory)
|
|
let destination = tempDirectory.appendingPathComponent("out.mp4")
|
|
|
|
try await CropEngine.crop(sourceURL: clip.url, destinationURL: destination)
|
|
|
|
let resultInfo = try await VideoInspector.inspect(destination)
|
|
XCTAssertFalse(resultInfo.hasAudioTrack)
|
|
}
|
|
|
|
func testReportsProgress() async throws {
|
|
let clip = try TestVideoFactory.makeNineBySixteenClip(in: tempDirectory)
|
|
let destination = tempDirectory.appendingPathComponent("out.mp4")
|
|
|
|
var reportedValues: [Double] = []
|
|
try await CropEngine.crop(sourceURL: clip.url, destinationURL: destination) { value in
|
|
reportedValues.append(value)
|
|
}
|
|
|
|
XCTAssertFalse(reportedValues.isEmpty)
|
|
XCTAssertEqual(reportedValues.last ?? 0, 1.0, accuracy: 0.15)
|
|
}
|
|
|
|
// MARK: - Ablehnungen
|
|
|
|
func testRejectsNonNineBySixteenSource() async throws {
|
|
let clip = try TestVideoFactory.makeClip(size: CGSize(width: 400, height: 400), in: tempDirectory)
|
|
let destination = tempDirectory.appendingPathComponent("out.mp4")
|
|
|
|
do {
|
|
try await CropEngine.crop(sourceURL: clip.url, destinationURL: destination)
|
|
XCTFail("Erwartete Ablehnung für eine Nicht-9:16-Quelle")
|
|
} catch CropEngineError.sourceNotNineBySixteen {
|
|
// erwartet
|
|
}
|
|
}
|
|
|
|
func testRejectsExistingDestinationWithoutOverwrite() async throws {
|
|
let clip = try TestVideoFactory.makeNineBySixteenClip(in: tempDirectory)
|
|
let destination = tempDirectory.appendingPathComponent("out.mp4")
|
|
FileManager.default.createFile(atPath: destination.path, contents: Data())
|
|
|
|
do {
|
|
try await CropEngine.crop(sourceURL: clip.url, destinationURL: destination)
|
|
XCTFail("Erwartete Ablehnung wegen bestehender Zieldatei")
|
|
} catch CropEngineError.destinationAlreadyExists {
|
|
// erwartet
|
|
}
|
|
}
|
|
|
|
func testOverwritesExistingDestinationWhenExplicitlyAllowed() async throws {
|
|
let clip = try TestVideoFactory.makeNineBySixteenClip(in: tempDirectory)
|
|
let destination = tempDirectory.appendingPathComponent("out.mp4")
|
|
FileManager.default.createFile(atPath: destination.path, contents: Data("platzhalter".utf8))
|
|
|
|
try await CropEngine.crop(
|
|
sourceURL: clip.url,
|
|
destinationURL: destination,
|
|
options: .init(overwrite: true)
|
|
)
|
|
|
|
let resultInfo = try await VideoInspector.inspect(destination)
|
|
XCTAssertEqual(resultInfo.pixelSize, CropEngine.targetSize(forSourceSize: clip.size))
|
|
}
|
|
|
|
func testRejectsUnsupportedContainer() async throws {
|
|
let clip = try TestVideoFactory.makeNineBySixteenClip(in: tempDirectory)
|
|
let destination = tempDirectory.appendingPathComponent("out.mxf")
|
|
|
|
do {
|
|
try await CropEngine.crop(sourceURL: clip.url, destinationURL: destination)
|
|
XCTFail("Erwartete Ablehnung für ein MXF-Ziel")
|
|
} catch CropEngineError.unsupportedContainer {
|
|
// erwartet
|
|
}
|
|
}
|
|
|
|
func testRejectsMovAsDestinationBecauseTargetIsFixedToMp4() async throws {
|
|
let clip = try TestVideoFactory.makeNineBySixteenClip(in: tempDirectory)
|
|
let destination = tempDirectory.appendingPathComponent("out.mov")
|
|
|
|
do {
|
|
try await CropEngine.crop(sourceURL: clip.url, destinationURL: destination)
|
|
XCTFail("Erwartete Ablehnung für ein MOV-Ziel — Zielformat ist fest auf MP4 gesetzt")
|
|
} catch CropEngineError.unsupportedContainer {
|
|
// erwartet
|
|
}
|
|
}
|
|
}
|