Halo heisst ab hier Flaro — die Marke stand im Weg
Der Upload zu App Store Connect scheiterte am Namen. „Halo" ist eine eingetragene Marke von Microsoft; im App Store heisst zwar keine App exakt so, aber reservierte und markenrechtlich belegte Namen tauchen in keiner Suche auf. Der einzige verlässliche Test war die Eingabe dort, und die ging daneben. **Der Name wurde diesmal vorher geprüft.** Zwanzig Kandidaten gegen den deutschen und amerikanischen App Store, auf exakte Treffer und auf ähnliche Namen. „Kavo" wäre dieselbe Wand gewesen — exakt belegt in beiden Stores, in Deutschland von KaVo Dental GmbH. „Flare" ist mit 42 ähnlichen Apps je Store der am dichtesten besetzte Name der ganzen Runde. **Flaro** ist in beiden frei und liest sich als eigenes Wort, nicht als Flare mit Tippfehler. Das bleibt eine Store-Suche und keine Markenrecherche — reservierte Namen ohne veröffentlichte App sieht sie nicht. Aber es ist mehr Prüfung, als beim letzten Mal stattfand. --- 2160 Textvorkommen in 273 Dateien, 49 Pfade. Betroffen ist diesmal auch der Server: `Flaro.Api`, `Flaro.Core`, `Flaro.Data`, `Flaro.Media`, `Flaro.Metadata`, `FlaroContext`, `FlaroPaths`, `flaro.db`, die Variablen `FLARO_DATA`, `FLARO_CACHE`, `FLARO_TRANSCODE`, `FLARO_WRITE_SIDECARS`, und das Image. **Vor dem Ersetzen geprüft**, ob „halo" irgendwo als Teil eines anderen Wortes steckt — und ob es im TMDB-Token vorkommt. Tat es nicht; ein blindes Ersetzen hätte den sonst zerlegt. `docs/superpowers/specs/` bleibt wie beim letzten Mal unangetastet: datierte Entwurfsdokumente sind ein Protokoll getroffener Entscheidungen. Das Server-Dokument hat stattdessen einen Nachtrag bekommen, damit es niemanden in die Irre führt. **Die Bundle-ID wandert mit**, zum zweiten Mal an einem Tag: `com.scarriffleservices.flaro`. Wer die vorige Fassung installiert hat, hat jetzt zwei Karteileichen auf dem Gerät — beide löschen, dann ist Ruhe. 308 Server-Tests grün, 368 im Swift-Paket, null Warnungen. iOS und tvOS bauen, das Image baut, und ein frischer Container läuft durch Einrichtung und Anmeldung. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
f98b995050
commit
a7cf6c5def
@@ -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