Der Kern der Sprachwahl: Kürzel, die nicht lügen, und Abwägen statt Filtern

**`LanguageCode`** bringt Kürzel auf eine vergleichbare Form. Der Anlass
ist ein stiller Fehler: verglichen wurden die ersten zwei Zeichen, also
`ge` gegen `de` — und `ger` ist die ISO-639-2/B-Form von Deutsch, die
ffmpeg in viele Dateien schreibt. Bei solchen Titeln wirkte die
Spracheinstellung überhaupt nicht.

`gsw` bleibt `gsw`. Schweizerdeutsch ist keine Schreibweise von Deutsch,
und es gleichzusetzen hiesse, jemandem Schweizerdeutsch unterzuschieben,
der Hochdeutsch wollte. Es gehört in die Rangliste, nicht in eine
Gleichsetzung.

**`AudioTrackChooser`** wägt ab, statt zu filtern: jede Spur bekommt
Punkte, die höchste gewinnt. Damit gibt es immer eine Antwort, und
Audiodeskription wird gemieden, ohne unmöglich zu werden — gibt es eine
Sprache nur mit Audiodeskription, ist sie besser als Stille.

Gemerkt wird nicht eine Nummer, sondern eine Beschreibung der Spur:
Folgen derselben Serie haben unterschiedlich sortierte Spuren, und
„Spur 2" bedeutet in der nächsten Folge etwas anderes.

Siebzehn Tests, darunter der ZDF-Fall wörtlich — deutsche Spur mit
Audiodeskription als Standard, englisch betitelte deutsche Spur daneben
— und zwar in beide Richtungen: mit Meiden gewinnt die richtige, ohne
Meiden gewinnt das Kürzel. Sonst stünde der Schalter nur dabei.

Dazu trägt `MediaStream` jetzt `title`, `isDefault`, `isHearingImpaired`
und `isOriginal`. Der Name aus der Datei ist das zweite Signal neben dem
Kürzel und manchmal das einzige richtige.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-09-27 19:15:08 +02:00
co-authored by Claude Opus 5
parent 946de29893
commit afb4a63655
5 changed files with 496 additions and 2 deletions
@@ -174,6 +174,19 @@ public struct MediaStream: Sendable, Equatable, Identifiable {
public let isSelected: Bool
/// Untertitel, die auch bei abgeschalteten Untertiteln erscheinen sollen.
public let isForced: Bool
/// Wie die Spur **in der Datei** heißt.
///
/// Das zweite Signal neben dem Sprachkürzel — und manchmal das einzige
/// richtige. Wo im Kürzel „eng" steht, kann hier „Deutsch" stehen; genau
/// so sehen die Dateien aus, bei denen die Sprachwahl bisher danebengriff.
public let title: String?
/// Die Spur, die der Server als Standard markiert.
public let isDefault: Bool
/// Für Hörgeschädigte. **Nicht** Audiodeskription — dafür gibt es bei
/// Jellyfin kein Kennzeichen, die lässt sich nur am Namen erkennen.
public let isHearingImpaired: Bool
/// Die Originaltonspur, sofern die Datei das vermerkt.
public let isOriginal: Bool
public init(
id: String,
@@ -184,7 +197,11 @@ public struct MediaStream: Sendable, Equatable, Identifiable {
displayTitle: String? = nil,
channels: Int? = nil,
isSelected: Bool = false,
isForced: Bool = false
isForced: Bool = false,
title: String? = nil,
isDefault: Bool = false,
isHearingImpaired: Bool = false,
isOriginal: Bool = false
) {
self.id = id
self.kind = kind
@@ -195,8 +212,15 @@ public struct MediaStream: Sendable, Equatable, Identifiable {
self.channels = channels
self.isSelected = isSelected
self.isForced = isForced
self.title = title
self.isDefault = isDefault
self.isHearingImpaired = isHearingImpaired
self.isOriginal = isOriginal
}
/// Die Nummer, unter der der Server diese Spur führt.
public var index: Int? { Int(id) }
/// Ein brauchbarer Name, auch wenn der Server keinen liefert.
///
/// „Spur 3" hilft niemandem bei der Wahl; Sprache und Codec schon.