Flixy heißt ab hier Halo
Reine Umbenennung, kein Verhalten geändert — bewusst als eigener Commit, damit „umbenannt" und „umgebaut" im Nachhinein auseinanderzuhalten sind. 712 mal `Flixy`, 778 mal `flixy`, 7 mal `FLIXY` in 169 Dateien, dazu 25 Pfade. Ziele: `Halo`, `HaloTV`, `HaloTopShelf`, `HaloUITests`, `HaloKit` mit `HaloDesign`, `HaloDownloads`, `HaloPlayback` und `HaloProbe`. `MediaKit`, `JellyfinKit` und `DownloadWidget` tragen den Namen nicht und bleiben. `docs/superpowers/specs/` ist ausgenommen. Datierte Entwurfsdokumente sind ein Protokoll getroffener Entscheidungen; sie nachträglich umzuschreiben hiesse, die Vergangenheit zu glätten. **Die Bundle-ID wandert mit** — ausdrücklich so gewollt, mit bekanntem Preis: `com.scarriffleservices.halo` ist für iOS eine *andere* App. Die installierte Fassung bleibt daneben stehen, mit Downloads und Anmeldung in ihrem Container; Halo startet leer. Einmal neu anmelden, heruntergeladene Folgen einmal neu laden. Dasselbe gilt für die App-Gruppe, die sich TV-App und Top Shelf teilen. **Eine Folge davon, die beim Entscheiden noch nicht auf dem Tisch lag:** der Aufräumschritt aus dem vorigen Commit kommt an die alten Plex-Token nicht mehr heran. Der Schlüsselbund grenzt nach Team und Bundle-Kennung ab — was unter `…flixy` liegt, ist für `…halo` unerreichbar. Erledigt wird es trotzdem, nur anders: **die alte App löschen.** iOS räumt deren Schlüsselbund-Einträge beim Deinstallieren mit ab. Der Code bleibt stehen, weil er für jeden Stand vor dieser Umbenennung richtig ist und im Jahr einen Startvergleich kostet. Die Projektdatei ist aus `project.yml` neu erzeugt, der alte Ordner gelöscht statt umbenannt — sie steht ohnehin nicht im Repo. Das Verzeichnis des Repos selbst heißt weiter `Flixy`; das umzubenennen ist ein Handgriff ausserhalb des Projekts. 368 Tests grün. iOS und tvOS bauen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
2fcf6821c8
commit
4b9a1e7b67
@@ -0,0 +1,135 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user