1import Foundation
2import AVFoundation
3
4/// Headless pipeline check: `sequencer --selftest <mediafile>`.
5/// Verifies probe on the given file, then runs the full filmstrip/proxy
6/// pipeline on a short synthetic clip, and prints sample Fusion Lua.
7func runSelftest(path: String) {
8 func spin(until done: () -> Bool, timeout: TimeInterval) -> Bool {
9 let deadline = Date().addingTimeInterval(timeout)
10 while !done() {
11 if Date() > deadline { return false }
12 RunLoop.main.run(until: Date().addingTimeInterval(0.05))
13 }
14 return true
15 }
16
17 print("== Sequencer selftest ==")
18 print("ffmpeg: \(MediaPipeline.findExecutable("ffmpeg") ?? "NOT FOUND")")
19 print("ffprobe: \(MediaPipeline.findExecutable("ffprobe") ?? "NOT FOUND")")
20 print("cache: \(MediaPipeline.shared.cacheRoot.path)")
21
22 // 1. Probe the real file.
23 print("\n-- probe: \(path)")
24 var probed: MediaItem?
25 var probeDone = false
26 MediaPipeline.shared.importFile(URL(fileURLWithPath: path)) { item in
27 probed = item
28 probeDone = true
29 }
30 guard spin(until: { probeDone }, timeout: 60) else { print("FAIL: probe timed out"); return }
31 if let m = probed {
32 print(String(format: "OK: %.1fs %dx%d @ %.2ffps audio=%@ key=%@",
33 m.duration, m.width, m.height, m.fps, m.hasAudio ? "yes" : "no", m.cacheKey))
34 } else {
35 print("FAIL: could not probe \(path)")
36 }
37
38 // 2. Full pipeline on a short synthetic clip.
39 print("\n-- pipeline on 4s synthetic clip")
40 let tmpDir = FileManager.default.temporaryDirectory
41 .appendingPathComponent("sequencer-selftest", isDirectory: true)
42 try? FileManager.default.createDirectory(at: tmpDir, withIntermediateDirectories: true)
43 let testClip = tmpDir.appendingPathComponent("test.mov")
44 if let ffmpeg = MediaPipeline.findExecutable("ffmpeg") {
45 let p = Process()
46 p.executableURL = URL(fileURLWithPath: ffmpeg)
47 p.arguments = ["-y", "-f", "lavfi", "-i", "testsrc2=size=1280x720:rate=30:duration=4",
48 "-f", "lavfi", "-i", "sine=frequency=440:duration=4",
49 "-c:v", "h264_videotoolbox", "-c:a", "aac", "-shortest", testClip.path]
50 p.standardError = Pipe()
51 try? p.run()
52 p.waitUntilExit()
53 if p.terminationStatus != 0 { print("FAIL: could not generate synthetic clip") }
54 }
55
56 var testItem: MediaItem?
57 var testDone = false
58 MediaPipeline.shared.importFile(testClip) { item in
59 testItem = item
60 testDone = true
61 }
62 _ = spin(until: { testDone }, timeout: 30)
63 guard let item = testItem else { print("FAIL: probe of synthetic clip failed"); return }
64
65 DocumentContext.headless.chunks.want(media: item, sourceTime: 0)
66 _ = spin(until: {
67 MediaPipeline.shared.status(for: item).filmstripReady
68 && DocumentContext.headless.chunks.isCovered(media: item, sourceTime: 0)
69 }, timeout: 120)
70 print("filmstrip: \(MediaPipeline.shared.status(for: item).filmstripReady ? "OK" : "FAIL")")
71 print("chunk 0: \(DocumentContext.headless.chunks.isCovered(media: item, sourceTime: 0) ? "OK" : "FAIL")")
72
73 if let proxy = DocumentContext.headless.chunks.builtChunkURL(media: item, index: 0) {
74 let asset = AVURLAsset(url: proxy)
75 let sem = DispatchSemaphore(value: 0)
76 var playable = false
77 var codec = "?"
78 Task {
79 playable = (try? await asset.load(.isPlayable)) ?? false
80 if let track = try? await asset.loadTracks(withMediaType: .video).first,
81 let desc = try? await track.load(.formatDescriptions).first {
82 let sub = CMFormatDescriptionGetMediaSubType(desc)
83 codec = String(format: "%c%c%c%c",
84 (sub >> 24) & 255, (sub >> 16) & 255, (sub >> 8) & 255, sub & 255)
85 }
86 sem.signal()
87 }
88 _ = spin(until: { sem.wait(timeout: .now()) == .success }, timeout: 30)
89 print("proxy AVFoundation-playable: \(playable ? "OK" : "FAIL") (codec \(codec))")
90 }
91
92 // 3. Fusion Lua sample.
93 print("\n-- fusion lua for a 1.5s clip starting at 2.0s")
94 var model = ProjectModel()
95 model.fps = item.fps
96 model.media = [item]
97 model.tracks = [Track(hue: 0.5)]
98 let clip = Clip(mediaId: item.id, track: .video(0), start: 2.0, srcIn: 1.0, duration: 1.5)
99 model.clips = [clip]
100 print(FusionExport.loaderLua(for: [clip], project: model))
101 print("\n== selftest done ==")
102}