import Foundation /// Non-drop-frame timecode. Frame count + fps. public struct Timecode: Sendable, Equatable, CustomStringConvertible { public var frames: Int public var fps: Int public init(frames: Int, fps: Int) { self.frames = frames self.fps = fps } /// Parse HH:MM:SS:FF. public static func parse(_ s: String, fps: Int) -> Timecode? { let parts = s.split(separator: ":") guard parts.count == 4, let h = Int(parts[0]), let m = Int(parts[1]), let sec = Int(parts[2]), let f = Int(parts[3]), m < 60, sec < 60, f < fps else { return nil } return Timecode(frames: ((h * 3600 + m * 60 + sec) * fps) + f, fps: fps) } public var description: String { let totalSeconds = frames / fps let f = frames % fps let h = totalSeconds / 3600 let m = (totalSeconds % 3600) / 60 let s = totalSeconds % 60 return String(format: "%02d:%02d:%02d:%02d", h, m, s, f) } public static func + (l: Timecode, r: Timecode) -> Timecode { Timecode(frames: l.frames + r.frames, fps: l.fps) } public static func - (l: Timecode, r: Timecode) -> Timecode { Timecode(frames: max(0, l.frames - r.frames), fps: l.fps) } }