Untertitel lassen sich jetzt einstellen
Groesse in vier Stufen, Schrift und Hintergrund als fuenf feste Paare — Weiss, Weiss auf Schwarz, Schwarz auf Weiss, Gelb, Gelb auf Schwarz. Feste Paare statt zweier Regler, weil unabhaengige Achsen unlesbare Mischungen zulassen: Schwarz auf durchsichtig ueber einer Nachtszene ist eine gueltige Einstellung und eine wertlose. Die Uebersetzung nach AVFoundation ist eine Tabelle aus Schluesseln, bei der jeder Fehler lautlos ist — ein falscher Schluessel laesst den Initialisierer `nil` zurueckgeben, und dann gibt es gar keine Regel. Auf dem Bildschirm sieht „keine Regel\" genauso aus wie „Regel ohne Wirkung\". Zehn Tests decken das ab, darunter die drei Fallen: Alpha zuerst in der Farbe, Prozentwert statt Punktgroesse, und der Hintergrund auch fuer „durchsichtig\" ausdruecklich gesetzt statt weggelassen — weggelassen gilt sonst, was Jellyfins WebVTT selbst mitbringt, und das ist oft ein schwarzer Kasten. Als Protokollmethode, nicht als Feld an `PlaybackSource`: die wird einmal beim Oeffnen gelesen, und eine Einstellung, die erst beim naechsten Film wirkt, haelt jeder fuer kaputt. Angewandt wird an zwei Stellen — am laufenden Gegenstand und am frisch angelegten. Nur das erste zu tun hiesse, dass die naechste Folge wieder in der Vorgabe erscheint. VLC erbt die leere Vorgabe. Die Einstellungsseite sagt beides: dass es fuer den Standardplayer gilt, und dass eingebrannte Bilduntertitel von Blu-rays sich nicht gestalten lassen, weil es dort keinen Text gibt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
a1ff5cb7bb
commit
0815432efd
@@ -0,0 +1,97 @@
|
||||
import Foundation
|
||||
|
||||
/// Wie Untertitel aussehen sollen.
|
||||
///
|
||||
/// **Warum hier und nicht im App-Ziel.** Weil es prüfbar sein muss: die
|
||||
/// Übersetzung nach AVFoundation ist eine Tabelle aus Schlüsseln, bei der ein
|
||||
/// Tippfehler lautlos dazu führt, dass **gar nichts** gesetzt wird. Ein Test
|
||||
/// findet das, ein Blick auf den Bildschirm nicht — dort sieht „keine Regel"
|
||||
/// genauso aus wie „Regel ohne Wirkung".
|
||||
public struct SubtitleStyle: Sendable, Equatable {
|
||||
/// Eine Farbe, wie CoreMedia sie versteht: vier Anteile von 0 bis 1.
|
||||
public struct RGBA: Sendable, Equatable {
|
||||
public let red: Double
|
||||
public let green: Double
|
||||
public let blue: Double
|
||||
public let alpha: Double
|
||||
|
||||
public init(red: Double, green: Double, blue: Double, alpha: Double = 1) {
|
||||
self.red = red
|
||||
self.green = green
|
||||
self.blue = blue
|
||||
self.alpha = alpha
|
||||
}
|
||||
|
||||
static let white = RGBA(red: 1, green: 1, blue: 1)
|
||||
static let black = RGBA(red: 0, green: 0, blue: 0)
|
||||
static let yellow = RGBA(red: 1, green: 0.85, blue: 0.2)
|
||||
static let clear = RGBA(red: 0, green: 0, blue: 0, alpha: 0)
|
||||
/// Nicht ganz deckend: ein voll schwarzer Kasten wirkt wie ein Loch
|
||||
/// im Bild.
|
||||
static let box = RGBA(red: 0, green: 0, blue: 0, alpha: 0.75)
|
||||
}
|
||||
|
||||
/// Die Grösse, **relativ** zur Vorgabe des Players.
|
||||
///
|
||||
/// Ein Prozentwert, keine Punktgrösse: hundert heisst unverändert. So
|
||||
/// bleibt die Einstellung richtig, egal wie gross das Bild ist.
|
||||
public enum Size: Int, CaseIterable, Identifiable, Sendable, Codable {
|
||||
case small = 75
|
||||
case normal = 100
|
||||
case large = 130
|
||||
case huge = 175
|
||||
|
||||
public var id: Int { rawValue }
|
||||
}
|
||||
|
||||
/// Schrift und Kasten als **Paar**, nicht als zwei Regler.
|
||||
///
|
||||
/// Unabhängige Achsen liessen unlesbare Mischungen zu — Schwarz auf
|
||||
/// durchsichtig über einer Nachtszene ist eine gültige Einstellung und
|
||||
/// eine wertlose. Diese fünf decken ab, was man tatsächlich will.
|
||||
public enum Palette: String, CaseIterable, Identifiable, Sendable, Codable {
|
||||
case whiteOnClear
|
||||
case whiteOnBlack
|
||||
case blackOnWhite
|
||||
case yellowOnClear
|
||||
case yellowOnBlack
|
||||
|
||||
public var id: String { rawValue }
|
||||
}
|
||||
|
||||
public var size: Size
|
||||
public var palette: Palette
|
||||
|
||||
public init(size: Size = .normal, palette: Palette = .whiteOnClear) {
|
||||
self.size = size
|
||||
self.palette = palette
|
||||
}
|
||||
|
||||
public var foreground: RGBA {
|
||||
switch palette {
|
||||
case .whiteOnClear, .whiteOnBlack: .white
|
||||
case .blackOnWhite: .black
|
||||
case .yellowOnClear, .yellowOnBlack: .yellow
|
||||
}
|
||||
}
|
||||
|
||||
public var background: RGBA {
|
||||
switch palette {
|
||||
case .whiteOnClear, .yellowOnClear: .clear
|
||||
case .whiteOnBlack, .yellowOnBlack: .box
|
||||
case .blackOnWhite: RGBA(red: 1, green: 1, blue: 1, alpha: 0.85)
|
||||
}
|
||||
}
|
||||
|
||||
/// Ob ein Schlagschatten nötig ist.
|
||||
///
|
||||
/// Ohne Kasten steht die Schrift unmittelbar im Bild, und Weiss auf einer
|
||||
/// hellen Szene ist dort unlesbar. Mit Kasten wäre der Schatten nur
|
||||
/// Unruhe.
|
||||
public var wantsEdgeShadow: Bool {
|
||||
switch palette {
|
||||
case .whiteOnClear, .yellowOnClear: true
|
||||
case .whiteOnBlack, .blackOnWhite, .yellowOnBlack: false
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user