Files
Flaro/Packages/FlixyKit/Sources/FlixyPlayback/Engine/SubtitleStyle.swift
T
ScarriffleandClaude Opus 5 0815432efd 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>
2026-09-28 21:22:22 +02:00

98 lines
3.3 KiB
Swift

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
}
}
}