import Foundation /// Die Auflösung als Stufe, nicht als Zahl. /// /// **Warum es das gibt.** Bisher stand auf der Detailseite schlicht `1080` — /// die Zeichenkette, die Plex liefert, in Großbuchstaben. Weder eine Zahl mit /// Bedeutung noch ein Name, den jemand benutzt. /// /// **Warum die Breite zählt und nicht die Höhe.** Ein Kinofilm liegt in 2,39:1 /// vor und misst dann 1920 × 804. Nach der Höhe wäre das „720p"; nach der /// Breite ist es, was es ist. Genau so sahen die Fassungen aus, bei denen die /// alte Einstufung danebenlag — und zwar ausgerechnet bei den großen Filmen. /// Die Höhe bleibt als Notnagel für Server, die keine Breite melden. public enum VideoResolution: Sendable, Equatable, Comparable, CaseIterable { case sd case hd case fullHD case qhd case uhd4K case uhd8K /// Wie der Name in der Oberfläche steht. /// /// Nicht übersetzt: „Full HD" und „4K" heißen in jeder Sprache so, und /// eine Übersetzungstabelle für vier feste Wörter wäre eine Fehlerquelle /// ohne Nutzen. public var name: String { switch self { case .sd: "SD" case .hd: "HD" case .fullHD: "Full HD" case .qhd: "QHD" case .uhd4K: "4K" case .uhd8K: "8K" } } /// Die technische Schreibweise — für Stufen, die man wählt, statt für /// Material, das man beschreibt. public var technicalName: String { switch self { case .sd: "480p" case .hd: "720p" case .fullHD: "1080p" case .qhd: "1440p" case .uhd4K: "2160p" case .uhd8K: "4320p" } } /// Die Bildhöhe, die diese Stufe üblicherweise hat. /// /// Für Entscheidungen, die eine Zahl brauchen — etwa welche /// Download-Stufen bei diesem Material überhaupt Sinn ergeben. public var nominalHeight: Int { switch self { case .sd: 480 case .hd: 720 case .fullHD: 1_080 case .qhd: 1_440 case .uhd4K: 2_160 case .uhd8K: 4_320 } } /// Stuft eine Fassung ein. /// /// In dieser Reihenfolge: Breite, dann Höhe, dann der Name des Servers. /// Gibt `nil` zurück, wenn nichts davon etwas hergibt — dann ist eine /// erfundene Stufe schlechter als gar keine Angabe. public init?(width: Int?, height: Int?, serverLabel: String? = nil) { if let width, width > 0 { self = Self.fromWidth(width) } else if let height, height > 0 { self = Self.fromHeight(height) } else if let stufe = Self.fromServerLabel(serverLabel) { self = stufe } else { return nil } } /// Der verlässliche Weg. Die Breite ist vom Bildformat unabhängig. private static func fromWidth(_ width: Int) -> VideoResolution { switch width { case 7_000...: .uhd8K case 3_000..<7_000: .uhd4K // 3840 und 4096 case 2_000..<3_000: .qhd // 2560 case 1_700..<2_000: .fullHD // 1920 case 1_100..<1_700: .hd // 1280 default: .sd } } /// Der Notnagel. /// /// Aus der Höhe allein ist ein breitwandiges 1080p (804 Zeilen) nicht von /// einem vollformatigen 720p zu unterscheiden. Das ist keine Nachlässigkeit /// in den Grenzen, sondern liegt an der Angabe: die Information fehlt /// schlicht. Deshalb kommt die Breite zuerst. private static func fromHeight(_ height: Int) -> VideoResolution { switch height { case 2_800...: .uhd8K case 1_500..<2_800: .uhd4K case 1_250..<1_500: .qhd case 850..<1_250: .fullHD case 650..<850: .hd default: .sd } } /// Was Plex hinschreibt: „4k", „1080", „sd". private static func fromServerLabel(_ raw: String?) -> VideoResolution? { guard let raw = raw?.lowercased().trimmingCharacters(in: .whitespaces), !raw.isEmpty else { return nil } switch raw { case "8k", "4320", "4320p": return .uhd8K case "4k", "uhd", "2160", "2160p": return .uhd4K case "qhd", "1440", "1440p": return .qhd case "fhd", "1080", "1080p": return .fullHD case "hd", "720", "720p": return .hd case "sd", "576", "576p", "480", "480p", "360", "360p", "240", "240p": return .sd default: break } // „1080i" und Ähnliches: die Ziffern vorne deuten, den Rest verwerfen. let ziffern = raw.prefix { $0.isNumber } guard let zahl = Int(ziffern), zahl > 0 else { return nil } // Über tausend ist es eine Breite, darunter eine Höhe — andersherum // gibt es keine sinnvolle Lesart. return zahl >= 2_000 ? fromWidth(zahl) : fromHeight(zahl) } }