diff --git a/Package.swift b/Package.swift index b1c3bb3..ea14e1e 100644 --- a/Package.swift +++ b/Package.swift @@ -40,6 +40,9 @@ let package = Package( .systemLibrary(name: "CSQLite", path: "Sources/CSQLite"), .target(name: "ForgeLogger", dependencies: ["ForgeCamera", "ForgeLibrary"]), .target(name: "ForgeExport", dependencies: ["ForgeLibrary", "ForgeGrade"]), + // Offload + proxy + .target(name: "ForgeOffload", dependencies: ["ForgeLibrary"]), + .target(name: "ForgeProxy", dependencies: ["ForgeLibrary", "ForgeGrade", "ForgeColor"]), // Panel + video seams .target(name: "ForgePanel", dependencies: [ "ForgeGrade", @@ -50,6 +53,7 @@ let package = Package( .executableTarget(name: "forge-cli", dependencies: [ "ForgeCamera", "ForgeCameraARRI", "ForgeCameraRED", "ForgeCameraSony", "ForgeSim", "ForgeLogger", "ForgeExport", "ForgePanel", "ForgeVideo", + "ForgeOffload", "ForgeProxy", .product(name: "ArgumentParser", package: "swift-argument-parser"), ]), // Tests @@ -62,6 +66,8 @@ let package = Package( .testTarget(name: "ForgeLibraryTests", dependencies: ["ForgeLibrary"]), .testTarget(name: "ForgeLoggerTests", dependencies: ["ForgeLogger", "ForgeSim"]), .testTarget(name: "ForgeExportTests", dependencies: ["ForgeExport"]), + .testTarget(name: "ForgeOffloadTests", dependencies: ["ForgeOffload"]), + .testTarget(name: "ForgeProxyTests", dependencies: ["ForgeProxy"]), .testTarget(name: "ForgePanelTests", dependencies: ["ForgePanel"]), .testTarget(name: "ForgeVideoTests", dependencies: ["ForgeVideo"]), ] diff --git a/Sources/ForgeOffload/ForgeOffload.swift b/Sources/ForgeOffload/ForgeOffload.swift new file mode 100644 index 0000000..35edea5 --- /dev/null +++ b/Sources/ForgeOffload/ForgeOffload.swift @@ -0,0 +1 @@ +// ForgeOffload diff --git a/Sources/ForgeProxy/ForgeProxy.swift b/Sources/ForgeProxy/ForgeProxy.swift new file mode 100644 index 0000000..b926314 --- /dev/null +++ b/Sources/ForgeProxy/ForgeProxy.swift @@ -0,0 +1 @@ +// ForgeProxy diff --git a/Tests/ForgeOffloadTests/PlaceholderTests.swift b/Tests/ForgeOffloadTests/PlaceholderTests.swift new file mode 100644 index 0000000..9f08b2b --- /dev/null +++ b/Tests/ForgeOffloadTests/PlaceholderTests.swift @@ -0,0 +1,5 @@ +import XCTest + +final class ForgeOffloadPlaceholderTests: XCTestCase { + func testPlaceholder() { XCTAssertTrue(true) } +} diff --git a/Tests/ForgeProxyTests/PlaceholderTests.swift b/Tests/ForgeProxyTests/PlaceholderTests.swift new file mode 100644 index 0000000..1f956d1 --- /dev/null +++ b/Tests/ForgeProxyTests/PlaceholderTests.swift @@ -0,0 +1,5 @@ +import XCTest + +final class ForgeProxyPlaceholderTests: XCTestCase { + func testPlaceholder() { XCTAssertTrue(true) } +} diff --git a/docs/plans/001-implementation-plan.md b/docs/plans/001-implementation-plan.md index df54fdf..366a00b 100644 --- a/docs/plans/001-implementation-plan.md +++ b/docs/plans/001-implementation-plan.md @@ -154,6 +154,37 @@ public protocol CameraDriver: Actor { ### Task 5.3: Debounced push pipeline - Tests: rapid param changes (simulated panel spins) → preview eval every change, camera push coalesced to ≤1 per 150ms, final state always pushed (trailing edge); per-camera independent debounce. +## Phase 7 — Offload + verification + +### Task 7.1: ForgeOffload — hashing +- Tests: xxHash64 + MD5 known-vector tests; streaming hash of large file matches whole-file hash; chunked reader. +- Impl: xxHash64 (pure Swift, spec vectors), MD5 via system, `HashingReader`. + +### Task 7.2: ForgeOffload — copy engine +- Tests: source tree → 2 destinations, byte-identical, hash-verified via post-copy re-read; interrupted copy resumes (partial file detected, re-copied); collision policy (exists+same hash = skip, exists+diff hash = error). +- Impl: `OffloadJob` (source, [destinations]), sequential copy w/ streaming hash, verify pass re-reads destination. + +### Task 7.3: ForgeOffload — MHL v2 manifest +- Tests: golden-file MHL XML (ASC MHL spec shape: hashes, paths, sizes, creator info); manifest verifies against tree; tamper detection (flipped byte → verify fail). +- Impl: MHL writer + verifier. + +### Task 7.4: Offload reports + library link +- Tests: HTML/CSV report golden; offloaded clip matched to shot record by clip name. +- CLI: `forge offload --to --to `, `forge verify `. + +## Phase 8 — Proxy generation + +### Task 8.1: ForgeProxy — ffmpeg wrapper +- Tests: command-line assembly golden (ProRes/DNxHR/H.264 presets, scale, lut3d filter path quoting, TC+clipname drawtext burn-in); process runner against `true`/fake-ffmpeg script asserting argv; real ffmpeg smoke test if binary present (skip otherwise). +- Impl: `ProxyPreset`, `FFmpegCommandBuilder`, `ProxyJob` queue (serial v1). + +### Task 8.2: Graded proxies +- Tests: shot grade snapshot → flattened .cube written to temp → lut3d filter arg references it; ungraded fallback when no snapshot. +- CLI: `forge proxy --day --preset prores-proxy --burn-look`. + +### Task 8.3: Watch folder +- Tests: new file in watched dir → job enqueued once (debounce partial writes via size-stable check); already-processed skip list. + ## Phase 6 — macOS (requires Mac hardware; out of scope on this box, scaffolded only) - SwiftUI app shell (3-zone layout per design), Metal preview renderer (same GradeStack→shader codegen or LUT texture sampling), DeckLink SDK C++ bridge implementing FrameSource/Sink, Tangent Hub local connection, real-camera path verification (ARRI paths const file, RED SDK agreement, Venice protocol docs).