diff --git a/docs/superpowers/specs/2026-09-27-standardsprachen-design.md b/docs/superpowers/specs/2026-09-27-standardsprachen-design.md new file mode 100644 index 0000000..4903a1f --- /dev/null +++ b/docs/superpowers/specs/2026-09-27-standardsprachen-design.md @@ -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.