SPB Git

spb/focale Public

Swift 100%
3.9 KB · 126 lines swift
Raw Blame History
1//2//  CaptureContext.swift3//  Focale4//5//  Author: Simon-Pierre Boucher6//  Contact: contact@spboucher.ai7//8//  The differentiating piece of the whole project (CLAUDE.md §4):9//  everything we know about a photo at the moment the shutter fires,10//  captured for free, with zero inference cost.11//1213import Foundation1415/// Context recorded at shutter time. Written both to the local index and16/// into the asset's own metadata, so the information survives an uninstall.17struct CaptureContext: Codable, Sendable {18    var recipe: Recipe.ID?          // active preset19    var project: Project.ID?        // project declared by the user20    var projectName: String?        // denormalized for metadata embedding21    var subjectHint: String?        // typed or dictated before/after the shot22    var settings: ManualSettings    // ISO, shutter, lens, focus23    var scene: SceneSignal          // luminance, motion, distance (LiDAR if available)24    var burstRole: BurstRole?       // selection within a burst25    var place: PlaceRef?            // place, never raw coordinates2627    /// Compact JSON used when embedding into asset metadata (EXIF UserComment).28    func metadataJSON() -> String? {29        let encoder = JSONEncoder()30        encoder.outputFormatting = [.sortedKeys, .withoutEscapingSlashes]31        guard let data = try? encoder.encode(self) else { return nil }32        return String(data: data, encoding: .utf8)33    }3435    static func decode(fromMetadataJSON json: String) -> CaptureContext? {36        guard let data = json.data(using: .utf8) else { return nil }37        return try? JSONDecoder().decode(CaptureContext.self, from: data)38    }39}4041/// Snapshot of the manual settings applied when the photo was taken.42struct ManualSettings: Codable, Sendable, Hashable {43    var iso: Float?                 // nil = auto44    var shutterSeconds: Double?     // nil = auto45    var lensPosition: Float?        // 0...1, nil = autofocus46    var whiteBalanceKelvin: Float?  // nil = auto WB47    var exposureBias: Float48    var lens: LensKind49    var zoomFactor: Double50    var format: CaptureFormat51    /// Optional so recipes saved before this field existed still decode.52    /// nil = auto.53    var flashMode: FlashMode?5455    static let automatic = ManualSettings(56        iso: nil, shutterSeconds: nil, lensPosition: nil,57        whiteBalanceKelvin: nil, exposureBias: 0,58        lens: .wide, zoomFactor: 1.0, format: .heic, flashMode: nil59    )60}6162enum FlashMode: String, Codable, Sendable, CaseIterable {63    case auto, on, off6465    var displayName: String {66        switch self {67        case .auto: "Flash auto"68        case .on: "Flash activé"69        case .off: "Flash désactivé"70        }71    }7273    var symbolName: String {74        switch self {75        case .auto: "bolt.badge.a"76        case .on: "bolt.fill"77        case .off: "bolt.slash.fill"78        }79    }8081    var next: FlashMode {82        switch self {83        case .auto: .on84        case .on: .off85        case .off: .auto86        }87    }88}8990enum LensKind: String, Codable, Sendable, CaseIterable {91    case ultraWide, wide, telephoto9293    /// User-facing name, in French (code stays in English).94    var displayName: String {95        switch self {96        case .ultraWide: "Ultra grand-angle"97        case .wide: "Grand-angle"98        case .telephoto: "Téléobjectif"99        }100    }101}102103enum CaptureFormat: String, Codable, Sendable, CaseIterable {104    case heic105    case proRAW106107    var displayName: String {108        switch self {109        case .heic: "HEIC"110        case .proRAW: "ProRAW"111        }112    }113}114115enum BurstRole: String, Codable, Sendable {116    case selected   // user's pick within the burst117    case member118}119120/// A place, deliberately coarse. Never raw coordinates (CLAUDE.md §4).121struct PlaceRef: Codable, Sendable {122    var name: String?         // "Chalet", "Garage Marcel"123    var locality: String?     // "Québec"124    var countryCode: String?  // "CA"125}126