Entwurf für Standardsprachen der Tonspur

Drei Ebenen (allgemein → Mediathek → Serie), eine globale Rangliste als
Boden darunter, und die Wahl als prüfbares Punktesystem.

Zwei Funde bestimmen den Entwurf:

**Jellyfin hat kein Kennzeichen für Audiodeskription.** `MediaStream`
führt `IsDefault`, `IsForced`, `IsHearingImpaired`, `IsOriginal` — nichts
für Sehschädigung; die `visual_impaired`-Disposition reicht der Server
nicht durch. Sie ist nur am Text zu erkennen, und deshalb muss die
Erkennung nachsichtig sein und sich überstimmen lassen.

**Der Sprachvergleich greift heute bei `ger` nicht.** Verglichen werden
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 wirkt die Spracheinstellung lautlos gar nicht. Gemessen,
nicht vermutet.

Auf Serienebene wird nicht eine Sprache gespeichert, sondern die
Signatur der tatsächlich gewählten Spur. Nur so ist der ZDF-Fall lösbar,
bei dem die deutsche Spur die mit Audiodeskription ist und die als
englisch ausgezeichnete die richtige.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-09-27 18:59:46 +02:00
co-authored by Claude Opus 5
parent 10caa5fdd1
commit 41cd7331a9
@@ -0,0 +1,177 @@
# Standardsprachen für die Tonspur
## Kontext
Heute gibt es genau eine Einstellung: eine bevorzugte Tonsprache für alles. Sie wird angewandt, indem die **erste** Spur genommen wird, deren Sprachcode passt ([PlayerModel.swift:1374](../../../Flixy/Views/Player/PlayerModel.swift)). Kein Abwägen, keine Ausweichkette, keine Rücksicht darauf, was für eine Spur das ist.
Gewünscht ist etwas Abgestuftes: eine allgemeine Sprache, eine je Mediathek, und bei Serien eine **je Serie** — damit die eine auf Englisch läuft und die andere auf Deutsch.
Und ein konkreter Ärger, der den ganzen Entwurf bestimmt.
## Der ZDF-Fall
Bei manchen ZDF-Serien ist die als **deutsch** ausgezeichnete Spur die **mit Audiodeskription**, und die als **englisch** ausgezeichnete ist in Wahrheit Deutsch ohne Audiodeskription.
Daran scheitert jede Lösung, die nur „bevorzuge Deutsch" sagt — sie wählt zuverlässig die falsche. Wer hier ankommen will, muss zwei Dinge können:
1. **Audiodeskription erkennen und meiden.**
2. **Eine einmal getroffene Wahl für die ganze Serie behalten**, auch wenn sie einer Sprache widerspricht.
## Was Jellyfin hergibt — und was nicht
Aus `MediaBrowser.Model/Entities/MediaStream.cs` (Stand master):
| Feld | da? |
|---|---|
| `Language` | ja |
| `Title` (roh aus der Datei) | ja — **holen wir heute nicht** |
| `DisplayTitle` (zusammengesetzt) | ja |
| `IsDefault`, `IsForced` | ja |
| `IsHearingImpaired` | ja |
| `IsOriginal` | ja |
| **Audiodeskription** | **nein** |
Es gibt **kein Kennzeichen für Audiodeskription**. `IsHearingImpaired` meint Hörschädigung (Untertitel mit Geräuschbeschreibung), nicht Sehschädigung. Die `visual_impaired`-Disposition aus dem Container reicht Jellyfin nicht durch.
**Folge:** Audiodeskription ist nur am **Text** zu erkennen. Das ist keine schöne Lösung, aber die einzige — und es heisst, dass sie nachsichtig sein und sich überstimmen lassen muss.
Heute dekodieren wir von einer Spur nur `Index, Type, Codec, Language, DisplayTitle, Channels, IsDefault, IsForced` ([JellyfinItem.swift:166](../../../Packages/FlixyKit/Sources/JellyfinKit/JellyfinItem.swift)). `Title`, `IsHearingImpaired` und `IsOriginal` kommen dazu.
---
## Entwurf
### Drei Ebenen, eine Frage
```
Serie → Mediathek → allgemein
```
Jede Ebene kann „wie die Ebene darüber" sagen. Gefragt wird von unten nach oben; die erste Antwort gilt. Bei Filmen gibt es die Serienebene nicht — dort endet es bei der Mediathek, und genau so war es gewünscht.
### Gespeichert wird nicht „eine Sprache", sondern eine Spur
Das ist die Kernentscheidung, und sie kommt aus dem ZDF-Fall.
Auf der **Serienebene** wird nicht „Deutsch" abgelegt, sondern eine **Signatur** der Spur, die tatsächlich gewählt wurde:
```
TrackSignature { languageCode, normalizedTitle, channels, codec }
```
Eine Kennung („Spur 2") taugt nicht: Folgen derselben Serie haben unterschiedlich sortierte Spuren. Eine Signatur überlebt das, weil sie beschreibt, **wie** die Spur aussieht.
Damit ist der ZDF-Fall lösbar: einmal die „englische" Spur wählen, und jede weitere Folge nimmt sie — obwohl der Sprachwunsch Deutsch lautet.
Auf **Mediatheks-** und **allgemeiner** Ebene bleibt es bei einer Sprache. Dort wäre eine Signatur sinnlos, weil sie über verschiedene Titel hinweg nichts bedeutet.
### Die Wahl als Punktevergabe
Ein reiner Typ in `FlixyPlayback/Decision/AudioTrackChooser.swift` — Spuren und Wunsch hinein, eine Spur heraus. Kein Netz, keine Einstellungen, keine Ansicht. Dieselbe Bauform wie `PlaybackDecider`, `StreamHealth` und `DowngradeRule`, und aus demselben Grund: **eine Regel, die man nicht mit einer Zahlenfolge durchspielen kann, ist nicht zu beurteilen.**
| Kriterium | Punkte |
|---|---|
| passt zur gemerkten Signatur | **+100** |
| passt zur gewünschten Sprache | +60 |
| steht in der Rangliste | +50 minus Position |
| `IsDefault` | +10 |
| mehr Kanäle (5.1 vor Stereo) | +1 je Kanal |
| sieht nach **Audiodeskription** aus | **−200** |
| sieht nach **Kommentarspur** aus | −200 |
Gewinnt die höchste Summe; bei Gleichstand die Spur mit der kleineren Nummer, damit dieselbe Datei immer dasselbe ergibt.
**Abzug statt Ausschluss**, und das ist Absicht: gibt es eine Sprache *nur* mit Audiodeskription, ist sie immer noch besser als Stille. Ein harter Ausschluss liefe dort ins Leere.
Erkannt wird an Wortmarken in `Title` und `DisplayTitle` — `audiodeskription`, `audio description`, `hörfilm`, `descriptive`, freistehendes `ad`; für Kommentare `kommentar`, `commentary`, `director`. Die Liste steht an **einer** Stelle und ist geprüft.
### Die Sprachcodes lügen — und wir fallen darauf herein
Vor allem anderen ein Fund, gemessen statt vermutet. So vergleicht die App heute
([PlayerModel.swift:1399](../../../Flixy/Views/Player/PlayerModel.swift)): die ersten zwei Zeichen.
```
deu ~ de → Treffer
ger ~ de → KEIN Treffer ← Jellyfin meldet sehr oft „ger"
de-DE ~ de → Treffer
gsw ~ de → KEIN Treffer ← Schweizerdeutsch, eigene Sprache
```
**`ger` ist die ISO-639-2/B-Form von Deutsch**, und ffmpeg schreibt sie in viele Dateien. Bei solchen Titeln greift die Spracheinstellung heute **überhaupt nicht** — lautlos, ohne dass irgendetwas darauf hindeutet.
Gebraucht wird also ein richtiger Vergleich: kleinschreiben, Region abschneiden (`de-DE` → `de`), und die zwanzig Sprachen, die zwei dreibuchstabige Formen haben, über eine Tabelle zusammenführen (`ger`/`deu`, `fre`/`fra`, `dut`/`nld`, `cze`/`ces`, `gre`/`ell` …).
**`gsw` bleibt `gsw`.** Schweizerdeutsch ist keine Schreibweise von Deutsch, sondern eine eigene Sprache ohne zweibuchstabigen Code. Es auf `de` abzubilden wäre bequem und falsch — wer Hochdeutsch will, bekäme Schweizerdeutsch untergeschoben. Stattdessen steht es in der Rangliste, und zwar da, wo du es hinstellst.
### Wenn die Wunschsprache fehlt: eine Rangliste
Keine Zufallswahl, kein einzelner Ausweichwert, sondern eine **geordnete Liste** — „probier Deutsch, sonst Schweizerdeutsch, sonst Englisch, sonst Französisch".
**Nur einmal, für alles.** Sie ist keine dritte Einstellung je Mediathek oder Serie: dort steht der *Wunsch*, die Rangliste ist der Boden darunter und gilt überall gleich. Eine Reihenfolge je Mediathek zu pflegen wäre Arbeit für einen Fall, den es nicht gibt.
Sie greift **nur**, wenn der Wunsch bei diesem Titel fehlt. Hast du eine Datei mit nur Deutsch, ist nichts zu entscheiden; hast du eine mit zwölf Sprachen, entscheidet die Liste.
Die vollständige Reihenfolge, die immer zu einer Antwort kommt:
1. gemerkte Signatur der Serie
2. Wunschsprache (Serie → Mediathek → allgemein)
3. die Rangliste, von oben nach unten
4. die Spur mit `IsDefault`
5. die erste Spur ohne Audiodeskription
6. die erste Spur
Das Punktesystem bildet 1–3 ab; 4–6 sind der Boden darunter.
### Gemerkt wird, was man tut
Wählt jemand **während einer Folge** eine andere Tonspur, wird sie als Signatur für diese Serie abgelegt. Ohne Nachfrage — die Frage „für diese Serie merken?" bei jeder Umstellung ist genau die Art Dialog, die man nach dem dritten Mal wegklickt.
Sichtbar wird es trotzdem: das Spurenblatt zeigt „Für diese Serie gemerkt" mit einem Weg, es wieder zu löschen. **Eine automatische Handlung, die man nicht sehen kann, ist von einem Fehler nicht zu unterscheiden** — dasselbe Prinzip wie bei der Qualitätsautomatik.
Bei Filmen wird nichts gemerkt: ein Film wird einmal gesehen.
### Wo es steht
- **Einstellungen → Sprachen**: Tonsprache, die **Rangliste** (ziehbar, mit Hinzufügen und Entfernen), Untertitelsprache, und ein Schalter „Audiodeskription meiden" (an).
- **Mediathek → Filter/Einstellungen**: „Tonsprache für diese Mediathek" mit „wie allgemein" als Vorgabe.
- **Serie → Detailseite**: „Tonsprache für diese Serie", mit dem gemerkten Wert, falls einer da ist.
Abgelegt wird alles **je Gerät** in `UserDefaults` — wie alle anderen Einstellungen auch, und wie besprochen ohne Synchronisierung.
---
## Plex
Für **dieses** Stück ist die Antwort einfach: es wird Jellyfin-only gebaut. Die Auswahl steckt im neutralen Teil und würde bei Plex arbeiten, wenn dort jemals dieselben Angaben ankommen — aber geprüft wird sie nur gegen Jellyfin.
**PlexKit jetzt zu entfernen, wäre eine eigene Entscheidung**, und keine kleine: gut fünftausend Zeilen, die vollständige Live-TV-Anbindung samt Programmführer und Aufnahmen, der PIN-Anmeldeweg, das Werkzeug `FlixyProbe` und rund achtzig Tests hängen daran. Das ist umkehrbar nur über die Versionsgeschichte.
Vorschlag: **nicht jetzt.** Erst soll Jellyfin das können, was du brauchst; erst wenn du Plex tatsächlich abgeschaltet hast, wird ausgebaut. Solange es steht, kostet es nichts ausser dem Verzicht auf neue Plex-Funktionen — und den hast du ohnehin ausgesprochen.
---
## Was ich nicht bauen würde
1. **Keinen Proxy vor Jellyfin.** Ausdrücklich ausgeschlossen, und ich teile das: eine Schicht, die Antworten umschreibt, bricht bei jedem Server-Update anders.
2. **Keinen eigenen Server.** Monate für Anmeldung, Mediathek, Metadaten, Sitzungen — und die Sprachwahl ist davon der kleinste Teil.
3. **Keine Sprache je Genre oder Kategorie.** Du hast es selbst verworfen, und zu Recht: die Frage „welche Sprache für Dokumentationen" hat niemand.
4. **Keine Zufallswahl** als Ausweichfall. Zweimal dieselbe Folge soll zweimal dasselbe ergeben.
5. **Kein Dialog beim Umstellen.** Siehe oben.
6. **Keine Heuristik über den Inhalt** — etwa Tonspuren vergleichen, um Audiodeskription zu erkennen. Das ist Signalverarbeitung auf dem Gerät für einen Fall, den drei Wortmarken erschlagen.
## Wie geprüft wird
- **`AudioTrackChooserTests`**: Spurlisten von Hand, darunter der ZDF-Fall wörtlich — deutsche Spur mit Audiodeskription, englisch betitelte deutsche Spur — und die Erwartung, dass nach einmaligem Merken die richtige kommt.
- **Sprachcodes**: `ger`/`deu`/`de-DE` müssen alle auf Deutsch passen, `gsw` auf **keines** davon. Genau der Fall, der heute stillschweigend danebengeht.
- Rangliste: Wunschsprache fehlt, zweite greift; keine aus der Liste vorhanden; nur Audiodeskription da; gar keine Sprachangabe; eine einzige Spur.
- Gleichstand: zweimal dasselbe Ergebnis.
- Gegen die **Attrappe** lässt sich eine Datei mit mehreren Spuren melden und die Auswahl im Player ansehen.
- An deinem Server: eine ZDF-Folge, einmal die richtige Spur wählen, nächste Folge öffnen.
## Fertig ist es, wenn
- Eine ZDF-Folge nach einmaligem Umstellen dauerhaft ohne Audiodeskription läuft.
- Eine Serie auf Englisch und eine andere auf Deutsch startet, ohne dass zwischendurch etwas umgestellt wird.
- Fehlt die Wunschsprache, greift die Rangliste — nachvollziehbar und jedes Mal gleich.
- Eine Datei, die ihre Tonspur `ger` nennt, wird endlich als Deutsch erkannt.
- Was die App automatisch gemerkt hat, ist im Spurenblatt zu sehen und zu löschen.