import Foundation /// Wie die Vorschaubilder eines Titels zusammengesetzt sind. /// /// **Dieselbe Rechnung wie im Server und im Web.** Dort heisst der Typ /// genauso; die Kachel zu einer Sekunde wird an drei Stellen ausgerechnet, /// und wenn eine davon abweicht, sieht man es nicht als Fehler — man hält /// die Vorschau für ein ungenaues Bild. /// /// **Die Kachelmasse stehen nicht fest.** Skaliert wird auf eine feste /// Breite, die Höhe folgt dem Seitenverhältnis des Films. Deshalb misst /// der Server sie und teilt sie mit, statt dass sie hier stünden. public struct Vorschauplan: Sendable, Equatable, Decodable { public let intervall: Double public let spalten: Int public let zeilen: Int public let kachelBreite: Double public let kachelHoehe: Double public let blaetter: Int public init( intervall: Double, spalten: Int, zeilen: Int, kachelBreite: Double, kachelHoehe: Double, blaetter: Int ) { self.intervall = intervall self.spalten = spalten self.zeilen = zeilen self.kachelBreite = kachelBreite self.kachelHoehe = kachelHoehe self.blaetter = blaetter } /// Wie eine Kachel adressiert wird: welches Blatt, und wo darin. public struct Stelle: Sendable, Equatable { public let blatt: Int public let spalte: Int public let zeile: Int } public var proBlatt: Int { spalten * zeilen } /// Welche Kachel die Stelle zeigt — oder nichts. /// /// **Hinter dem letzten Bild bleibt das letzte stehen.** Das Raster /// endet dort, wo ffmpeg aufgehört hat; eine Sekunde darüber hinaus — /// bei ungenauer Laufzeit durchaus möglich — soll die Vorschau nicht /// verschwinden. public func stelle(bei sekunde: Double) -> Stelle? { guard blaetter > 0, spalten > 0, zeilen > 0, intervall > 0 else { return nil } let nummer = min(Int(max(0, sekunde) / intervall), proBlatt * blaetter - 1) let rest = nummer % proBlatt return Stelle(blatt: nummer / proBlatt, spalte: rest % spalten, zeile: rest / spalten) } }