cli: forge push/status/sim — flatten+push looks over IP, split-CDL flag, live sim; verified end-to-end
This commit is contained in:
parent
5730fb53a2
commit
642af95d4e
1 changed files with 115 additions and 1 deletions
|
|
@ -1 +1,115 @@
|
|||
@main struct ForgeCLI { static func main() { print("forge") } }
|
||||
import ArgumentParser
|
||||
import Foundation
|
||||
import ForgeCamera
|
||||
import ForgeCameraARRI
|
||||
import ForgeGrade
|
||||
import ForgeColor
|
||||
import ForgeSim
|
||||
|
||||
@main
|
||||
struct Forge: AsyncParsableCommand {
|
||||
static let configuration = CommandConfiguration(
|
||||
commandName: "forge",
|
||||
abstract: "On-set live grading: push looks to cameras over IP.",
|
||||
subcommands: [Push.self, Status.self, Sim.self])
|
||||
}
|
||||
|
||||
struct CameraOptions: ParsableArguments {
|
||||
@Option(name: .long, help: "Camera IP/host")
|
||||
var host: String
|
||||
|
||||
@Option(name: .long, help: "Camera port")
|
||||
var port: Int
|
||||
|
||||
@Option(name: .long, help: "Driver: arri")
|
||||
var driver: String = "arri"
|
||||
|
||||
func makeDriver() throws -> any CameraDriver {
|
||||
switch driver {
|
||||
case "arri":
|
||||
return ArriDriver(host: host, port: port)
|
||||
default:
|
||||
throw ValidationError("unknown driver '\(driver)' (supported: arri)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Push: AsyncParsableCommand {
|
||||
static let configuration = CommandConfiguration(abstract: "Flatten a grade stack and push to camera.")
|
||||
|
||||
@OptionGroup var camera: CameraOptions
|
||||
|
||||
@Option(name: .long, help: "Grade snapshot JSON file")
|
||||
var look: String
|
||||
|
||||
@Option(name: .long, help: "LUT lattice size")
|
||||
var lattice: Int = 33
|
||||
|
||||
@Flag(name: .long, help: "Keep leading CDL native (camera trim), bake remainder")
|
||||
var splitCdl = false
|
||||
|
||||
func run() async throws {
|
||||
let data = try Data(contentsOf: URL(fileURLWithPath: look))
|
||||
let stack = try GradeSnapshot.decode(data)
|
||||
let driver = try camera.makeDriver()
|
||||
|
||||
let flattened = Flattener.flatten(
|
||||
stack: stack,
|
||||
latticeSize: lattice,
|
||||
splitLeadingCDL: splitCdl && driver.capabilities.supportsNativeCDL)
|
||||
|
||||
try await driver.connect()
|
||||
try await driver.push(look: flattened)
|
||||
await driver.disconnect()
|
||||
print("pushed: lattice=\(lattice) cdl=\(flattened.cdl != nil ? "native" : "baked") -> \(camera.host):\(camera.port)")
|
||||
}
|
||||
}
|
||||
|
||||
struct Status: AsyncParsableCommand {
|
||||
static let configuration = CommandConfiguration(abstract: "Print camera connection/rec state and metadata.")
|
||||
|
||||
@OptionGroup var camera: CameraOptions
|
||||
|
||||
func run() async throws {
|
||||
let driver = try camera.makeDriver()
|
||||
var iterator = driver.state.makeAsyncIterator()
|
||||
try await driver.connect()
|
||||
|
||||
// First polled state carries metadata.
|
||||
var reported = false
|
||||
for _ in 0..<10 {
|
||||
guard let s = await iterator.next() else { break }
|
||||
if let md = s.metadata {
|
||||
print("connection: \(s.connection.rawValue)")
|
||||
print("recording: \(s.isRecording)")
|
||||
print("clip: \(s.clipName ?? "-")")
|
||||
print("ei: \(md.exposureIndex.map(String.init) ?? "-") wb: \(md.whiteBalance.map(String.init) ?? "-") tint: \(md.tint.map(String.init) ?? "-")")
|
||||
print("fps: \(md.fps.map { String($0) } ?? "-") tc: \(md.timecode ?? "-")")
|
||||
reported = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !reported {
|
||||
print("connection: connected (no metadata yet)")
|
||||
}
|
||||
await driver.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
struct Sim: AsyncParsableCommand {
|
||||
static let configuration = CommandConfiguration(abstract: "Run an ARRI-style camera simulator.")
|
||||
|
||||
@Option(name: .long, help: "Listen port")
|
||||
var port: Int = 8035
|
||||
|
||||
func run() async throws {
|
||||
let sim = ArriSimulator()
|
||||
try await sim.start(port: port)
|
||||
let bound = await sim.boundPort ?? port
|
||||
print("arri sim listening on 127.0.0.1:\(bound)")
|
||||
// Run until killed.
|
||||
while true {
|
||||
try await Task.sleep(for: .seconds(3600))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue