From 32f592b83ee355b99af46767d6468db3616146d4 Mon Sep 17 00:00:00 2001 From: Scarriffle Date: Thu, 15 Oct 2026 16:46:27 +0200 Subject: [PATCH] =?UTF-8?q?Ein=20Abspann=20darf=20aus=20St=C3=BCcken=20bes?= =?UTF-8?q?tehen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Zwei Fälle fielen durch, beide am Bestand gesehen. Bei einem Marvel-Film liegt hinter dem Abspann eine Post-Credit-Szene: die Suche begann am Dateiende, fand nur den kurzen Rest dahinter und verwarf ihn als zu kurz. Und ein Abspann, den eine Szene in der Mitte teilt, zerfiel in zwei Reste, von denen keiner lang genug war. Dieselbe Ursache: der Abspann musste eine zusammenhängende dunkle Strecke bis zum Dateiende sein. Jetzt bildet die Suche Läufe, fasst sie über Lücken bis vier Minuten zusammen — so lang ist die längste bekannte Post-Credit-Szene plus Reserve — und lässt die **Summe** über die Mindestlänge entscheiden statt jedes einzelne Stück. Ein Lauf unter fünfzehn Sekunden ist eine Blende und zählt nicht mit. Dafür mussten die eindeutigen Indizes über (Titel, Art) weichen; sie standen mit dem Vermerk „eine Art einmal je Titel" da, und genau das stimmt nicht mehr. `SchreibenAsync` ersetzt jetzt die Menge, statt eine Zeile umzuschreiben — das ist der einzige Weg, der in beide Richtungen stimmt, wenn aus zwei Stücken wieder eines wird. **Drei Stellen trugen das nicht.** Im Web hiess „Abspann überspringen" bei einem Film immer Schluss — beim ersten von zwei Stücken bekäme niemand die Szene je zu sehen; geschlossen wird jetzt nur, wenn nichts mehr folgt. Auf iOS verwarf der Spieler die Abspannmarke eines Films ganz; diese Regel gehört nicht dorthin, sondern auf den Server, und ist ersatzlos weg. Und die Gesehen-Schwelle nahm irgendeine Abspannzeile — ohne `OrderBy` entschied die Datenbank; gemeint ist immer die früheste. Dazu der Schalter „Zusätzliche Szenen mit überspringen" (Vorgabe aus) bei den Geräteeinstellungen, und ein Messwerkzeug: ein Knopf auf der Filmseite lädt die Kurve je Schlüsselbild als Tabelle — Dunkelanteil des ganzen Bildes und der beiden Randstreifen. Ob bei Outtakes im Abspann die Ränder dunkel bleiben, ist eine Vermutung; bevor daraus eine Schwelle wird, will ich die Zahlen sehen. Fünf neue Prüfungen, darunter die wichtigste: ein durchgehender Abspann bleibt ein Stück. Co-Authored-By: Claude Opus 5 --- Flaro/Resources/Localizable.xcstrings | 10456 ++++++++-------- Flaro/Session/PlaybackSettings.swift | 10 + Flaro/Views/Player/PlayerModel.swift | 31 +- .../Views/Settings/PlaybackSettingsView.swift | 1 + .../Endpoints/LibraryContentEndpoints.cs | 43 + .../Flaro.Api/Endpoints/PlaybackEndpoints.cs | 7 + server/src/Flaro.Api/Markenerkennung.cs | 91 +- server/src/Flaro.Core/Media/Abspannkante.cs | 180 +- server/src/Flaro.Data/FlaroContext.cs | 12 +- .../20261015142742_Abspannstuecke.Designer.cs | 1357 ++ .../20261015142742_Abspannstuecke.cs | 58 + .../Migrations/FlaroContextModelSnapshot.cs | 8 +- server/src/Flaro.Media/Bildabtaster.cs | 52 +- .../Flaro.Core.Tests/AbspannkanteTests.cs | 209 +- .../Flaro.Media.Tests/BildabtasterTests.cs | 11 +- web/app/src/api.ts | 8 + web/app/src/spieler.ts | 13 +- web/app/src/views/detail.ts | 12 +- 18 files changed, 7216 insertions(+), 5343 deletions(-) create mode 100644 server/src/Flaro.Data/Migrations/20261015142742_Abspannstuecke.Designer.cs create mode 100644 server/src/Flaro.Data/Migrations/20261015142742_Abspannstuecke.cs diff --git a/Flaro/Resources/Localizable.xcstrings b/Flaro/Resources/Localizable.xcstrings index 233662f..fbb0594 100644 --- a/Flaro/Resources/Localizable.xcstrings +++ b/Flaro/Resources/Localizable.xcstrings @@ -1,8096 +1,8112 @@ { - "sourceLanguage" : "en", - "strings" : { - " · " : { - "comment" : "A separator between different parts of the episode metadata.", - "isCommentAutoGenerated" : true + "sourceLanguage": "en", + "strings": { + " · ": { + "comment": "A separator between different parts of the episode metadata.", + "isCommentAutoGenerated": true }, - "%@" : { - "comment" : "A number in a list.", - "isCommentAutoGenerated" : true + "%@": { + "comment": "A number in a list.", + "isCommentAutoGenerated": true }, - "%@ → %@" : { - "comment" : "A line in a diagnostic trace that shows a network request path and its result. The first argument is the network request path. The second argument is the network response result.", - "isCommentAutoGenerated" : true, - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "%1$@ → %2$@" + "%@ GB": { + "comment": "A picker option in the settings view that lets the user select the maximum amount of storage space that can be used for downloaded content. The value is given in gigabytes.", + "isCommentAutoGenerated": true + }, + "%@ → %@": { + "comment": "A line in a diagnostic trace that shows a network request path and its result. The first argument is the network request path. The second argument is the network response result.", + "isCommentAutoGenerated": true, + "localizations": { + "en": { + "stringUnit": { + "state": "new", + "value": "%1$@ → %2$@" } } } }, - "%@ GB" : { - "comment" : "A picker option in the settings view that lets the user select the maximum amount of storage space that can be used for downloaded content. The value is given in gigabytes.", - "isCommentAutoGenerated" : true - }, - "%@: %@" : { - "comment" : "A label that shows the audio or subtitle language. The argument is the language name.", - "isCommentAutoGenerated" : true, - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "%1$@: %2$@" + "%@: %@": { + "comment": "A label that shows the audio or subtitle language. The argument is the language name.", + "isCommentAutoGenerated": true, + "localizations": { + "en": { + "stringUnit": { + "state": "new", + "value": "%1$@: %2$@" } } } }, - "4K" : { - "comment" : "A label for a 4K video.", - "isCommentAutoGenerated" : true + "4K": { + "comment": "A label for a 4K video.", + "isCommentAutoGenerated": true + }, + "Full HD": { + "comment": "A label for a media resolution.", + "isCommentAutoGenerated": true + }, + "HD": { + "comment": "A label for a media resolution.", + "isCommentAutoGenerated": true }, - "account.hint" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Öffnet Einstellungen und Abmelden" + "SD": { + "comment": "A label for a 720p movie.", + "isCommentAutoGenerated": true + }, + "account.hint": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Öffnet Einstellungen und Abmelden" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Opens settings and sign out" + "en": { + "stringUnit": { + "state": "translated", + "value": "Opens settings and sign out" } } } }, - "account.settings" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Einstellungen" + "account.settings": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Einstellungen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Settings" + "en": { + "stringUnit": { + "state": "translated", + "value": "Settings" } } } }, - "action.addFavourite" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Zu Favoriten" + "action.addFavourite": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Zu Favoriten" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Add to favourites" + "en": { + "stringUnit": { + "state": "translated", + "value": "Add to favourites" } } } }, - "action.apply" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Übernehmen" + "action.apply": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Übernehmen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Apply" + "en": { + "stringUnit": { + "state": "translated", + "value": "Apply" } } } }, - "action.back" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Zurück" + "action.back": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Zurück" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Back" + "en": { + "stringUnit": { + "state": "translated", + "value": "Back" } } } }, - "action.cancel" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Abbrechen" + "action.cancel": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Abbrechen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Cancel" + "en": { + "stringUnit": { + "state": "translated", + "value": "Cancel" } } } }, - "action.clearRating" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bewertung entfernen" + "action.clearRating": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Bewertung entfernen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Clear rating" + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear rating" } } } }, - "action.close" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Schließen" + "action.close": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Schließen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Close" + "en": { + "stringUnit": { + "state": "translated", + "value": "Close" } } } }, - "action.delete" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Löschen" + "action.delete": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Löschen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Delete" + "en": { + "stringUnit": { + "state": "translated", + "value": "Delete" } } } }, - "action.done" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Fertig" + "action.done": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Fertig" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Done" + "en": { + "stringUnit": { + "state": "translated", + "value": "Done" } } } }, - "action.markUnwatched" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Als ungesehen markieren" + "action.markUnwatched": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Als ungesehen markieren" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mark as unwatched" + "en": { + "stringUnit": { + "state": "translated", + "value": "Mark as unwatched" } } } }, - "action.markWatched" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Als gesehen markieren" + "action.markWatched": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Als gesehen markieren" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mark as watched" + "en": { + "stringUnit": { + "state": "translated", + "value": "Mark as watched" } } } }, - "action.rate" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bewerten" + "action.rate": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Bewerten" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Rate" + "en": { + "stringUnit": { + "state": "translated", + "value": "Rate" } } } }, - "action.removeFavourite" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Aus Favoriten entfernen" + "action.removeFavourite": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Aus Favoriten entfernen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Remove from favourites" + "en": { + "stringUnit": { + "state": "translated", + "value": "Remove from favourites" } } } }, - "action.removeFromContinue" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Aus „Weiterschauen“ entfernen" + "action.removeFromContinue": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Aus „Weiterschauen“ entfernen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Remove from Continue Watching" + "en": { + "stringUnit": { + "state": "translated", + "value": "Remove from Continue Watching" } } } }, - "action.retry" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Erneut versuchen" + "action.retry": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Erneut versuchen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Try again" + "en": { + "stringUnit": { + "state": "translated", + "value": "Try again" } } } }, - "action.signOut" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Abmelden" + "action.signOut": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Abmelden" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sign out" + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign out" } } } }, - "appearance.accent" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Akzentfarbe" + "appearance.accent": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Akzentfarbe" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Accent color" + "en": { + "stringUnit": { + "state": "translated", + "value": "Accent color" } } } }, - "appearance.accentExplanation" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Die Farbe erscheint auf Knöpfen, Fortschrittsbalken und der ausgewählten Zeile in der Seitenleiste." + "appearance.accentExplanation": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Die Farbe erscheint auf Knöpfen, Fortschrittsbalken und der ausgewählten Zeile in der Seitenleiste." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The color appears on buttons, progress bars and the selected row in the sidebar." + "en": { + "stringUnit": { + "state": "translated", + "value": "The color appears on buttons, progress bars and the selected row in the sidebar." } } } }, - "appearance.adjusted %@" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Die Farbe wurde auf %@ aufgehellt, damit sie auf dem dunklen Hintergrund lesbar bleibt." + "appearance.adjusted %@": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Die Farbe wurde auf %@ aufgehellt, damit sie auf dem dunklen Hintergrund lesbar bleibt." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Lightened to %@ so it stays readable on the dark background." + "en": { + "stringUnit": { + "state": "translated", + "value": "Lightened to %@ so it stays readable on the dark background." } } } }, - "appearance.cardScale" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Kachelgröße" + "appearance.cardScale": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Kachelgröße" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tile size" + "en": { + "stringUnit": { + "state": "translated", + "value": "Tile size" } } } }, - "appearance.cardScaleExplanation" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wie groß Plakate und Vorschaubilder erscheinen — auf der Startseite und in den Mediatheken. Die Einstellung gilt nur auf diesem Gerät." + "appearance.cardScaleExplanation": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Wie groß Plakate und Vorschaubilder erscheinen — auf der Startseite und in den Mediatheken. Die Einstellung gilt nur auf diesem Gerät." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "How large posters and thumbnails appear — on the home screen and in libraries. This setting applies to this device only." + "en": { + "stringUnit": { + "state": "translated", + "value": "How large posters and thumbnails appear — on the home screen and in libraries. This setting applies to this device only." } } } }, - "appearance.customColor" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Eigene Farbe" + "appearance.customColor": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Eigene Farbe" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Custom color" + "en": { + "stringUnit": { + "state": "translated", + "value": "Custom color" } } } }, - "appearance.hexPlaceholder" : { - "comment" : "Beispiel für eine Hex-Farbe", - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "#1DB954" + "appearance.hexPlaceholder": { + "comment": "Beispiel für eine Hex-Farbe", + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "#1DB954" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "#1DB954" + "en": { + "stringUnit": { + "state": "translated", + "value": "#1DB954" } } } }, - "appearance.invalidHex" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Das ist keine gültige Farbe. Erwartet wird etwas wie #1DB954." + "appearance.invalidHex": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Das ist keine gültige Farbe. Erwartet wird etwas wie #1DB954." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "That's not a valid color. Try something like #1DB954." + "en": { + "stringUnit": { + "state": "translated", + "value": "That's not a valid color. Try something like #1DB954." } } } }, - "appearance.navigation" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Navigation" + "appearance.navigation": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Navigation" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Navigation" + "en": { + "stringUnit": { + "state": "translated", + "value": "Navigation" } } } }, - "appearance.navigationExplanation" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Seitlich fährt die Leiste über den Inhalt und gibt ihn danach wieder frei. Oben stehen alle Einträge gleichzeitig da und nehmen dauerhaft eine Zeile ein." + "appearance.navigationExplanation": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Seitlich fährt die Leiste über den Inhalt und gibt ihn danach wieder frei. Oben stehen alle Einträge gleichzeitig da und nehmen dauerhaft eine Zeile ein." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "At the side, the bar slides over the content and gets out of the way again. At the top, every entry is visible at once and takes up a row permanently." + "en": { + "stringUnit": { + "state": "translated", + "value": "At the side, the bar slides over the content and gets out of the way again. At the top, every entry is visible at once and takes up a row permanently." } } } }, - "appearance.navigationSide" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Seitlich" + "appearance.navigationSide": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Seitlich" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "At the side" + "en": { + "stringUnit": { + "state": "translated", + "value": "At the side" } } } }, - "appearance.navigationTop" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Oben" + "appearance.navigationTop": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Oben" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "At the top" + "en": { + "stringUnit": { + "state": "translated", + "value": "At the top" } } } }, - "appearance.preview" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Vorschau" + "appearance.preview": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Vorschau" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Preview" + "en": { + "stringUnit": { + "state": "translated", + "value": "Preview" } } } }, - "appearance.title" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Erscheinungsbild" + "appearance.title": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Erscheinungsbild" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Appearance" + "en": { + "stringUnit": { + "state": "translated", + "value": "Appearance" } } } }, - "connecting.searching" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Server werden gesucht …" + "connecting.searching": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Server werden gesucht …" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Looking for servers…" + "en": { + "stringUnit": { + "state": "translated", + "value": "Looking for servers…" } } } }, - "connecting.to %@" : { - "comment" : "Platzhalter: Servername", - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Verbinde mit %@ …" + "connecting.to %@": { + "comment": "Platzhalter: Servername", + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Verbinde mit %@ …" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Connecting to %@…" + "en": { + "stringUnit": { + "state": "translated", + "value": "Connecting to %@…" } } } }, - "detail.cast" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Besetzung" + "detail.cast": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Besetzung" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Cast" + "en": { + "stringUnit": { + "state": "translated", + "value": "Cast" } } } }, - "detail.episodeCount %lld" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld Folgen" + "detail.episodeCount %lld": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "%lld Folgen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld episodes" + "en": { + "stringUnit": { + "state": "translated", + "value": "%lld episodes" } } } }, - "detail.episodes" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Folgen" + "detail.episodes": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Folgen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Episodes" + "en": { + "stringUnit": { + "state": "translated", + "value": "Episodes" } } } }, - "detail.loadSelected %lld" : { - "comment" : "Die angekreuzten Folgen laden", - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld Folgen laden" + "detail.loadSelected %lld": { + "comment": "Die angekreuzten Folgen laden", + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "%lld Folgen laden" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Download %lld episodes" + "en": { + "stringUnit": { + "state": "translated", + "value": "Download %lld episodes" } } } }, - "detail.playFromStart" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Von vorn abspielen" + "detail.playFromStart": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Von vorn abspielen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Play from start" + "en": { + "stringUnit": { + "state": "translated", + "value": "Play from start" } } } }, - "detail.seasons" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Staffeln" + "detail.seasons": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Staffeln" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Seasons" + "en": { + "stringUnit": { + "state": "translated", + "value": "Seasons" } } } }, - "detail.select" : { - "comment" : "Mehrere Folgen auswählen", - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Auswählen" + "detail.select": { + "comment": "Mehrere Folgen auswählen", + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Auswählen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Select" + "en": { + "stringUnit": { + "state": "translated", + "value": "Select" } } } }, - "detail.selectAll" : { - "comment" : "Alle Folgen ankreuzen", - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Alle" + "detail.selectAll": { + "comment": "Alle Folgen ankreuzen", + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Alle" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "All" + "en": { + "stringUnit": { + "state": "translated", + "value": "All" } } } }, - "detail.selectNone" : { - "comment" : "Alle Haken entfernen", - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Keine" + "detail.selectNone": { + "comment": "Alle Haken entfernen", + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Keine" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "None" + "en": { + "stringUnit": { + "state": "translated", + "value": "None" } } } }, - "detail.toSeason" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Zur Staffel" + "detail.toSeason": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Zur Staffel" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Go to season" + "en": { + "stringUnit": { + "state": "translated", + "value": "Go to season" } } } }, - "detail.toSeries" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Zur Serie" + "detail.toSeries": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Zur Serie" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Go to series" + "en": { + "stringUnit": { + "state": "translated", + "value": "Go to series" } } } }, - "detail.unwatchedCount %lld" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld ungesehen" + "detail.unwatchedCount %lld": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "%lld ungesehen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld unwatched" + "en": { + "stringUnit": { + "state": "translated", + "value": "%lld unwatched" } } } }, - "detail.versions" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Fassung" + "detail.versions": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Fassung" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Version" + "en": { + "stringUnit": { + "state": "translated", + "value": "Version" } } } }, - "diagnostics.connection" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Verbindung" + "diagnostics.connection": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Verbindung" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Connection" + "en": { + "stringUnit": { + "state": "translated", + "value": "Connection" } } } }, - "diagnostics.connectionUsed" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Verbindung" + "diagnostics.connectionUsed": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Verbindung" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Connection" + "en": { + "stringUnit": { + "state": "translated", + "value": "Connection" } } } }, - "diagnostics.copy" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bericht kopieren" + "diagnostics.copy": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Bericht kopieren" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Copy report" + "en": { + "stringUnit": { + "state": "translated", + "value": "Copy report" } } } }, - "diagnostics.copyHint" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Enthält keinen Token. Kannst du unbesorgt weitergeben." + "diagnostics.copyHint": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Enthält keinen Token. Kannst du unbesorgt weitergeben." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Contains no token. Safe to share." + "en": { + "stringUnit": { + "state": "translated", + "value": "Contains no token. Safe to share." } } } }, - "diagnostics.lastAttempt" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Letzter Abspielversuch" + "diagnostics.lastAttempt": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Letzter Abspielversuch" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Last playback attempt" + "en": { + "stringUnit": { + "state": "translated", + "value": "Last playback attempt" } } } }, - "diagnostics.method" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Weg" + "diagnostics.method": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Weg" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Method" + "en": { + "stringUnit": { + "state": "translated", + "value": "Method" } } } }, - "diagnostics.noServer" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Keine Serververbindung." + "diagnostics.noServer": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Keine Serververbindung." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "No server connection." + "en": { + "stringUnit": { + "state": "translated", + "value": "No server connection." } } } }, - "diagnostics.nothingYet" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Noch nichts abgespielt." + "diagnostics.nothingYet": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Noch nichts abgespielt." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nothing played yet." + "en": { + "stringUnit": { + "state": "translated", + "value": "Nothing played yet." } } } }, - "diagnostics.source" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Quelle (ohne Token)" + "diagnostics.source": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Quelle (ohne Token)" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Source (token removed)" + "en": { + "stringUnit": { + "state": "translated", + "value": "Source (token removed)" } } } }, - "diagnostics.steps" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ablauf" + "diagnostics.steps": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Ablauf" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Timeline" + "en": { + "stringUnit": { + "state": "translated", + "value": "Timeline" } } } }, - "diagnostics.title" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Diagnose" + "diagnostics.title": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Diagnose" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Diagnostics" + "en": { + "stringUnit": { + "state": "translated", + "value": "Diagnostics" } } } }, - "diagnostics.title.label" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Titel" + "diagnostics.title.label": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Titel" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Title" + "en": { + "stringUnit": { + "state": "translated", + "value": "Title" } } } }, - "downloads.active" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Läuft gerade" + "downloads.active": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Läuft gerade" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "In progress" + "en": { + "stringUnit": { + "state": "translated", + "value": "In progress" } } } }, - "downloads.allEpisodes" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Alle Folgen laden" + "downloads.allEpisodes": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Alle Folgen laden" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Download all episodes" + "en": { + "stringUnit": { + "state": "translated", + "value": "Download all episodes" } } } }, - "downloads.audio.hint" : { - "comment" : "Erklärt, warum die Wahl jetzt fällt", - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Beim Umpacken kommt genau eine mit — danach lässt sie sich nicht mehr wechseln." + "downloads.audio.hint": { + "comment": "Erklärt, warum die Wahl jetzt fällt", + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Beim Umpacken kommt genau eine mit — danach lässt sie sich nicht mehr wechseln." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Repackaging keeps exactly one — it cannot be changed afterwards." + "en": { + "stringUnit": { + "state": "translated", + "value": "Repackaging keeps exactly one — it cannot be changed afterwards." } } } }, - "downloads.audio.recommended %@" : { - "comment" : "Markiert den Vorschlag", - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%@ · empfohlen" + "downloads.audio.recommended %@": { + "comment": "Markiert den Vorschlag", + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "%@ · empfohlen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%@ · recommended" + "en": { + "stringUnit": { + "state": "translated", + "value": "%@ · recommended" } } } }, - "downloads.audio.title" : { - "comment" : "Überschrift der Tonspurwahl beim Herunterladen", - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Welche Tonspur?" + "downloads.audio.title": { + "comment": "Überschrift der Tonspurwahl beim Herunterladen", + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Welche Tonspur?" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Which audio track?" + "en": { + "stringUnit": { + "state": "translated", + "value": "Which audio track?" } } } }, - "downloads.available" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Offline verfügbar" + "downloads.available": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Offline verfügbar" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Available offline" + "en": { + "stringUnit": { + "state": "translated", + "value": "Available offline" } } } }, - "downloads.batch.queued %lld" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld Folgen in die Warteschlange gelegt." + "downloads.batch.queued %lld": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "%lld Folgen in die Warteschlange gelegt." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Queued %lld episodes." + "en": { + "stringUnit": { + "state": "translated", + "value": "Queued %lld episodes." } } } }, - "downloads.batch.title" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Downloads" + "downloads.batch.title": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Downloads" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Downloads" + "en": { + "stringUnit": { + "state": "translated", + "value": "Downloads" } } } }, - "downloads.batch.unavailable %lld" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld Folgen haben keine abspielbare Datei auf dem Server." + "downloads.batch.unavailable %lld": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "%lld Folgen haben keine abspielbare Datei auf dem Server." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld episodes have no playable file on the server." + "en": { + "stringUnit": { + "state": "translated", + "value": "%lld episodes have no playable file on the server." } } } }, - "downloads.cancel" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Download abbrechen" + "downloads.cancel": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Download abbrechen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Cancel download" + "en": { + "stringUnit": { + "state": "translated", + "value": "Cancel download" } } } }, - "downloads.download" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Herunterladen" + "downloads.download": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Herunterladen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Download" + "en": { + "stringUnit": { + "state": "translated", + "value": "Download" } } } }, - "downloads.empty.description" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Lade Filme und Folgen herunter, um sie ohne Verbindung anzusehen." + "downloads.empty.description": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Lade Filme und Folgen herunter, um sie ohne Verbindung anzusehen." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Download movies and episodes to watch them without a connection." + "en": { + "stringUnit": { + "state": "translated", + "value": "Download movies and episodes to watch them without a connection." } } } }, - "downloads.empty.title" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nichts offline" + "downloads.empty.title": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Nichts offline" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nothing offline" + "en": { + "stringUnit": { + "state": "translated", + "value": "Nothing offline" } } } }, - "downloads.episodeCount %lld" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld Folgen geladen" + "downloads.episodeCount %lld": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "%lld Folgen geladen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld episodes downloaded" + "en": { + "stringUnit": { + "state": "translated", + "value": "%lld episodes downloaded" } } } }, - "downloads.failed" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Fehlgeschlagen" + "downloads.failed": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Fehlgeschlagen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Failed" + "en": { + "stringUnit": { + "state": "translated", + "value": "Failed" } } } }, - "downloads.hold.offline" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Keine Verbindung" + "downloads.hold.offline": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Keine Verbindung" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "No connection" + "en": { + "stringUnit": { + "state": "translated", + "value": "No connection" } } } }, - "downloads.hold.offline.description" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ohne Verbindung geht nichts weiter. Angefangene Downloads laufen weiter, sobald das Gerät wieder am Netz ist." + "downloads.hold.offline.description": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Ohne Verbindung geht nichts weiter. Angefangene Downloads laufen weiter, sobald das Gerät wieder am Netz ist." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nothing transfers without a connection. Started downloads continue once the device is back online." + "en": { + "stringUnit": { + "state": "translated", + "value": "Nothing transfers without a connection. Started downloads continue once the device is back online." } } } }, - "downloads.hold.wifi" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wartet auf WLAN" + "downloads.hold.wifi": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Wartet auf WLAN" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Waiting for Wi-Fi" + "en": { + "stringUnit": { + "state": "translated", + "value": "Waiting for Wi-Fi" } } } }, - "downloads.hold.wifi.description" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "„Nur über WLAN“ ist eingeschaltet und das Gerät hängt gerade am Mobilfunk. Sobald WLAN da ist, geht es von selbst weiter." + "downloads.hold.wifi.description": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "„Nur über WLAN“ ist eingeschaltet und das Gerät hängt gerade am Mobilfunk. Sobald WLAN da ist, geht es von selbst weiter." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "“Wi-Fi only” is on and the device is on cellular. Transfers resume by themselves once Wi-Fi is back." + "en": { + "stringUnit": { + "state": "translated", + "value": "“Wi-Fi only” is on and the device is on cellular. Transfers resume by themselves once Wi-Fi is back." } } } }, - "downloads.movies" : { - "comment" : "Überschrift für heruntergeladene Filme", - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Filme" + "downloads.movies": { + "comment": "Überschrift für heruntergeladene Filme", + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Filme" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Movies" + "en": { + "stringUnit": { + "state": "translated", + "value": "Movies" } } } }, - "downloads.pause" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Anhalten" + "downloads.pause": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Anhalten" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Pause" + "en": { + "stringUnit": { + "state": "translated", + "value": "Pause" } } } }, - "downloads.paused" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Angehalten" + "downloads.paused": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Angehalten" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Paused" + "en": { + "stringUnit": { + "state": "translated", + "value": "Paused" } } } }, - "downloads.perSecond %@" : { - "comment" : "Tempo einer laufenden Übertragung", - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%@/s" + "downloads.perSecond %@": { + "comment": "Tempo einer laufenden Übertragung", + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "%@/s" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%@/s" + "en": { + "stringUnit": { + "state": "translated", + "value": "%@/s" } } } }, - "downloads.quality.approx %@ %@" : { - "comment" : "Platzhalter: Stufenname, geschätzte Größe", - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%@ · etwa %@" + "downloads.quality.approx %@ %@": { + "comment": "Platzhalter: Stufenname, geschätzte Größe", + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "%@ · etwa %@" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%@ · about %@" + "en": { + "stringUnit": { + "state": "translated", + "value": "%@ · about %@" } } } }, - "downloads.quality.hint" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Niedrigere Stufen rechnet der Server unterwegs herunter. Das dauert länger, spart aber deutlich Platz." + "downloads.quality.hint": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Niedrigere Stufen rechnet der Server unterwegs herunter. Das dauert länger, spart aber deutlich Platz." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Lower levels are transcoded by the server. That takes longer but saves a lot of space." + "en": { + "stringUnit": { + "state": "translated", + "value": "Lower levels are transcoded by the server. That takes longer but saves a lot of space." } } } }, - "downloads.quality.title" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "In welcher Qualität?" + "downloads.quality.title": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "In welcher Qualität?" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Which quality?" + "en": { + "stringUnit": { + "state": "translated", + "value": "Which quality?" } } } }, - "downloads.queued" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wartet" + "downloads.queued": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Wartet" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Queued" + "en": { + "stringUnit": { + "state": "translated", + "value": "Queued" } } } }, - "downloads.refused.exists" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Dieser Titel steht bereits in der Warteschlange." + "downloads.refused.exists": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Dieser Titel steht bereits in der Warteschlange." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "This title is already queued." + "en": { + "stringUnit": { + "state": "translated", + "value": "This title is already queued." } } } }, - "downloads.refused.limit %@ %@" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Das Speicherlimit ist erreicht: %@ von %@ belegt." + "downloads.refused.limit %@ %@": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Das Speicherlimit ist erreicht: %@ von %@ belegt." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Storage limit reached: %@ of %@ used." + "en": { + "stringUnit": { + "state": "translated", + "value": "Storage limit reached: %@ of %@ used." } } } }, - "downloads.refused.noMedia" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Zu diesem Titel liegt keine Datei auf dem Server." + "downloads.refused.noMedia": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Zu diesem Titel liegt keine Datei auf dem Server." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "There's no file for this title on the server." + "en": { + "stringUnit": { + "state": "translated", + "value": "There's no file for this title on the server." } } } }, - "downloads.refused.noPart" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Die Datei auf dem Server hat keinen abrufbaren Teil. Möglicherweise wurde sie noch nicht analysiert." + "downloads.refused.noPart": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Die Datei auf dem Server hat keinen abrufbaren Teil. Möglicherweise wurde sie noch nicht analysiert." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The file on the server has no retrievable part. It may not have been analyzed yet." + "en": { + "stringUnit": { + "state": "translated", + "value": "The file on the server has no retrievable part. It may not have been analyzed yet." } } } }, - "downloads.refused.noServer" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Keine Verbindung zum Server." + "downloads.refused.noServer": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Keine Verbindung zum Server." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "No connection to the server." + "en": { + "stringUnit": { + "state": "translated", + "value": "No connection to the server." } } } }, - "downloads.refused.noURL" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Die Download-Adresse ließ sich nicht bilden." + "downloads.refused.noURL": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Die Download-Adresse ließ sich nicht bilden." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The download address couldn't be built." + "en": { + "stringUnit": { + "state": "translated", + "value": "The download address couldn't be built." } } } }, - "downloads.refused.space %@ %@" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Zu wenig Platz auf dem Gerät: %@ nötig, %@ frei." + "downloads.refused.space %@ %@": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Zu wenig Platz auf dem Gerät: %@ nötig, %@ frei." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Not enough space on this device: %@ needed, %@ free." + "en": { + "stringUnit": { + "state": "translated", + "value": "Not enough space on this device: %@ needed, %@ free." } } } }, - "downloads.refused.title" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Download nicht möglich" + "downloads.refused.title": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Download nicht möglich" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Can't download" + "en": { + "stringUnit": { + "state": "translated", + "value": "Can't download" } } } }, - "downloads.remaining %@" : { - "comment" : "Geschätzte Restzeit eines Downloads", - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "noch %@" + "downloads.remaining %@": { + "comment": "Geschätzte Restzeit eines Downloads", + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "noch %@" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%@ left" + "en": { + "stringUnit": { + "state": "translated", + "value": "%@ left" } } } }, - "downloads.remaining.underMinute" : { - "comment" : "Restzeit unter einer Minute — eine Zahl wäre hier genauer, als die Schätzung ist", - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "gleich fertig" + "downloads.remaining.underMinute": { + "comment": "Restzeit unter einer Minute — eine Zahl wäre hier genauer, als die Schätzung ist", + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "gleich fertig" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "almost done" + "en": { + "stringUnit": { + "state": "translated", + "value": "almost done" } } } }, - "downloads.removeConfirm" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Download entfernen?" + "downloads.removeConfirm": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Download entfernen?" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Remove download?" + "en": { + "stringUnit": { + "state": "translated", + "value": "Remove download?" } } } }, - "downloads.resume" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Fortsetzen" + "downloads.resume": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Fortsetzen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Resume" + "en": { + "stringUnit": { + "state": "translated", + "value": "Resume" } } } }, - "downloads.season" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Staffel laden" + "downloads.season": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Staffel laden" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Download season" + "en": { + "stringUnit": { + "state": "translated", + "value": "Download season" } } } }, - "downloads.used" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Belegt" + "downloads.used": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Belegt" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Used" + "en": { + "stringUnit": { + "state": "translated", + "value": "Used" } } } }, - "downloads.wifiOnly" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nur über WLAN laden" + "downloads.wifiOnly": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Nur über WLAN laden" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Download over Wi-Fi only" + "en": { + "stringUnit": { + "state": "translated", + "value": "Download over Wi-Fi only" } } } }, - "episode.long %lld %lld" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Staffel %lld · Folge %lld" + "episode.long %lld %lld": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Staffel %lld · Folge %lld" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Season %lld · Episode %lld" + "en": { + "stringUnit": { + "state": "translated", + "value": "Season %lld · Episode %lld" } } } }, - "episode.number %lld" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Folge %lld" + "episode.number %lld": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Folge %lld" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Episode %lld" + "en": { + "stringUnit": { + "state": "translated", + "value": "Episode %lld" } } } }, - "episode.short %lld %lld" : { - "comment" : "Nur auf Kacheln, wo eine Zeile breit ist. Wo Platz ist — Player, Aufmacher am Fernseher — steht episode.long.", - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "S%lld E%lld" + "episode.short %lld %lld": { + "comment": "Nur auf Kacheln, wo eine Zeile breit ist. Wo Platz ist — Player, Aufmacher am Fernseher — steht episode.long.", + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "S%lld E%lld" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "S%lld E%lld" + "en": { + "stringUnit": { + "state": "translated", + "value": "S%lld E%lld" } } } }, - "error.notFound" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Dieser Titel ist auf dem Server nicht mehr vorhanden." + "error.notFound": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Dieser Titel ist auf dem Server nicht mehr vorhanden." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "This title is no longer on the server." + "en": { + "stringUnit": { + "state": "translated", + "value": "This title is no longer on the server." } } } }, - "error.server %lld" : { - "comment" : "Platzhalter: HTTP-Statuscode", - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Der Server hat mit Fehler %lld geantwortet." + "error.server %lld": { + "comment": "Platzhalter: HTTP-Statuscode", + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Der Server hat mit Fehler %lld geantwortet." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The server responded with error %lld." + "en": { + "stringUnit": { + "state": "translated", + "value": "The server responded with error %lld." } } } }, - "error.serverUnreachable" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Der Server ist nicht erreichbar. Prüfe, ob er läuft und ob du im selben Netzwerk bist." + "error.serverUnreachable": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Der Server ist nicht erreichbar. Prüfe, ob er läuft und ob du im selben Netzwerk bist." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The server can't be reached. Check that it's running and that you're on the same network." + "en": { + "stringUnit": { + "state": "translated", + "value": "The server can't be reached. Check that it's running and that you're on the same network." } } } }, - "error.title" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Das hat nicht geklappt" + "error.title": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Das hat nicht geklappt" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Something went wrong" + "en": { + "stringUnit": { + "state": "translated", + "value": "Something went wrong" } } } }, - "error.unauthorized" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Die Anmeldung ist abgelaufen. Melde dich erneut an." + "error.unauthorized": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Die Anmeldung ist abgelaufen. Melde dich erneut an." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Your sign-in has expired. Please sign in again." + "en": { + "stringUnit": { + "state": "translated", + "value": "Your sign-in has expired. Please sign in again." } } } }, - "error.unexpectedResponse" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Die Antwort des Servers war unerwartet. Möglicherweise läuft dort eine neuere Fassung, als diese App kennt." + "error.unexpectedResponse": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Die Antwort des Servers war unerwartet. Möglicherweise läuft dort eine neuere Fassung, als diese App kennt." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The server's response was unexpected. It may be running a newer version than this app knows." + "en": { + "stringUnit": { + "state": "translated", + "value": "The server's response was unexpected. It may be running a newer version than this app knows." } } } }, - "filter.any" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Alle" + "filter.any": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Alle" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Any" + "en": { + "stringUnit": { + "state": "translated", + "value": "Any" } } } }, - "filter.downloadedOnly" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nur Heruntergeladene" + "filter.downloadedOnly": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Nur Heruntergeladene" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Downloaded only" + "en": { + "stringUnit": { + "state": "translated", + "value": "Downloaded only" } } } }, - "filter.filters" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Filter" + "filter.filters": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Filter" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Filters" + "en": { + "stringUnit": { + "state": "translated", + "value": "Filters" } } } }, - "filter.genre" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Genre" + "filter.genre": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Genre" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Genre" + "en": { + "stringUnit": { + "state": "translated", + "value": "Genre" } } } }, - "filter.librarySettings" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Diese Mediathek" + "filter.librarySettings": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Diese Mediathek" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "This library" + "en": { + "stringUnit": { + "state": "translated", + "value": "This library" } } } }, - "filter.reset" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Zurücksetzen" + "filter.reset": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Zurücksetzen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Reset" + "en": { + "stringUnit": { + "state": "translated", + "value": "Reset" } } } }, - "filter.resolution" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Auflösung" + "filter.resolution": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Auflösung" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Resolution" + "en": { + "stringUnit": { + "state": "translated", + "value": "Resolution" } } } }, - "filter.sort" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sortierung" + "filter.sort": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Sortierung" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sort by" + "en": { + "stringUnit": { + "state": "translated", + "value": "Sort by" } } } }, - "filter.title" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Filter" + "filter.title": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Filter" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Filters" + "en": { + "stringUnit": { + "state": "translated", + "value": "Filters" } } } }, - "filter.unwatchedOnly" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nur Ungesehene" + "filter.unwatchedOnly": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Nur Ungesehene" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Unwatched only" + "en": { + "stringUnit": { + "state": "translated", + "value": "Unwatched only" } } } }, - "Full HD" : { - "comment" : "A label for a media resolution.", - "isCommentAutoGenerated" : true - }, - "HD" : { - "comment" : "A label for a media resolution.", - "isCommentAutoGenerated" : true - }, - "hero.details" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Details" + "hero.details": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Details" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Details" + "en": { + "stringUnit": { + "state": "translated", + "value": "Details" } } } }, - "hero.next" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nächster Vorschlag" + "hero.next": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Nächster Vorschlag" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Next suggestion" + "en": { + "stringUnit": { + "state": "translated", + "value": "Next suggestion" } } } }, - "hero.play" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Abspielen" + "hero.play": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Abspielen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Play" + "en": { + "stringUnit": { + "state": "translated", + "value": "Play" } } } }, - "hero.previous" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Voriger Vorschlag" + "hero.previous": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Voriger Vorschlag" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Previous suggestion" + "en": { + "stringUnit": { + "state": "translated", + "value": "Previous suggestion" } } } }, - "hero.resume" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Weiterschauen" + "hero.resume": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Weiterschauen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Resume" + "en": { + "stringUnit": { + "state": "translated", + "value": "Resume" } } } }, - "home.combineContinue" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Weiterschauen und Als Nächstes zusammenlegen" + "home.combineContinue": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Weiterschauen und Als Nächstes zusammenlegen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Merge Continue and Next Up" + "en": { + "stringUnit": { + "state": "translated", + "value": "Merge Continue and Next Up" } } } }, - "home.downloaded" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Heruntergeladen" + "home.downloaded": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Heruntergeladen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Downloaded" + "en": { + "stringUnit": { + "state": "translated", + "value": "Downloaded" } } } }, - "home.empty.description" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sobald dein Server Mediatheken enthält, erscheinen sie hier." + "home.empty.description": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Sobald dein Server Mediatheken enthält, erscheinen sie hier." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Once your server has libraries, they'll show up here." + "en": { + "stringUnit": { + "state": "translated", + "value": "Once your server has libraries, they'll show up here." } } } }, - "home.empty.title" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Noch nichts da" + "home.empty.title": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Noch nichts da" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nothing here yet" + "en": { + "stringUnit": { + "state": "translated", + "value": "Nothing here yet" } } } }, - "home.hero" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Aufmacher" + "home.hero": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Aufmacher" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Featured" + "en": { + "stringUnit": { + "state": "translated", + "value": "Featured" } } } }, - "home.heroCount" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Titel im Wechsel" + "home.heroCount": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Titel im Wechsel" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Titles in rotation" + "en": { + "stringUnit": { + "state": "translated", + "value": "Titles in rotation" } } } }, - "home.heroExplanation" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Der Aufmacher wandert alle sieben Sekunden weiter und hört damit auf, sobald du ihn berührst." + "home.heroExplanation": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Der Aufmacher wandert alle sieben Sekunden weiter und hört damit auf, sobald du ihn berührst." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The featured area moves on every seven seconds and stops as soon as you touch it." + "en": { + "stringUnit": { + "state": "translated", + "value": "The featured area moves on every seven seconds and stops as soon as you touch it." } } } }, - "home.heroHeight" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Höhe des Aufmachers" + "home.heroHeight": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Höhe des Aufmachers" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Featured height" + "en": { + "stringUnit": { + "state": "translated", + "value": "Featured height" } } } }, - "home.heroLibraries" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mediatheken für den Aufmacher" + "home.heroLibraries": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Mediatheken für den Aufmacher" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Libraries for the hero" + "en": { + "stringUnit": { + "state": "translated", + "value": "Libraries for the hero" } } } }, - "home.heroLibrariesExplanation" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ohne Auswahl kommt alles infrage. Ist etwas gewählt, speist sich der Aufmacher nur noch aus diesen Mediatheken — Weiterschauen und Favoriten bleiben dann draussen, weil der Server nicht sagt, aus welcher Mediathek ein angefangener Titel stammt." + "home.heroLibrariesExplanation": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Ohne Auswahl kommt alles infrage. Ist etwas gewählt, speist sich der Aufmacher nur noch aus diesen Mediatheken — Weiterschauen und Favoriten bleiben dann draussen, weil der Server nicht sagt, aus welcher Mediathek ein angefangener Titel stammt." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "With nothing selected, everything is eligible. Once you select libraries, the hero is drawn only from them — Continue Watching and Favourites stay out, because the server does not say which library a part-watched title came from." + "en": { + "stringUnit": { + "state": "translated", + "value": "With nothing selected, everything is eligible. Once you select libraries, the hero is drawn only from them — Continue Watching and Favourites stay out, because the server does not say which library a part-watched title came from." } } } }, - "home.heroLibrariesNoMatch" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Für die gewählten Mediatheken gibt es derzeit keine eigene Reihe auf der Startseite. Die Auswahl greift deshalb nicht — der Aufmacher zeigt alles." + "home.heroLibrariesNoMatch": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Für die gewählten Mediatheken gibt es derzeit keine eigene Reihe auf der Startseite. Die Auswahl greift deshalb nicht — der Aufmacher zeigt alles." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The selected libraries currently have no row of their own on the home screen, so the selection has no effect — the hero shows everything." + "en": { + "stringUnit": { + "state": "translated", + "value": "The selected libraries currently have no row of their own on the home screen, so the selection has no effect — the hero shows everything." } } } }, - "home.heroSource" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Inhalt" + "home.heroSource": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Inhalt" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Content" + "en": { + "stringUnit": { + "state": "translated", + "value": "Content" } } } }, - "home.heroSource.continueFirst" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Angefangenes zuerst" + "home.heroSource.continueFirst": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Angefangenes zuerst" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Started titles first" + "en": { + "stringUnit": { + "state": "translated", + "value": "Started titles first" } } } }, - "home.heroSource.latestOnly" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nur Neuzugänge" + "home.heroSource.latestOnly": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Nur Neuzugänge" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "New arrivals only" + "en": { + "stringUnit": { + "state": "translated", + "value": "New arrivals only" } } } }, - "home.hideWatched" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Gesehenes aus Neuzugängen ausblenden" + "home.hideWatched": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Gesehenes aus Neuzugängen ausblenden" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Hide watched titles from new arrivals" + "en": { + "stringUnit": { + "state": "translated", + "value": "Hide watched titles from new arrivals" } } } }, - "home.offline" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Kein Server erreichbar. Was hier steht, stammt vom letzten Mal — ansehen lässt sich nur, was auf dem Gerät liegt." + "home.offline": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Kein Server erreichbar. Was hier steht, stammt vom letzten Mal — ansehen lässt sich nur, was auf dem Gerät liegt." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "No server reachable. What you see is from last time — only what is on this device can be played." + "en": { + "stringUnit": { + "state": "translated", + "value": "No server reachable. What you see is from last time — only what is on this device can be played." } } } }, - "home.rowsHeader" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Reihen" + "home.rowsHeader": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Reihen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Rows" + "en": { + "stringUnit": { + "state": "translated", + "value": "Rows" } } } }, - "homeLayout.empty" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Der Server liefert derzeit keine Reihen." + "homeLayout.empty": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Der Server liefert derzeit keine Reihen." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The server isn't providing any rows right now." + "en": { + "stringUnit": { + "state": "translated", + "value": "The server isn't providing any rows right now." } } } }, - "homeLayout.explanation" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Schalte Reihen aus, die du nicht brauchst, und sortiere sie über „Bearbeiten“ um. Neue Reihen vom Server erscheinen am Ende." + "homeLayout.explanation": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Schalte Reihen aus, die du nicht brauchst, und sortiere sie über „Bearbeiten“ um. Neue Reihen vom Server erscheinen am Ende." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Turn off rows you don't need and reorder them via Edit. New rows from the server appear at the end." + "en": { + "stringUnit": { + "state": "translated", + "value": "Turn off rows you don't need and reorder them via Edit. New rows from the server appear at the end." } } } }, - "homeLayout.title" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Startseite" + "homeLayout.title": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Startseite" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Home screen" + "en": { + "stringUnit": { + "state": "translated", + "value": "Home screen" } } } }, - "item.watched" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Gesehen" + "item.watched": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Gesehen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Watched" + "en": { + "stringUnit": { + "state": "translated", + "value": "Watched" } } } }, - "languages.audio" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tonspur" + "languages.audio": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Tonspur" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Audio track" + "en": { + "stringUnit": { + "state": "translated", + "value": "Audio track" } } } }, - "languages.audioExplanation" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Gibt es beim Abspielen eine Tonspur in dieser Sprache, wird sie von selbst gewählt. Sonst bleibt es bei der Spur, die der Server voranstellt." + "languages.audioExplanation": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Gibt es beim Abspielen eine Tonspur in dieser Sprache, wird sie von selbst gewählt. Sonst bleibt es bei der Spur, die der Server voranstellt." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "If a track in this language exists, it is selected automatically. Otherwise the server's own first track is used." + "en": { + "stringUnit": { + "state": "translated", + "value": "If a track in this language exists, it is selected automatically. Otherwise the server's own first track is used." } } } }, - "languages.audioHeader" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ton" + "languages.audioHeader": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Ton" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Audio" + "en": { + "stringUnit": { + "state": "translated", + "value": "Audio" } } } }, - "languages.audioShort" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ton" + "languages.audioShort": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Ton" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Audio" + "en": { + "stringUnit": { + "state": "translated", + "value": "Audio" } } } }, - "languages.avoidAudioDescription" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Audiodeskription meiden" + "languages.avoidAudioDescription": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Audiodeskription meiden" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Avoid audio description" + "en": { + "stringUnit": { + "state": "translated", + "value": "Avoid audio description" } } } }, - "languages.avoidAudioDescriptionExplanation" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Spuren, die eine Szenenbeschreibung für Sehbehinderte enthalten, werden nur genommen, wenn es keine andere in dieser Sprache gibt. Erkannt wird das am Namen der Spur — der Server kennzeichnet es nicht." + "languages.avoidAudioDescriptionExplanation": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Spuren, die eine Szenenbeschreibung für Sehbehinderte enthalten, werden nur genommen, wenn es keine andere in dieser Sprache gibt. Erkannt wird das am Namen der Spur — der Server kennzeichnet es nicht." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tracks that describe the scene for blind viewers are only used when there is no other track in that language. They are recognised by name — the server doesn't flag them." + "en": { + "stringUnit": { + "state": "translated", + "value": "Tracks that describe the scene for blind viewers are only used when there is no other track in that language. They are recognised by name — the server doesn't flag them." } } } }, - "languages.fallbackAdd" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sprache hinzufügen" + "languages.fallbackAdd": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Sprache hinzufügen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Add language" + "en": { + "stringUnit": { + "state": "translated", + "value": "Add language" } } } }, - "languages.fallbackEmpty" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Keine Ausweichsprachen" + "languages.fallbackEmpty": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Keine Ausweichsprachen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "No fallback languages" + "en": { + "stringUnit": { + "state": "translated", + "value": "No fallback languages" } } } }, - "languages.fallbackExplanation" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Gilt nur, wenn ein Titel die gewünschte Tonsprache gar nicht hat. Dann wird von oben nach unten gesucht." + "languages.fallbackExplanation": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Gilt nur, wenn ein Titel die gewünschte Tonsprache gar nicht hat. Dann wird von oben nach unten gesucht." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Applies only when a title doesn't have your preferred audio language at all. Then it works down the list." + "en": { + "stringUnit": { + "state": "translated", + "value": "Applies only when a title doesn't have your preferred audio language at all. Then it works down the list." } } } }, - "languages.fallbackHeader" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Rangliste" + "languages.fallbackHeader": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Rangliste" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Fallback order" + "en": { + "stringUnit": { + "state": "translated", + "value": "Fallback order" } } } }, - "languages.forThisLibrary" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tonsprache für diese Mediathek" + "languages.forThisLibrary": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Tonsprache für diese Mediathek" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Audio language for this library" + "en": { + "stringUnit": { + "state": "translated", + "value": "Audio language for this library" } } } }, - "languages.forThisLibraryExplanation" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Gilt für alle Titel dieser Mediathek. Eine Serie kann es überstimmen, und was du im Player von Hand wählst, überstimmt beides." + "languages.forThisLibraryExplanation": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Gilt für alle Titel dieser Mediathek. Eine Serie kann es überstimmen, und was du im Player von Hand wählst, überstimmt beides." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Applies to every title in this library. A series can override it, and what you pick by hand in the player overrides both." + "en": { + "stringUnit": { + "state": "translated", + "value": "Applies to every title in this library. A series can override it, and what you pick by hand in the player overrides both." } } } }, - "languages.forThisSeries" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tonsprache für diese Serie" + "languages.forThisSeries": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Tonsprache für diese Serie" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Audio language for this series" + "en": { + "stringUnit": { + "state": "translated", + "value": "Audio language for this series" } } } }, - "languages.forThisTitle" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tonsprache für diesen Titel" + "languages.forThisTitle": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Tonsprache für diesen Titel" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Audio language for this title" + "en": { + "stringUnit": { + "state": "translated", + "value": "Audio language for this title" } } } }, - "languages.headerExplanation" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Welche Tonspur und welche Untertitel beim Abspielen von selbst gewählt werden." + "languages.headerExplanation": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Welche Tonspur und welche Untertitel beim Abspielen von selbst gewählt werden." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Which audio track and which subtitles are chosen automatically during playback." + "en": { + "stringUnit": { + "state": "translated", + "value": "Which audio track and which subtitles are chosen automatically during playback." } } } }, - "languages.inherit" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wie allgemein" + "languages.inherit": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Wie allgemein" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "As set generally" + "en": { + "stringUnit": { + "state": "translated", + "value": "As set generally" } } } }, - "languages.none" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Keine" + "languages.none": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Keine" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "None" + "en": { + "stringUnit": { + "state": "translated", + "value": "None" } } } }, - "languages.partial %@" : { - "comment" : "Eine kleine Schriftart, die", - "isCommentAutoGenerated" : true + "languages.partial %@": { + "comment": "Eine kleine Schriftart, die", + "isCommentAutoGenerated": true }, - "languages.partial %lld" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nur Sprachen, die in jeder Folge vorkommen. %lld weitere gibt es nur in einem Teil." + "languages.partial %lld": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Nur Sprachen, die in jeder Folge vorkommen. %lld weitere gibt es nur in einem Teil." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Only languages present in every episode. %lld more exist in some episodes only." + "en": { + "stringUnit": { + "state": "translated", + "value": "Only languages present in every episode. %lld more exist in some episodes only." } } } }, - "languages.serverDefault" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wie der Server vorgibt" + "languages.serverDefault": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Wie der Server vorgibt" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "As the server prefers" + "en": { + "stringUnit": { + "state": "translated", + "value": "As the server prefers" } } } }, - "languages.subtitles" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Untertitel" + "languages.subtitles": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Untertitel" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Subtitles" + "en": { + "stringUnit": { + "state": "translated", + "value": "Subtitles" } } } }, - "languages.subtitlesExplanation" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Untertitel werden nur eingeschaltet, wenn es sie in dieser Sprache gibt. „Aus“ heißt: gar keine, bis du sie im Player selbst einschaltest." + "languages.subtitlesExplanation": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Untertitel werden nur eingeschaltet, wenn es sie in dieser Sprache gibt. „Aus“ heißt: gar keine, bis du sie im Player selbst einschaltest." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Subtitles are only switched on when they exist in this language. “Off” means none until you turn them on in the player." + "en": { + "stringUnit": { + "state": "translated", + "value": "Subtitles are only switched on when they exist in this language. “Off” means none until you turn them on in the player." } } } }, - "languages.subtitlesForThisSeries" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Untertitel für diese Serie" + "languages.subtitlesForThisSeries": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Untertitel für diese Serie" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Subtitles for this series" + "en": { + "stringUnit": { + "state": "translated", + "value": "Subtitles for this series" } } } }, - "languages.subtitlesForThisTitle" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Untertitel für diesen Titel" + "languages.subtitlesForThisTitle": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Untertitel für diesen Titel" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Subtitles for this title" + "en": { + "stringUnit": { + "state": "translated", + "value": "Subtitles for this title" } } } }, - "languages.subtitlesHeader" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Untertitel" + "languages.subtitlesHeader": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Untertitel" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Subtitles" + "en": { + "stringUnit": { + "state": "translated", + "value": "Subtitles" } } } }, - "languages.subtitlesOff" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Aus" + "languages.subtitlesOff": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Aus" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Off" + "en": { + "stringUnit": { + "state": "translated", + "value": "Off" } } } }, - "languages.subtitlesShort" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Untertitel" + "languages.subtitlesShort": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Untertitel" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Subtitles" + "en": { + "stringUnit": { + "state": "translated", + "value": "Subtitles" } } } }, - "languages.title" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ton & Untertitel" + "languages.title": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Ton & Untertitel" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Audio & subtitles" + "en": { + "stringUnit": { + "state": "translated", + "value": "Audio & subtitles" } } } }, - "library.allLoaded %@" : { - "comment" : "A footer in the library view that shows the number of items in the library.", - "isCommentAutoGenerated" : true + "library.allLoaded %@": { + "comment": "A footer in the library view that shows the number of items in the library.", + "isCommentAutoGenerated": true }, - "library.allLoaded %lld" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Alle %lld Titel geladen" + "library.allLoaded %lld": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Alle %lld Titel geladen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "All %lld titles loaded" + "en": { + "stringUnit": { + "state": "translated", + "value": "All %lld titles loaded" } } } }, - "library.clearFilters" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Filter zurücksetzen" + "library.clearFilters": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Filter zurücksetzen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Clear filters" + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear filters" } } } }, - "library.empty.description" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mit den aktuellen Filtern bleibt nichts übrig." + "library.empty.description": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Mit den aktuellen Filtern bleibt nichts übrig." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nothing matches the current filters." + "en": { + "stringUnit": { + "state": "translated", + "value": "Nothing matches the current filters." } } } }, - "library.empty.title" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Keine Titel" + "library.empty.title": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Keine Titel" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "No titles" + "en": { + "stringUnit": { + "state": "translated", + "value": "No titles" } } } }, - "library.filters" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Filter" + "library.filters": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Filter" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Filters" + "en": { + "stringUnit": { + "state": "translated", + "value": "Filters" } } } }, - "library.kind.liveTV" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Live-TV" + "library.kind.liveTV": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Live-TV" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Live TV" + "en": { + "stringUnit": { + "state": "translated", + "value": "Live TV" } } } }, - "library.kind.movie" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Filme" + "library.kind.movie": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Filme" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Movies" + "en": { + "stringUnit": { + "state": "translated", + "value": "Movies" } } } }, - "library.kind.music" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Musik" + "library.kind.music": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Musik" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Music" + "en": { + "stringUnit": { + "state": "translated", + "value": "Music" } } } }, - "library.kind.other" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sonstiges" + "library.kind.other": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Sonstiges" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Other" + "en": { + "stringUnit": { + "state": "translated", + "value": "Other" } } } }, - "library.kind.photo" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Fotos" + "library.kind.photo": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Fotos" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Photos" + "en": { + "stringUnit": { + "state": "translated", + "value": "Photos" } } } }, - "library.kind.show" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Serien" + "library.kind.show": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Serien" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "TV shows" + "en": { + "stringUnit": { + "state": "translated", + "value": "TV shows" } } } }, - "library.loadMore" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Weitere laden" + "library.loadMore": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Weitere laden" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Load more" + "en": { + "stringUnit": { + "state": "translated", + "value": "Load more" } } } }, - "library.loadMoreFailed" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Das Nachladen ist hängen geblieben — es fehlen noch Titel." + "library.loadMoreFailed": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Das Nachladen ist hängen geblieben — es fehlen noch Titel." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Loading more stalled — some titles are still missing." + "en": { + "stringUnit": { + "state": "translated", + "value": "Loading more stalled — some titles are still missing." } } } }, - "library.reload" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Neu laden" + "library.reload": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Neu laden" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Reload" + "en": { + "stringUnit": { + "state": "translated", + "value": "Reload" } } } }, - "library.scan" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mediathek durchsuchen" + "library.scan": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Mediathek durchsuchen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Scan library" + "en": { + "stringUnit": { + "state": "translated", + "value": "Scan library" } } } }, - "library.search" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Suchen" + "library.search": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Suchen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Search" + "en": { + "stringUnit": { + "state": "translated", + "value": "Search" } } } }, - "library.title" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mediatheken" + "library.title": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Mediatheken" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Libraries" + "en": { + "stringUnit": { + "state": "translated", + "value": "Libraries" } } } }, - "library.toggleLayout" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ansicht wechseln" + "library.toggleLayout": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Ansicht wechseln" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Switch layout" + "en": { + "stringUnit": { + "state": "translated", + "value": "Switch layout" } } } }, - "libraryHub.empty" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Auf diesem Server ist keine Mediathek zu sehen." + "libraryHub.empty": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Auf diesem Server ist keine Mediathek zu sehen." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "No library is visible on this server." + "en": { + "stringUnit": { + "state": "translated", + "value": "No library is visible on this server." } } } }, - "liveTV.alreadyScheduled" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Zur Aufnahme vorgemerkt" + "liveTV.alreadyScheduled": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Zur Aufnahme vorgemerkt" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Scheduled to record" + "en": { + "stringUnit": { + "state": "translated", + "value": "Scheduled to record" } } } }, - "liveTV.anyChannel" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Auf allen Sendern" + "liveTV.anyChannel": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Auf allen Sendern" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "On any channel" + "en": { + "stringUnit": { + "state": "translated", + "value": "On any channel" } } } }, - "liveTV.cancelTimer" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Absagen" + "liveTV.cancelTimer": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Absagen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Cancel" + "en": { + "stringUnit": { + "state": "translated", + "value": "Cancel" } } } }, - "liveTV.channelCount %@" : { - "comment" : "A label displaying the number of channels. The argument is the number of channels.", - "isCommentAutoGenerated" : true + "liveTV.channelCount %@": { + "comment": "A label displaying the number of channels. The argument is the number of channels.", + "isCommentAutoGenerated": true }, - "liveTV.channelCount %lld" : { - "comment" : "Platzhalter: Anzahl Sender", - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld Sender" + "liveTV.channelCount %lld": { + "comment": "Platzhalter: Anzahl Sender", + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "%lld Sender" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld channels" + "en": { + "stringUnit": { + "state": "translated", + "value": "%lld channels" } } } }, - "liveTV.deleteRecording.confirm" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Aufnahme löschen?" + "liveTV.deleteRecording.confirm": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Aufnahme löschen?" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Delete recording?" + "en": { + "stringUnit": { + "state": "translated", + "value": "Delete recording?" } } } }, - "liveTV.gap" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Keine Angaben" + "liveTV.gap": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Keine Angaben" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "No data" + "en": { + "stringUnit": { + "state": "translated", + "value": "No data" } } } }, - "liveTV.mode" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ansicht" + "liveTV.mode": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Ansicht" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "View" + "en": { + "stringUnit": { + "state": "translated", + "value": "View" } } } }, - "liveTV.mode.guide" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Programm" + "liveTV.mode.guide": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Programm" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Guide" + "en": { + "stringUnit": { + "state": "translated", + "value": "Guide" } } } }, - "liveTV.mode.now" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Jetzt" + "liveTV.mode.now": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Jetzt" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Now" + "en": { + "stringUnit": { + "state": "translated", + "value": "Now" } } } }, - "liveTV.next %@ %@" : { - "comment" : "Platzhalter: Uhrzeit, Sendungstitel", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Danach %@ · %@" + "liveTV.next %@ %@": { + "comment": "Platzhalter: Uhrzeit, Sendungstitel", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Danach %@ · %@" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Next %@ · %@" + "en": { + "stringUnit": { + "state": "translated", + "value": "Next %@ · %@" } } } }, - "liveTV.noChannels.description" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Der Empfänger ist eingerichtet, liefert aber keine Sender." + "liveTV.noChannels.description": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Der Empfänger ist eingerichtet, liefert aber keine Sender." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The tuner is set up but isn't providing any channels." + "en": { + "stringUnit": { + "state": "translated", + "value": "The tuner is set up but isn't providing any channels." } } } }, - "liveTV.noChannels.title" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Keine Sender" + "liveTV.noChannels.title": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Keine Sender" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "No channels" + "en": { + "stringUnit": { + "state": "translated", + "value": "No channels" } } } }, - "liveTV.noProgram" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Kein Programm verfügbar" + "liveTV.noProgram": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Kein Programm verfügbar" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "No guide data" + "en": { + "stringUnit": { + "state": "translated", + "value": "No guide data" } } } }, - "liveTV.noStream" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Der Server hat keine Adresse für diesen Sender geliefert." + "liveTV.noStream": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Der Server hat keine Adresse für diesen Sender geliefert." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The server did not return a stream for this channel." + "en": { + "stringUnit": { + "state": "translated", + "value": "The server did not return a stream for this channel." } } } }, - "liveTV.notConfigured.description" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Live-TV braucht eine Fernsehkarte oder einen Netzwerk-Tuner am Server. Richte ihn in den Servereinstellungen ein, dann erscheinen die Sender hier." + "liveTV.notConfigured.description": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Live-TV braucht eine Fernsehkarte oder einen Netzwerk-Tuner am Server. Richte ihn in den Servereinstellungen ein, dann erscheinen die Sender hier." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Live TV needs a tuner card or network tuner on your server. Set one up in the server settings and channels will appear here." + "en": { + "stringUnit": { + "state": "translated", + "value": "Live TV needs a tuner card or network tuner on your server. Set one up in the server settings and channels will appear here." } } } }, - "liveTV.notConfigured.title" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Kein Empfänger eingerichtet" + "liveTV.notConfigured.title": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Kein Empfänger eingerichtet" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "No tuner set up" + "en": { + "stringUnit": { + "state": "translated", + "value": "No tuner set up" } } } }, - "liveTV.now" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Jetzt" + "liveTV.now": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Jetzt" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Now" + "en": { + "stringUnit": { + "state": "translated", + "value": "Now" } } } }, - "liveTV.record" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Aufnehmen" + "liveTV.record": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Aufnehmen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Record" + "en": { + "stringUnit": { + "state": "translated", + "value": "Record" } } } }, - "liveTV.recording.inProgress" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nimmt auf" + "liveTV.recordSeries": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Serie aufnehmen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Recording" + "en": { + "stringUnit": { + "state": "translated", + "value": "Record series" } } } }, - "liveTV.recordings" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Aufnahmen" + "liveTV.recording.inProgress": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Nimmt auf" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Recordings" + "en": { + "stringUnit": { + "state": "translated", + "value": "Recording" } } } }, - "liveTV.recordings.empty.description" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Im Programm eine Sendung antippen und „Aufnehmen“ wählen — sie steht dann hier." + "liveTV.recordings": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Aufnahmen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tap a programme in the guide and choose “Record” — it will show up here." + "en": { + "stringUnit": { + "state": "translated", + "value": "Recordings" } } } }, - "liveTV.recordings.empty.title" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Noch nichts aufgenommen" + "liveTV.recordings.empty.description": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Im Programm eine Sendung antippen und „Aufnehmen“ wählen — sie steht dann hier." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nothing recorded yet" + "en": { + "stringUnit": { + "state": "translated", + "value": "Tap a programme in the guide and choose “Record” — it will show up here." } } } }, - "liveTV.recordSeries" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Serie aufnehmen" + "liveTV.recordings.empty.title": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Noch nichts aufgenommen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Record series" + "en": { + "stringUnit": { + "state": "translated", + "value": "Nothing recorded yet" } } } }, - "liveTV.runningNow" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Läuft gerade" + "liveTV.runningNow": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Läuft gerade" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "On now" + "en": { + "stringUnit": { + "state": "translated", + "value": "On now" } } } }, - "liveTV.scheduled" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Geplant" + "liveTV.scheduled": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Geplant" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Scheduled" + "en": { + "stringUnit": { + "state": "translated", + "value": "Scheduled" } } } }, - "liveTV.selection" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sender auswählen" + "liveTV.selection": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Sender auswählen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Choose channels" + "en": { + "stringUnit": { + "state": "translated", + "value": "Choose channels" } } } }, - "liveTV.selection.all" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Alle" + "liveTV.selection.all": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Alle" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "All" + "en": { + "stringUnit": { + "state": "translated", + "value": "All" } } } }, - "liveTV.selection.hint" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ohne Auswahl werden alle Sender gezeigt." + "liveTV.selection.hint": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Ohne Auswahl werden alle Sender gezeigt." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "With nothing selected, all channels are shown." + "en": { + "stringUnit": { + "state": "translated", + "value": "With nothing selected, all channels are shown." } } } }, - "liveTV.series" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ganze Reihe" + "liveTV.series": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Ganze Reihe" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Whole series" + "en": { + "stringUnit": { + "state": "translated", + "value": "Whole series" } } } }, - "liveTV.timeRange %@ %@" : { - "comment" : "Platzhalter: Anfangs- und Endzeit", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%@ – %@" + "liveTV.timeRange %@ %@": { + "comment": "Platzhalter: Anfangs- und Endzeit", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "%@ – %@" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%@ – %@" + "en": { + "stringUnit": { + "state": "translated", + "value": "%@ – %@" } } } }, - "liveTV.tune" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Einschalten" + "liveTV.tune": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Einschalten" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Watch" + "en": { + "stringUnit": { + "state": "translated", + "value": "Watch" } } } }, - "liveTV.tuneFailed" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sender lässt sich nicht einschalten" + "liveTV.tuneFailed": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Sender lässt sich nicht einschalten" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Cannot tune channel" + "en": { + "stringUnit": { + "state": "translated", + "value": "Cannot tune channel" } } } }, - "miniPlayer.close" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wiedergabe beenden" + "miniPlayer.close": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Wiedergabe beenden" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Stop playback" + "en": { + "stringUnit": { + "state": "translated", + "value": "Stop playback" } } } }, - "miniPlayer.expand" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Auf Vollbild vergrößern" + "miniPlayer.expand": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Auf Vollbild vergrößern" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Expand to full screen" + "en": { + "stringUnit": { + "state": "translated", + "value": "Expand to full screen" } } } }, - "miniPlayer.resize" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Größe ändern" + "miniPlayer.resize": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Größe ändern" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Resize" + "en": { + "stringUnit": { + "state": "translated", + "value": "Resize" } } } }, - "placeholder.description" : { - "comment" : "Platzhalter-Bildschirme", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Dieser Bereich entsteht in einer späteren Phase." + "placeholder.description": { + "comment": "Platzhalter-Bildschirme", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Dieser Bereich entsteht in einer späteren Phase." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "This area is coming in a later phase." + "en": { + "stringUnit": { + "state": "translated", + "value": "This area is coming in a later phase." } } } }, - "player.airPlay" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Auf Gerät übertragen" + "player.airPlay": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Auf Gerät übertragen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Play on another device" + "en": { + "stringUnit": { + "state": "translated", + "value": "Play on another device" } } } }, - "player.audioTracks" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tonspur" + "player.audioTracks": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Tonspur" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Audio" + "en": { + "stringUnit": { + "state": "translated", + "value": "Audio" } } } }, - "player.back10" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "10 Sekunden zurück" + "player.back10": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "10 Sekunden zurück" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Back 10 seconds" + "en": { + "stringUnit": { + "state": "translated", + "value": "Back 10 seconds" } } } }, - "player.buffering" : { - "comment" : "Sprachausgabe für den Abspielknopf, solange auf Daten gewartet wird", - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Lädt …" + "player.buffering": { + "comment": "Sprachausgabe für den Abspielknopf, solange auf Daten gewartet wird", + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Lädt …" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Loading…" + "en": { + "stringUnit": { + "state": "translated", + "value": "Loading…" } } } }, - "player.close" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Player schließen" + "player.close": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Player schließen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Close player" + "en": { + "stringUnit": { + "state": "translated", + "value": "Close player" } } } }, - "player.exitFullScreen" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Vollbild beenden" + "player.exitFullScreen": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Vollbild beenden" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Exit full screen" + "en": { + "stringUnit": { + "state": "translated", + "value": "Exit full screen" } } } }, - "player.fillVideo" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bild füllen" + "player.fillVideo": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Bild füllen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Fill screen" + "en": { + "stringUnit": { + "state": "translated", + "value": "Fill screen" } } } }, - "player.fitVideo" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bild einpassen" + "player.fitVideo": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Bild einpassen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Fit to screen" + "en": { + "stringUnit": { + "state": "translated", + "value": "Fit to screen" } } } }, - "player.forgetRememberedTrack" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nicht mehr merken" + "player.forgetRememberedTrack": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Nicht mehr merken" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Stop remembering" + "en": { + "stringUnit": { + "state": "translated", + "value": "Stop remembering" } } } }, - "player.forward10" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "10 Sekunden vor" + "player.forward10": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "10 Sekunden vor" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Forward 10 seconds" + "en": { + "stringUnit": { + "state": "translated", + "value": "Forward 10 seconds" } } } }, - "player.fullScreen" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Vollbild" + "player.fullScreen": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Vollbild" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Enter full screen" + "en": { + "stringUnit": { + "state": "translated", + "value": "Enter full screen" } } } }, - "player.loadFailed" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Die Datei ließ sich nicht öffnen." + "player.loadFailed": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Die Datei ließ sich nicht öffnen." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The file couldn't be opened." + "en": { + "stringUnit": { + "state": "translated", + "value": "The file couldn't be opened." } } } }, - "player.minimize" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Verkleinern" + "player.minimize": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Verkleinern" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Minimize" + "en": { + "stringUnit": { + "state": "translated", + "value": "Minimize" } } } }, - "player.nextEpisode" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nächste Folge" + "player.nextEpisode": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Nächste Folge" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Next episode" + "en": { + "stringUnit": { + "state": "translated", + "value": "Next episode" } } } }, - "player.noPlayableFile" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Zu diesem Titel gibt es keine abspielbare Datei auf dem Server." + "player.noPlayableFile": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Zu diesem Titel gibt es keine abspielbare Datei auf dem Server." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "There's no playable file for this title on the server." + "en": { + "stringUnit": { + "state": "translated", + "value": "There's no playable file for this title on the server." } } } }, - "player.pause" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Pause" + "player.pause": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Pause" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Pause" + "en": { + "stringUnit": { + "state": "translated", + "value": "Pause" } } } }, - "player.pictureInPicture" : { - "comment" : "Knopf im Player: das Bild in ein kleines Fenster legen", - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bild im Bild" + "player.pictureInPicture": { + "comment": "Knopf im Player: das Bild in ein kleines Fenster legen", + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Bild im Bild" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Picture in Picture" + "en": { + "stringUnit": { + "state": "translated", + "value": "Picture in Picture" } } } }, - "player.play" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Abspielen" + "player.play": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Abspielen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Play" + "en": { + "stringUnit": { + "state": "translated", + "value": "Play" } } } }, - "player.playbackFailed" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Die Wiedergabe wurde unterbrochen." + "player.playbackFailed": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Die Wiedergabe wurde unterbrochen." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Playback was interrupted." + "en": { + "stringUnit": { + "state": "translated", + "value": "Playback was interrupted." } } } }, - "player.position" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Position" + "player.position": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Position" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Position" + "en": { + "stringUnit": { + "state": "translated", + "value": "Position" } } } }, - "player.preparing" : { - "comment" : "Hinweis, während der Player die Datei öffnet", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wird vorbereitet …" + "player.preparing": { + "comment": "Hinweis, während der Player die Datei öffnet", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Wird vorbereitet …" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Preparing…" + "en": { + "stringUnit": { + "state": "translated", + "value": "Preparing…" } } } }, - "player.previousEpisode" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Vorige Folge" + "player.previousEpisode": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Vorige Folge" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Previous episode" + "en": { + "stringUnit": { + "state": "translated", + "value": "Previous episode" } } } }, - "player.quality" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Qualität" + "player.quality": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Qualität" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Quality" + "en": { + "stringUnit": { + "state": "translated", + "value": "Quality" } } } }, - "player.rebuilding" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Stream wird neu aufgebaut …" + "player.rebuilding": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Stream wird neu aufgebaut …" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Rebuilding stream …" + "en": { + "stringUnit": { + "state": "translated", + "value": "Rebuilding stream …" } } } }, - "player.rebuilds" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Stream wird neu aufgebaut" + "player.rebuilds": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Stream wird neu aufgebaut" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Stream will be rebuilt" + "en": { + "stringUnit": { + "state": "translated", + "value": "Stream will be rebuilt" } } } }, - "player.rememberedTrack" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Für diese Serie gemerkt" + "player.rememberedTrack": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Für diese Serie gemerkt" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Remembered for this series" + "en": { + "stringUnit": { + "state": "translated", + "value": "Remembered for this series" } } } }, - "player.rememberedTrackExplanation %@" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Seit du „%@“ gewählt hast, wird diese Spur in jeder Folge genommen." + "player.rememberedTrackExplanation %@": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Seit du „%@“ gewählt hast, wird diese Spur in jeder Folge genommen." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Since you picked “%@”, that track is used for every episode." + "en": { + "stringUnit": { + "state": "translated", + "value": "Since you picked “%@”, that track is used for every episode." } } } }, - "player.restoring" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Stelle wird wiederhergestellt" + "player.restoring": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Stelle wird wiederhergestellt" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Returning to your position" + "en": { + "stringUnit": { + "state": "translated", + "value": "Returning to your position" } } } }, - "player.rotate" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Drehen" + "player.rotate": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Drehen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Rotate" + "en": { + "stringUnit": { + "state": "translated", + "value": "Rotate" } } } }, - "player.skipCredits" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Abspann überspringen" + "player.skipCredits": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Abspann überspringen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Skip credits" + "en": { + "stringUnit": { + "state": "translated", + "value": "Skip credits" } } } }, - "player.skipIntro" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Intro überspringen" + "player.skipIntro": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Intro überspringen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Skip intro" + "en": { + "stringUnit": { + "state": "translated", + "value": "Skip intro" } } } }, - "player.stalled" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Es kommen keine Daten mehr. Der Stream ist abgerissen." + "player.stalled": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Es kommen keine Daten mehr. Der Stream ist abgerissen." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "No more data is arriving. The stream stopped." + "en": { + "stringUnit": { + "state": "translated", + "value": "No more data is arriving. The stream stopped." } } } }, - "player.stalledUnlimited" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Es kommen keine Daten mehr. Die Qualität steht auf „Original\", der Server überträgt also die Datei unverändert — dafür reicht diese Verbindung nicht. Unter Einstellungen → Wiedergabe eine kleinere Stufe wählen." + "player.stalledUnlimited": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Es kommen keine Daten mehr. Die Qualität steht auf „Original\", der Server überträgt also die Datei unverändert — dafür reicht diese Verbindung nicht. Unter Einstellungen → Wiedergabe eine kleinere Stufe wählen." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "No more data is arriving. Quality is set to “Original”, so the server sends the file unchanged — this connection cannot carry that. Pick a smaller step under Settings → Playback." + "en": { + "stringUnit": { + "state": "translated", + "value": "No more data is arriving. Quality is set to “Original”, so the server sends the file unchanged — this connection cannot carry that. Pick a smaller step under Settings → Playback." } } } }, - "player.stop" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Beenden" + "player.stop": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Beenden" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Stop" + "en": { + "stringUnit": { + "state": "translated", + "value": "Stop" } } } }, - "player.subtitlesOff" : { - "comment" : "Eintrag, der die Untertitel abschaltet", - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Aus" + "player.subtitleTracks": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Untertitel" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Off" + "en": { + "stringUnit": { + "state": "translated", + "value": "Subtitles" } } } }, - "player.subtitleTracks" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Untertitel" + "player.subtitlesOff": { + "comment": "Eintrag, der die Untertitel abschaltet", + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Aus" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Subtitles" + "en": { + "stringUnit": { + "state": "translated", + "value": "Off" } } } }, - "player.tracks" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ton und Untertitel" + "player.tracks": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Ton und Untertitel" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Audio and subtitles" + "en": { + "stringUnit": { + "state": "translated", + "value": "Audio and subtitles" } } } }, - "player.transcodeRefused" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Der Server hat die Wiedergabe abgelehnt." + "player.transcodeRefused": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Der Server hat die Wiedergabe abgelehnt." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The server refused playback." + "en": { + "stringUnit": { + "state": "translated", + "value": "The server refused playback." } } } }, - "quality.audioOnly" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nur Ton" + "quality.audioOnly": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Nur Ton" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Audio only" + "en": { + "stringUnit": { + "state": "translated", + "value": "Audio only" } } } }, - "quality.audioOnly.hint" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Der Server kennt für Filme keinen reinen Tonstrom. Diese Stufe überträgt das Bild in der kleinsten Auflösung und blendet es aus." + "quality.audioOnly.hint": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Der Server kennt für Filme keinen reinen Tonstrom. Diese Stufe überträgt das Bild in der kleinsten Auflösung und blendet es aus." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The server has no audio-only stream for video. This setting sends the picture at the smallest size and hides it." + "en": { + "stringUnit": { + "state": "translated", + "value": "The server has no audio-only stream for video. This setting sends the picture at the smallest size and hides it." } } } }, - "quality.audioShort" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ton" + "quality.audioShort": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Ton" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Audio" + "en": { + "stringUnit": { + "state": "translated", + "value": "Audio" } } } }, - "quality.automatic" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Automatisch" + "quality.autoShort": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Auto" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Automatic" + "en": { + "stringUnit": { + "state": "translated", + "value": "Auto" } } } }, - "quality.autoShort" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Auto" + "quality.automatic": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Automatisch" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Auto" + "en": { + "stringUnit": { + "state": "translated", + "value": "Automatic" } } } }, - "quality.current" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Läuft gerade" + "quality.current": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Läuft gerade" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Currently playing" + "en": { + "stringUnit": { + "state": "translated", + "value": "Currently playing" } } } }, - "quality.current.bitrate" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bitrate" + "quality.current.bitrate": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Bitrate" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bitrate" + "en": { + "stringUnit": { + "state": "translated", + "value": "Bitrate" } } } }, - "quality.current.buffer" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Puffer" + "quality.current.buffer": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Puffer" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Buffer" + "en": { + "stringUnit": { + "state": "translated", + "value": "Buffer" } } } }, - "quality.current.dropped" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Verworfene Bilder" + "quality.current.dropped": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Verworfene Bilder" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Dropped frames" + "en": { + "stringUnit": { + "state": "translated", + "value": "Dropped frames" } } } }, - "quality.current.hint" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Gemessen, nicht gewünscht: die Stufe sagt, was erlaubt ist — hier steht, was ankommt." + "quality.current.hint": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Gemessen, nicht gewünscht: die Stufe sagt, was erlaubt ist — hier steht, was ankommt." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Measured, not requested: the setting caps the rate, this is what arrives." + "en": { + "stringUnit": { + "state": "translated", + "value": "Measured, not requested: the setting caps the rate, this is what arrives." } } } }, - "quality.current.noLadder" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Dieser Stream hat nur eine Stufe — es gibt nichts, worauf der Player ausweichen könnte. Bei Rücklern hilft hier nur eine kleinere Qualität von Hand." + "quality.current.noLadder": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Dieser Stream hat nur eine Stufe — es gibt nichts, worauf der Player ausweichen könnte. Bei Rücklern hilft hier nur eine kleinere Qualität von Hand." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "This stream has a single variant — there is nothing for the player to fall back to. If it stutters, only a manually chosen lower quality helps." + "en": { + "stringUnit": { + "state": "translated", + "value": "This stream has a single variant — there is nothing for the player to fall back to. If it stutters, only a manually chosen lower quality helps." } } } }, - "quality.current.prefetch" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Eigener Vorrat" + "quality.current.prefetch": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Eigener Vorrat" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Own prefetch" + "en": { + "stringUnit": { + "state": "translated", + "value": "Own prefetch" } } } }, - "quality.current.resolution" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Auflösung" + "quality.current.resolution": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Auflösung" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Resolution" + "en": { + "stringUnit": { + "state": "translated", + "value": "Resolution" } } } }, - "quality.current.stalls" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Rückler" + "quality.current.stalls": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Rückler" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Stalls" + "en": { + "stringUnit": { + "state": "translated", + "value": "Stalls" } } } }, - "quality.current.variants" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Stufen in der Playlist" + "quality.current.variants": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Stufen in der Playlist" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Variants in playlist" + "en": { + "stringUnit": { + "state": "translated", + "value": "Variants in playlist" } } } }, - "quality.everyRung" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Alle Stufen" + "quality.everyRung": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Alle Stufen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "All steps" + "en": { + "stringUnit": { + "state": "translated", + "value": "All steps" } } } }, - "quality.fullHD" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "1080p" + "quality.fullHD": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "1080p" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "1080p" + "en": { + "stringUnit": { + "state": "translated", + "value": "1080p" } } } }, - "quality.hd" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "720p" + "quality.hd": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "720p" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "720p" + "en": { + "stringUnit": { + "state": "translated", + "value": "720p" } } } }, - "quality.high" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Hoch (12 Mbit/s)" + "quality.high": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Hoch (12 Mbit/s)" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "High (12 Mbps)" + "en": { + "stringUnit": { + "state": "translated", + "value": "High (12 Mbps)" } } } }, - "quality.kilobits %lld" : { - "comment" : "Platzhalter: Kilobit je Sekunde", - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld kbps" + "quality.kilobits %lld": { + "comment": "Platzhalter: Kilobit je Sekunde", + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "%lld kbps" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld kbps" + "en": { + "stringUnit": { + "state": "translated", + "value": "%lld kbps" } } } }, - "quality.low" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Niedrig (4 Mbit/s)" + "quality.low": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Niedrig (4 Mbit/s)" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Low (4 Mbps)" + "en": { + "stringUnit": { + "state": "translated", + "value": "Low (4 Mbps)" } } } }, - "quality.lowRes" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "480p" + "quality.lowRes": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "480p" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "480p" + "en": { + "stringUnit": { + "state": "translated", + "value": "480p" } } } }, - "quality.medium" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mittel (8 Mbit/s)" + "quality.medium": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Mittel (8 Mbit/s)" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Medium (8 Mbps)" + "en": { + "stringUnit": { + "state": "translated", + "value": "Medium (8 Mbps)" } } } }, - "quality.megabits %@" : { - "comment" : "Platzhalter: Megabit je Sekunde", - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%@ Mbps" + "quality.megabits %@": { + "comment": "Platzhalter: Megabit je Sekunde", + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "%@ Mbps" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%@ Mbps" + "en": { + "stringUnit": { + "state": "translated", + "value": "%@ Mbps" } } } }, - "quality.minimal" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sparsam (2 Mbit/s)" + "quality.minimal": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Sparsam (2 Mbit/s)" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Data saver (2 Mbps)" + "en": { + "stringUnit": { + "state": "translated", + "value": "Data saver (2 Mbps)" } } } }, - "quality.original" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Original" + "quality.original": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Original" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Original" + "en": { + "stringUnit": { + "state": "translated", + "value": "Original" } } } }, - "quality.originalShort" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Max" + "quality.originalShort": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Max" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Max" + "en": { + "stringUnit": { + "state": "translated", + "value": "Max" } } } }, - "quality.prefetch.ahead %lld" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld s voraus" + "quality.prefetch.ahead %lld": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "%lld s voraus" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld s ahead" + "en": { + "stringUnit": { + "state": "translated", + "value": "%lld s ahead" } } } }, - "quality.prefetch.off" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "aus" + "quality.prefetch.off": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "aus" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "off" + "en": { + "stringUnit": { + "state": "translated", + "value": "off" } } } }, - "quality.prefetch.starting" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "läuft an" + "quality.prefetch.starting": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "läuft an" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "starting" + "en": { + "stringUnit": { + "state": "translated", + "value": "starting" } } } }, - "quality.seconds %@" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%@ s" + "quality.seconds %@": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "%@ s" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%@ s" + "en": { + "stringUnit": { + "state": "translated", + "value": "%@ s" } } } }, - "quality.stalls %lld %@" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld · %@ gewartet" + "quality.stalls %lld %@": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "%lld · %@ gewartet" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld · %@ waiting" + "en": { + "stringUnit": { + "state": "translated", + "value": "%lld · %@ waiting" } } } }, - "quickPlay.failed" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Abspielen nicht möglich" + "quickPlay.failed": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Abspielen nicht möglich" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Can’t play this" + "en": { + "stringUnit": { + "state": "translated", + "value": "Can’t play this" } } } }, - "quickPlay.noServer" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Keine Verbindung zum Server." + "quickPlay.noServer": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Keine Verbindung zum Server." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Not connected to the server." + "en": { + "stringUnit": { + "state": "translated", + "value": "Not connected to the server." } } } }, - "quickPlay.nothingToPlay" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Hier ist keine Folge zu finden, die sich abspielen lässt." + "quickPlay.nothingToPlay": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Hier ist keine Folge zu finden, die sich abspielen lässt." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "There’s no episode here that could be played." + "en": { + "stringUnit": { + "state": "translated", + "value": "There’s no episode here that could be played." } } } }, - "runtime.hoursMinutes %lld %lld" : { - "comment" : "Laufzeit mit Stunden", - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld Std %lld Min" + "runtime.hoursMinutes %lld %lld": { + "comment": "Laufzeit mit Stunden", + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "%lld Std %lld Min" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lldh %lldm" + "en": { + "stringUnit": { + "state": "translated", + "value": "%lldh %lldm" } } } }, - "runtime.minutes %lld" : { - "comment" : "Laufzeit unter einer Stunde", - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld Min" + "runtime.minutes %lld": { + "comment": "Laufzeit unter einer Stunde", + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "%lld Min" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lldm" + "en": { + "stringUnit": { + "state": "translated", + "value": "%lldm" } } } }, - "SD" : { - "comment" : "A label for a 720p movie.", - "isCommentAutoGenerated" : true - }, - "search.forgetRecent" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Verlauf löschen" + "search.forgetRecent": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Verlauf löschen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Clear history" + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear history" } } } }, - "search.hint" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Suche nach Filmen, Serien, Folgen und Personen." + "search.hint": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Suche nach Filmen, Serien, Folgen und Personen." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Search for movies, shows, episodes and people." + "en": { + "stringUnit": { + "state": "translated", + "value": "Search for movies, shows, episodes and people." } } } }, - "search.noResults" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nichts gefunden." + "search.noResults": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Nichts gefunden." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nothing found." + "en": { + "stringUnit": { + "state": "translated", + "value": "Nothing found." } } } }, - "search.recent" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Zuletzt gesucht" + "search.recent": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Zuletzt gesucht" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Recent searches" + "en": { + "stringUnit": { + "state": "translated", + "value": "Recent searches" } } } }, - "search.title" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Suche" + "search.title": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Suche" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Search" + "en": { + "stringUnit": { + "state": "translated", + "value": "Search" } } } }, - "season.number %lld" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Staffel %lld" + "season.number %lld": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Staffel %lld" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Season %lld" + "en": { + "stringUnit": { + "state": "translated", + "value": "Season %lld" } } } }, - "season.short %@" : { - "comment" : "A label that shows the number of the selected season.", - "isCommentAutoGenerated" : true + "season.short %@": { + "comment": "A label that shows the number of the selected season.", + "isCommentAutoGenerated": true }, - "season.short %lld" : { - "comment" : "Platzhalter: Staffelnummer", - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Staffel %lld" + "season.short %lld": { + "comment": "Platzhalter: Staffelnummer", + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Staffel %lld" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Season %lld" + "en": { + "stringUnit": { + "state": "translated", + "value": "Season %lld" } } } }, - "settings.account" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Konto" + "settings.account": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Konto" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Account" + "en": { + "stringUnit": { + "state": "translated", + "value": "Account" } } } }, - "settings.address" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Adresse" + "settings.address": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Adresse" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Address" + "en": { + "stringUnit": { + "state": "translated", + "value": "Address" } } } }, - "settings.app" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "App" + "settings.app": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "App" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "App" + "en": { + "stringUnit": { + "state": "translated", + "value": "App" } } } }, - "settings.autoPlayNext" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nächste Folge automatisch" + "settings.autoPlayNext": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Nächste Folge automatisch" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Play next episode automatically" + "en": { + "stringUnit": { + "state": "translated", + "value": "Play next episode automatically" } } } }, - "settings.autoSkipCredits" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Abspann automatisch überspringen" + "settings.autoSkipCredits": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Abspann automatisch überspringen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Skip credits automatically" + "en": { + "stringUnit": { + "state": "translated", + "value": "Skip credits automatically" } } } }, - "settings.autoSkipIntro" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Intro automatisch überspringen" + "settings.autoSkipIntro": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Intro automatisch überspringen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Skip intros automatically" + "en": { + "stringUnit": { + "state": "translated", + "value": "Skip intros automatically" } } } }, - "settings.build" : { - "comment" : "Zeitpunkt, zu dem der laufende Build entstanden ist", - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Build" + "settings.build": { + "comment": "Zeitpunkt, zu dem der laufende Build entstanden ist", + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Build" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Build" + "en": { + "stringUnit": { + "state": "translated", + "value": "Build" } } } }, - "settings.concurrent" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Höchstens gleichzeitig" + "settings.concurrent": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Höchstens gleichzeitig" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "At most at once" + "en": { + "stringUnit": { + "state": "translated", + "value": "At most at once" } } } }, - "settings.concurrentExplanation" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Eine ganze Staffel auf einmal zu laden teilt die Leitung so auf, dass keine Folge fertig wird. Unterwegs ist eine fertige Folge mehr wert als zehn halbe." + "settings.concurrentExplanation": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Eine ganze Staffel auf einmal zu laden teilt die Leitung so auf, dass keine Folge fertig wird. Unterwegs ist eine fertige Folge mehr wert als zehn halbe." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Loading a whole season at once splits the line so that no episode finishes. On the move, one finished episode beats ten half ones." + "en": { + "stringUnit": { + "state": "translated", + "value": "Loading a whole season at once splits the line so that no episode finishes. On the move, one finished episode beats ten half ones." } } } }, - "settings.concurrentHeader" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Gleichzeitige Downloads" + "settings.concurrentHeader": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Gleichzeitige Downloads" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Concurrent downloads" + "en": { + "stringUnit": { + "state": "translated", + "value": "Concurrent downloads" } } } }, - "settings.connection" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Verbindung" + "settings.connection": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Verbindung" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Connection" + "en": { + "stringUnit": { + "state": "translated", + "value": "Connection" } } } }, - "settings.connectionLocal" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Lokal" + "settings.connectionLocal": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Lokal" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Local" + "en": { + "stringUnit": { + "state": "translated", + "value": "Local" } } } }, - "settings.connectionRemote" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Direkt von außen" + "settings.connectionRemote": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Direkt von außen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Direct remote" + "en": { + "stringUnit": { + "state": "translated", + "value": "Direct remote" } } } }, - "settings.controls" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Steuerung" + "settings.controls": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Steuerung" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Controls" + "en": { + "stringUnit": { + "state": "translated", + "value": "Controls" } } } }, - "settings.controlsExplanation" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Zurück und vor lassen sich getrennt einstellen: zurück springt man, weil man etwas nicht verstanden hat — vor, weil man etwas überspringen will." + "settings.controlsExplanation": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Zurück und vor lassen sich getrennt einstellen: zurück springt man, weil man etwas nicht verstanden hat — vor, weil man etwas überspringen will." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Back and forward are set separately: you jump back because you missed something, and forward because you want to skip something." + "en": { + "stringUnit": { + "state": "translated", + "value": "Back and forward are set separately: you jump back because you missed something, and forward because you want to skip something." } } } }, - "settings.defaultRate" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Startgeschwindigkeit" + "settings.defaultRate": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Startgeschwindigkeit" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Starting speed" + "en": { + "stringUnit": { + "state": "translated", + "value": "Starting speed" } } } }, - "settings.deleteAfterDay" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nach einem Tag" + "settings.deleteAfterDay": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Nach einem Tag" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "After a day" + "en": { + "stringUnit": { + "state": "translated", + "value": "After a day" } } } }, - "settings.deleteAfterWeek" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nach einer Woche" + "settings.deleteAfterWeek": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Nach einer Woche" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "After a week" + "en": { + "stringUnit": { + "state": "translated", + "value": "After a week" } } } }, - "settings.deleteImmediately" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sofort" + "settings.deleteImmediately": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Sofort" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Immediately" + "en": { + "stringUnit": { + "state": "translated", + "value": "Immediately" } } } }, - "settings.deleteNever" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nie" + "settings.deleteNever": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Nie" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Never" + "en": { + "stringUnit": { + "state": "translated", + "value": "Never" } } } }, - "settings.deleteWatched" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Gesehenes löschen" + "settings.deleteWatched": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Gesehenes löschen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Delete watched" + "en": { + "stringUnit": { + "state": "translated", + "value": "Delete watched" } } } }, - "settings.downloadsExplanation" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Fehlende Folgen werden nachgeladen, sobald du eine gesehen hast. Gesehenes wird nach der eingestellten Frist entfernt." + "settings.downloadsExplanation": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Fehlende Folgen werden nachgeladen, sobald du eine gesehen hast. Gesehenes wird nach der eingestellten Frist entfernt." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Missing episodes are downloaded once you finish one. Watched titles are removed after the chosen delay." + "en": { + "stringUnit": { + "state": "translated", + "value": "Missing episodes are downloaded once you finish one. Watched titles are removed after the chosen delay." } } } }, - "settings.downloadsHeaderExplanation" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wie viel Platz Offline-Titel belegen dürfen, wie viele gleichzeitig laden und wann sie wieder verschwinden." + "settings.downloadsHeaderExplanation": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Wie viel Platz Offline-Titel belegen dürfen, wie viele gleichzeitig laden und wann sie wieder verschwinden." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "How much space offline titles may take, how many load at once, and when they disappear again." + "en": { + "stringUnit": { + "state": "translated", + "value": "How much space offline titles may take, how many load at once, and when they disappear again." } } } }, - "settings.episodes" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Folgen" + "settings.episodes": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Folgen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Episodes" + "en": { + "stringUnit": { + "state": "translated", + "value": "Episodes" } } } }, - "settings.forward" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Vorspulen" + "settings.forward": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Vorspulen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Fast-forward" + "en": { + "stringUnit": { + "state": "translated", + "value": "Fast-forward" } } } }, - "settings.keepCount %@" : { - "comment" : "A label that lets the user choose how many episodes of a series to keep after they've finished watching them all. The argument is the number of episodes to keep.", - "isCommentAutoGenerated" : true + "settings.keepCount %@": { + "comment": "A label that lets the user choose how many episodes of a series to keep after they've finished watching them all. The argument is the number of episodes to keep.", + "isCommentAutoGenerated": true }, - "settings.keepCount %lld" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld ungesehene" + "settings.keepCount %lld": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "%lld ungesehene" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld unwatched" + "en": { + "stringUnit": { + "state": "translated", + "value": "%lld unwatched" } } } }, - "settings.keepOff" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Aus" + "settings.keepOff": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Aus" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Off" + "en": { + "stringUnit": { + "state": "translated", + "value": "Off" } } } }, - "settings.keepUnwatched" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Folgen vorhalten" + "settings.keepUnwatched": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Folgen vorhalten" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Keep episodes ready" + "en": { + "stringUnit": { + "state": "translated", + "value": "Keep episodes ready" } } } }, - "settings.libraries" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mediatheken" + "settings.libraries": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Mediatheken" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Libraries" + "en": { + "stringUnit": { + "state": "translated", + "value": "Libraries" } } } }, - "settings.lockLandscape" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Im Querformat sperren" + "settings.lockLandscape": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Im Querformat sperren" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Lock to landscape" + "en": { + "stringUnit": { + "state": "translated", + "value": "Lock to landscape" } } } }, - "settings.lockLandscapeExplanation" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Der Player dreht beim Öffnen ins Querformat und bleibt dort. Abgeschaltet folgt er dem Gerät — für den, der im Liegen schaut." + "settings.lockLandscapeExplanation": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Der Player dreht beim Öffnen ins Querformat und bleibt dort. Abgeschaltet folgt er dem Gerät — für den, der im Liegen schaut." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The player rotates to landscape when it opens and stays there. Switched off it follows the device — for watching while lying down." + "en": { + "stringUnit": { + "state": "translated", + "value": "The player rotates to landscape when it opens and stays there. Switched off it follows the device — for watching while lying down." } } } }, - "settings.playback" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wiedergabe" + "settings.playback": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Wiedergabe" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Playback" + "en": { + "stringUnit": { + "state": "translated", + "value": "Playback" } } } }, - "settings.playbackExplanation" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wie gut das Bild sein soll, wie weit der Doppeltipp springt und was am Ende einer Folge passiert." + "settings.playbackExplanation": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Wie gut das Bild sein soll, wie weit der Doppeltipp springt und was am Ende einer Folge passiert." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "How good the picture should be, how far a double tap jumps, and what happens at the end of an episode." + "en": { + "stringUnit": { + "state": "translated", + "value": "How good the picture should be, how far a double tap jumps, and what happens at the end of an episode." } } } }, - "settings.quality" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Qualität" + "settings.quality": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Qualität" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Quality" + "en": { + "stringUnit": { + "state": "translated", + "value": "Quality" } } } }, - "settings.qualityCellular" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Über Mobilfunk" + "settings.qualityCellular": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Über Mobilfunk" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "On cellular" + "en": { + "stringUnit": { + "state": "translated", + "value": "On cellular" } } } }, - "settings.qualityExplanation" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Die Obergrenze, nicht das Ziel. Im eigenen Netz überträgt der Server das Original; unterwegs beginnt Flaro kleiner und tastet sich hinauf, solange die Verbindung es trägt — höchstens bis zu dieser Stufe. Auflösung und Bitrate gehen dabei zusammen." + "settings.qualityExplanation": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Die Obergrenze, nicht das Ziel. Im eigenen Netz überträgt der Server das Original; unterwegs beginnt Flaro kleiner und tastet sich hinauf, solange die Verbindung es trägt — höchstens bis zu dieser Stufe. Auflösung und Bitrate gehen dabei zusammen." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "An upper limit, not a target. On your own network the server sends the original; away from home Flaro starts lower and works its way up as long as the connection holds — never above this step. Resolution and bitrate move together." + "en": { + "stringUnit": { + "state": "translated", + "value": "An upper limit, not a target. On your own network the server sends the original; away from home Flaro starts lower and works its way up as long as the connection holds — never above this step. Resolution and bitrate move together." } } } }, - "settings.qualityUnlimitedHint" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Zuletzt hat die Wiedergabe gehakt. Ohne Grenze überträgt der Server das Original — gibt die Leitung das nicht her, kann nichts ausweichen. Eine Stufe darunter läuft dann ruhiger." + "settings.qualityUnlimitedHint": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Zuletzt hat die Wiedergabe gehakt. Ohne Grenze überträgt der Server das Original — gibt die Leitung das nicht her, kann nichts ausweichen. Eine Stufe darunter läuft dann ruhiger." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Playback stalled recently. Without a limit the server sends the original — if the connection cannot carry it, there is nothing to fall back to. One step down plays more smoothly." + "en": { + "stringUnit": { + "state": "translated", + "value": "Playback stalled recently. Without a limit the server sends the original — if the connection cannot carry it, there is nothing to fall back to. One step down plays more smoothly." } } } }, - "settings.qualityWifi" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Im WLAN" + "settings.qualityWifi": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Im WLAN" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "On Wi-Fi" + "en": { + "stringUnit": { + "state": "translated", + "value": "On Wi-Fi" } } } }, - "settings.rateNormal" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Normal" + "settings.rateNormal": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Normal" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Normal" + "en": { + "stringUnit": { + "state": "translated", + "value": "Normal" } } } }, - "settings.rememberQuality" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Gewählte Qualität merken" + "settings.rememberQuality": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Gewählte Qualität merken" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Remember the chosen quality" + "en": { + "stringUnit": { + "state": "translated", + "value": "Remember the chosen quality" } } } }, - "settings.rememberQualityExplanation" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Die im Player gewählte Stufe gilt dann auch für den nächsten Titel. Gibt es sie dort nicht, wird die nächstgelegene genommen." + "settings.rememberQualityExplanation": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Die im Player gewählte Stufe gilt dann auch für den nächsten Titel. Gibt es sie dort nicht, wird die nächstgelegene genommen." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The step you pick in the player then applies to the next title as well. If it isn't available there, the closest one is used." + "en": { + "stringUnit": { + "state": "translated", + "value": "The step you pick in the player then applies to the next title as well. If it isn't available there, the closest one is used." } } } }, - "settings.rewind" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Zurückspulen" + "settings.rewind": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Zurückspulen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Rewind" + "en": { + "stringUnit": { + "state": "translated", + "value": "Rewind" } } } }, - "settings.scanExplanation" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Der Suchlauf durchsucht die Ordner auf dem Server nach neuen Dateien. Das kann einige Minuten dauern." + "settings.scanExplanation": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Der Suchlauf durchsucht die Ordner auf dem Server nach neuen Dateien. Das kann einige Minuten dauern." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "A scan looks through the folders on the server for new files. It can take a few minutes." + "en": { + "stringUnit": { + "state": "translated", + "value": "A scan looks through the folders on the server for new files. It can take a few minutes." } } } }, - "settings.seconds %@" : { - "comment" : "Ein Picker, das die Anzahl der Sekunden zeigt, die man zurückgehen kann.", - "isCommentAutoGenerated" : true + "settings.seconds %@": { + "comment": "Ein Picker, das die Anzahl der Sekunden zeigt, die man zurückgehen kann.", + "isCommentAutoGenerated": true + }, + "settings.seconds %lld": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "%lld Sekunden" + } + }, + "en": { + "stringUnit": { + "state": "translated", + "value": "%lld seconds" + } + } + } }, - "settings.seconds %lld" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld Sekunden" + "settings.section.look": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Darstellung" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld seconds" + "en": { + "stringUnit": { + "state": "translated", + "value": "Appearance" } } } }, - "settings.section.look" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Darstellung" + "settings.section.playing": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Wiedergabe & Offline" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Appearance" + "en": { + "stringUnit": { + "state": "translated", + "value": "Playback & offline" } } } }, - "settings.section.playing" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wiedergabe & Offline" + "settings.server": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Server" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Playback & offline" + "en": { + "stringUnit": { + "state": "translated", + "value": "Server" } } } }, - "settings.server" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Server" + "settings.serverName": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Name" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Server" + "en": { + "stringUnit": { + "state": "translated", + "value": "Name" } } } }, - "settings.serverName" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Name" + "settings.showBitrate": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Bitrate im Qualitätsmenü zeigen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Name" + "en": { + "stringUnit": { + "state": "translated", + "value": "Show the bitrate in the quality menu" } } } }, - "settings.showBitrate" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bitrate im Qualitätsmenü zeigen" + "settings.signOutConfirm": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Wirklich abmelden?" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Show the bitrate in the quality menu" + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign out?" } } } }, - "settings.signOutConfirm" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wirklich abmelden?" + "settings.signOutExplanation": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Das Token wird vom Gerät entfernt. Heruntergeladene Titel bleiben erhalten." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sign out?" + "en": { + "stringUnit": { + "state": "translated", + "value": "The token will be removed from this device. Downloaded titles are kept." } } } }, - "settings.signOutExplanation" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Das Token wird vom Gerät entfernt. Heruntergeladene Titel bleiben erhalten." + "settings.skipExtraScenes": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Zusätzliche Szenen mit überspringen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The token will be removed from this device. Downloaded titles are kept." + "en": { + "stringUnit": { + "state": "translated", + "value": "Skip extra scenes too" } } } }, - "settings.storageLimit" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Speicherlimit" + "settings.storageLimit": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Speicherlimit" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Storage limit" + "en": { + "stringUnit": { + "state": "translated", + "value": "Storage limit" } } } }, - "settings.storageUnlimited" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Unbegrenzt" + "settings.storageUnlimited": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Unbegrenzt" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Unlimited" + "en": { + "stringUnit": { + "state": "translated", + "value": "Unlimited" } } } }, - "settings.version" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Version" + "settings.version": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Version" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Version" + "en": { + "stringUnit": { + "state": "translated", + "value": "Version" } } } }, - "sidebar.close" : { - "comment" : "Barrierefreiheit: abgedunkelte Fläche", - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Seitenleiste schließen" + "sidebar.close": { + "comment": "Barrierefreiheit: abgedunkelte Fläche", + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Seitenleiste schließen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Close sidebar" + "en": { + "stringUnit": { + "state": "translated", + "value": "Close sidebar" } } } }, - "sidebar.downloads" : { - "comment" : "Eintrag in der Seitenleiste", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Downloads" + "sidebar.downloads": { + "comment": "Eintrag in der Seitenleiste", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Downloads" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Downloads" + "en": { + "stringUnit": { + "state": "translated", + "value": "Downloads" } } } }, - "sidebar.edit" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bearbeiten" + "sidebar.edit": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Bearbeiten" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Edit" + "en": { + "stringUnit": { + "state": "translated", + "value": "Edit" } } } }, - "sidebar.editHint" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ziehen zum Umsortieren, Haken zum Ein- und Ausblenden." + "sidebar.editHint": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Ziehen zum Umsortieren, Haken zum Ein- und Ausblenden." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Drag to reorder, tap the check to show or hide." + "en": { + "stringUnit": { + "state": "translated", + "value": "Drag to reorder, tap the check to show or hide." } } } }, - "sidebar.hide" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ausblenden" + "sidebar.hide": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Ausblenden" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Hide" + "en": { + "stringUnit": { + "state": "translated", + "value": "Hide" } } } }, - "sidebar.home" : { - "comment" : "Eintrag der Startseite in der Seitenleiste", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Start" + "sidebar.home": { + "comment": "Eintrag der Startseite in der Seitenleiste", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Start" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Home" + "en": { + "stringUnit": { + "state": "translated", + "value": "Home" } } } }, - "sidebar.libraries" : { - "comment" : "Überschrift über der Liste der Mediatheken", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mediatheken" + "sidebar.libraries": { + "comment": "Überschrift über der Liste der Mediatheken", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Mediatheken" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Libraries" + "en": { + "stringUnit": { + "state": "translated", + "value": "Libraries" } } } }, - "sidebar.library" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bibliothek" + "sidebar.library": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Bibliothek" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Library" + "en": { + "stringUnit": { + "state": "translated", + "value": "Library" } } } }, - "sidebar.liveTV" : { - "comment" : "Eintrag in der Seitenleiste", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Live-TV" + "sidebar.liveTV": { + "comment": "Eintrag in der Seitenleiste", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Live-TV" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Live TV" + "en": { + "stringUnit": { + "state": "translated", + "value": "Live TV" } } } }, - "sidebar.more" : { - "comment" : "Überschrift über Live-TV, Downloads und Einstellungen", - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mehr" + "sidebar.more": { + "comment": "Überschrift über Live-TV, Downloads und Einstellungen", + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Mehr" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "More" + "en": { + "stringUnit": { + "state": "translated", + "value": "More" } } } }, - "sidebar.moveToSide" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Navigation zur Seite" + "sidebar.moveToSide": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Navigation zur Seite" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Move navigation to side" + "en": { + "stringUnit": { + "state": "translated", + "value": "Move navigation to side" } } } }, - "sidebar.moveToTop" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Navigation nach oben" + "sidebar.moveToTop": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Navigation nach oben" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Move navigation to top" + "en": { + "stringUnit": { + "state": "translated", + "value": "Move navigation to top" } } } }, - "sidebar.open" : { - "comment" : "Barrierefreiheit: Hamburger-Knopf", - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Seitenleiste öffnen" + "sidebar.open": { + "comment": "Barrierefreiheit: Hamburger-Knopf", + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Seitenleiste öffnen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Open sidebar" + "en": { + "stringUnit": { + "state": "translated", + "value": "Open sidebar" } } } }, - "sidebar.search" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Suchen" + "sidebar.search": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Suchen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Search" + "en": { + "stringUnit": { + "state": "translated", + "value": "Search" } } } }, - "sidebar.settings" : { - "comment" : "Eintrag in der Seitenleiste", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Einstellungen" + "sidebar.settings": { + "comment": "Eintrag in der Seitenleiste", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Einstellungen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Settings" + "en": { + "stringUnit": { + "state": "translated", + "value": "Settings" } } } }, - "sidebar.show" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Einblenden" + "sidebar.show": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Einblenden" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Show" + "en": { + "stringUnit": { + "state": "translated", + "value": "Show" } } } }, - "signIn.cancelled" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Anmeldung abgebrochen." + "signIn.cancelled": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Anmeldung abgebrochen." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sign-in cancelled." + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign-in cancelled." } } } }, - "signIn.flaro.addressHint" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Schema und Port darfst du weglassen — Flaro probiert die üblichen durch." + "signIn.flaro.addressHint": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Schema und Port darfst du weglassen — Flaro probiert die üblichen durch." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "You can leave out the scheme and port — Flaro tries the usual ones." + "en": { + "stringUnit": { + "state": "translated", + "value": "You can leave out the scheme and port — Flaro tries the usual ones." } } } }, - "signIn.flaro.addressMissing" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Trag eine Adresse ein." + "signIn.flaro.addressMissing": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Trag eine Adresse ein." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Enter an address." + "en": { + "stringUnit": { + "state": "translated", + "value": "Enter an address." } } } }, - "signIn.flaro.addressPlaceholder" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "z. B. 192.168.1.50 oder medien.example.com" + "signIn.flaro.addressPlaceholder": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "z. B. 192.168.1.50 oder medien.example.com" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "e.g. 192.168.1.50 or media.example.com" + "en": { + "stringUnit": { + "state": "translated", + "value": "e.g. 192.168.1.50 or media.example.com" } } } }, - "signIn.flaro.addressPrompt" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wo steht dein Server?" + "signIn.flaro.addressPrompt": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Wo steht dein Server?" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Where is your server?" + "en": { + "stringUnit": { + "state": "translated", + "value": "Where is your server?" } } } }, - "signIn.flaro.changeAddress" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Andere Adresse" + "signIn.flaro.changeAddress": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Andere Adresse" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Change address" + "en": { + "stringUnit": { + "state": "translated", + "value": "Change address" } } } }, - "signIn.flaro.codeExpired" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Der Code ist abgelaufen. Fordere einen neuen an." + "signIn.flaro.codeExpired": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Der Code ist abgelaufen. Fordere einen neuen an." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The code has expired. Request a new one." + "en": { + "stringUnit": { + "state": "translated", + "value": "The code has expired. Request a new one." } } } }, - "signIn.flaro.codeHint" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Öffne Flaro auf deinem Telefon und gib diesen Code ein." + "signIn.flaro.codeHint": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Öffne Flaro auf deinem Telefon und gib diesen Code ein." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Open Flaro on your phone and enter this code." + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Flaro on your phone and enter this code." } } } }, - "signIn.flaro.credentialsMissing" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Benutzername und Passwort fehlen." + "signIn.flaro.credentialsMissing": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Benutzername und Passwort fehlen." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Username and password are missing." + "en": { + "stringUnit": { + "state": "translated", + "value": "Username and password are missing." } } } }, - "signIn.flaro.findServer" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Server suchen" + "signIn.flaro.findServer": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Server suchen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Find server" + "en": { + "stringUnit": { + "state": "translated", + "value": "Find server" } } } }, - "signIn.flaro.needsSetup" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Dieser Server ist noch nicht eingerichtet. Öffne ihn im Browser und lege ein Konto an." + "signIn.flaro.needsSetup": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Dieser Server ist noch nicht eingerichtet. Öffne ihn im Browser und lege ein Konto an." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "This server isn't set up yet. Open it in a browser and create an account." + "en": { + "stringUnit": { + "state": "translated", + "value": "This server isn't set up yet. Open it in a browser and create an account." } } } }, - "signIn.flaro.notFound" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Unter dieser Adresse antwortet kein Flaro-Server." + "signIn.flaro.notFound": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Unter dieser Adresse antwortet kein Flaro-Server." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "No Flaro server answers at this address." + "en": { + "stringUnit": { + "state": "translated", + "value": "No Flaro server answers at this address." } } } }, - "signIn.flaro.searching" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Server wird gesucht …" + "signIn.flaro.searching": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Server wird gesucht …" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Looking for the server…" + "en": { + "stringUnit": { + "state": "translated", + "value": "Looking for the server…" } } } }, - "signIn.flaro.signIn" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Anmelden" + "signIn.flaro.signIn": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Anmelden" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sign in" + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign in" } } } }, - "signIn.flaro.signingIn" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wird angemeldet …" + "signIn.flaro.signingIn": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Wird angemeldet …" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Signing in…" + "en": { + "stringUnit": { + "state": "translated", + "value": "Signing in…" } } } }, - "signIn.flaro.title" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Flaro-Server" + "signIn.flaro.title": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Flaro-Server" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Flaro Server" + "en": { + "stringUnit": { + "state": "translated", + "value": "Flaro Server" } } } }, - "signIn.flaro.useCode" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Stattdessen Code" + "signIn.flaro.useCode": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Stattdessen Code" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Use a code instead" + "en": { + "stringUnit": { + "state": "translated", + "value": "Use a code instead" } } } }, - "signIn.flaro.usePassword" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Stattdessen Passwort" + "signIn.flaro.usePassword": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Stattdessen Passwort" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Use a password instead" + "en": { + "stringUnit": { + "state": "translated", + "value": "Use a password instead" } } } }, - "signIn.flaro.waitingForApproval" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Warte auf Bestätigung" + "signIn.flaro.waitingForApproval": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Warte auf Bestätigung" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Waiting for approval" + "en": { + "stringUnit": { + "state": "translated", + "value": "Waiting for approval" } } } }, - "signIn.jellyfin.addressHint" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Schema und Port kannst du weglassen — die üblichen werden durchprobiert." + "signIn.jellyfin.addressHint": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Schema und Port kannst du weglassen — die üblichen werden durchprobiert." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "You can leave out the scheme and port — the usual ones are tried in turn." + "en": { + "stringUnit": { + "state": "translated", + "value": "You can leave out the scheme and port — the usual ones are tried in turn." } } } }, - "signIn.jellyfin.addressMissing" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Trag bitte die Adresse deines Servers ein." + "signIn.jellyfin.addressMissing": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Trag bitte die Adresse deines Servers ein." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Please enter your server's address." + "en": { + "stringUnit": { + "state": "translated", + "value": "Please enter your server's address." } } } }, - "signIn.jellyfin.addressPlaceholder" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "192.168.1.50 oder medien.example.com" + "signIn.jellyfin.addressPlaceholder": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "192.168.1.50 oder medien.example.com" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "192.168.1.50 or media.example.com" + "en": { + "stringUnit": { + "state": "translated", + "value": "192.168.1.50 or media.example.com" } } } }, - "signIn.jellyfin.addressPrompt" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wo steht dein Server?" + "signIn.jellyfin.addressPrompt": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Wo steht dein Server?" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Where is your server?" + "en": { + "stringUnit": { + "state": "translated", + "value": "Where is your server?" } } } }, - "signIn.jellyfin.codeExpired" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Der Code ist abgelaufen. Fordere einen neuen an oder melde dich mit Passwort an." + "signIn.jellyfin.codeExpired": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Der Code ist abgelaufen. Fordere einen neuen an oder melde dich mit Passwort an." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The code has expired. Request a new one or sign in with a password." + "en": { + "stringUnit": { + "state": "translated", + "value": "The code has expired. Request a new one or sign in with a password." } } } }, - "signIn.jellyfin.connect" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Verbinden" + "signIn.jellyfin.connect": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Verbinden" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Connect" + "en": { + "stringUnit": { + "state": "translated", + "value": "Connect" } } } }, - "signIn.jellyfin.foundServer" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Gefundener Server" + "signIn.jellyfin.foundServer": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Gefundener Server" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Server found" + "en": { + "stringUnit": { + "state": "translated", + "value": "Server found" } } } }, - "signIn.jellyfin.notFound" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Unter dieser Adresse antwortet kein Jellyfin-Server." + "signIn.jellyfin.notFound": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Unter dieser Adresse antwortet kein Jellyfin-Server." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "No Jellyfin server answers at that address." + "en": { + "stringUnit": { + "state": "translated", + "value": "No Jellyfin server answers at that address." } } } }, - "signIn.jellyfin.otherAddress" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Andere Adresse eintragen" + "signIn.jellyfin.otherAddress": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Andere Adresse eintragen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Enter a different address" + "en": { + "stringUnit": { + "state": "translated", + "value": "Enter a different address" } } } }, - "signIn.jellyfin.passwordNote" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Das Passwort wird nur für diese eine Anfrage gehalten. Gespeichert wird allein der Zugangsschlüssel, und der im Schlüsselbund." + "signIn.jellyfin.passwordNote": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Das Passwort wird nur für diese eine Anfrage gehalten. Gespeichert wird allein der Zugangsschlüssel, und der im Schlüsselbund." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The password is held for this one request only. Only the access token is stored, and it goes into the keychain." + "en": { + "stringUnit": { + "state": "translated", + "value": "The password is held for this one request only. Only the access token is stored, and it goes into the keychain." } } } }, - "signIn.jellyfin.privacy" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "So erreicht dein Passwort diese App nie." + "signIn.jellyfin.privacy": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "So erreicht dein Passwort diese App nie." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "This way your password never reaches this app." + "en": { + "stringUnit": { + "state": "translated", + "value": "This way your password never reaches this app." } } } }, - "signIn.jellyfin.quickConnectHint" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Öffne Jellyfin auf einem Gerät, an dem du schon angemeldet bist, und gib diesen Code unter „Benutzer → Quick Connect“ ein." + "signIn.jellyfin.quickConnectHint": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Öffne Jellyfin auf einem Gerät, an dem du schon angemeldet bist, und gib diesen Code unter „Benutzer → Quick Connect“ ein." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Open Jellyfin on a device you're already signed in on and enter this code under “User → Quick Connect”." + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Jellyfin on a device you're already signed in on and enter this code under “User → Quick Connect”." } } } }, - "signIn.jellyfin.searching" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Server wird gesucht …" + "signIn.jellyfin.searching": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Server wird gesucht …" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Looking for the server …" + "en": { + "stringUnit": { + "state": "translated", + "value": "Looking for the server …" } } } }, - "signIn.jellyfin.signingIn" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Anmeldung läuft …" + "signIn.jellyfin.signingIn": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Anmeldung läuft …" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Signing in …" + "en": { + "stringUnit": { + "state": "translated", + "value": "Signing in …" } } } }, - "signIn.jellyfin.submit" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Anmelden" + "signIn.jellyfin.submit": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Anmelden" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sign in" + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign in" } } } }, - "signIn.jellyfin.title" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Jellyfin-Server" + "signIn.jellyfin.title": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Jellyfin-Server" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Jellyfin server" + "en": { + "stringUnit": { + "state": "translated", + "value": "Jellyfin server" } } } }, - "signIn.jellyfin.useCode" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Stattdessen einen Code bestätigen" + "signIn.jellyfin.useCode": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Stattdessen einen Code bestätigen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Approve a code instead" + "en": { + "stringUnit": { + "state": "translated", + "value": "Approve a code instead" } } } }, - "signIn.jellyfin.usePassword" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Stattdessen Benutzername und Passwort" + "signIn.jellyfin.usePassword": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Stattdessen Benutzername und Passwort" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Use a username and password instead" + "en": { + "stringUnit": { + "state": "translated", + "value": "Use a username and password instead" } } } }, - "signIn.jellyfin.waitingForApproval" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Warte auf die Bestätigung …" + "signIn.jellyfin.waitingForApproval": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Warte auf die Bestätigung …" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Waiting for approval …" + "en": { + "stringUnit": { + "state": "translated", + "value": "Waiting for approval …" } } } }, - "signIn.password" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Passwort" + "signIn.password": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Passwort" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Password" + "en": { + "stringUnit": { + "state": "translated", + "value": "Password" } } } }, - "signIn.privacy" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Die Anmeldung läuft unmittelbar mit deinem Server. Kein fremder Dienst ist daran beteiligt." + "signIn.privacy": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Die Anmeldung läuft unmittelbar mit deinem Server. Kein fremder Dienst ist daran beteiligt." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "You sign in directly with your own server. No third-party service is involved." + "en": { + "stringUnit": { + "state": "translated", + "value": "You sign in directly with your own server. No third-party service is involved." } } } }, - "signIn.tagline" : { - "comment" : "Untertitel auf dem Anmeldebildschirm", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Deine Mediathek, schöner." + "signIn.tagline": { + "comment": "Untertitel auf dem Anmeldebildschirm", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Deine Mediathek, schöner." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Your library, nicer." + "en": { + "stringUnit": { + "state": "translated", + "value": "Your library, nicer." } } } }, - "signIn.userName" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Benutzername" + "signIn.userName": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Benutzername" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Username" + "en": { + "stringUnit": { + "state": "translated", + "value": "Username" } } } }, - "signIn.withFlaro" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mit Flaro verbinden" + "signIn.withFlaro": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Mit Flaro verbinden" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Connect to Flaro" + "en": { + "stringUnit": { + "state": "translated", + "value": "Connect to Flaro" } } } }, - "signIn.withJellyfin" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mit Jellyfin anmelden" + "signIn.withJellyfin": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Mit Jellyfin anmelden" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sign in with Jellyfin" + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign in with Jellyfin" } } } }, - "sort.lastPlayed" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Zuletzt gesehen" + "sort.lastPlayed": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Zuletzt gesehen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Last watched" + "en": { + "stringUnit": { + "state": "translated", + "value": "Last watched" } } } }, - "sort.rating" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bewertung" + "sort.rating": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Bewertung" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Rating" + "en": { + "stringUnit": { + "state": "translated", + "value": "Rating" } } } }, - "sort.recentlyAdded" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Zuletzt hinzugefügt" + "sort.recentlyAdded": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Zuletzt hinzugefügt" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Recently added" + "en": { + "stringUnit": { + "state": "translated", + "value": "Recently added" } } } }, - "sort.releaseDate" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Erscheinungsjahr" + "sort.releaseDate": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Erscheinungsjahr" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Release date" + "en": { + "stringUnit": { + "state": "translated", + "value": "Release date" } } } }, - "sort.titleAscending" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Titel (A–Z)" + "sort.titleAscending": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Titel (A–Z)" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Title (A–Z)" + "en": { + "stringUnit": { + "state": "translated", + "value": "Title (A–Z)" } } } }, - "sort.titleDescending" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Titel (Z–A)" + "sort.titleDescending": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Titel (Z–A)" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Title (Z–A)" + "en": { + "stringUnit": { + "state": "translated", + "value": "Title (Z–A)" } } } }, - "subtitleStyle.headerExplanation" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Größe und Farbe der Untertitel im Player." + "subtitleStyle.headerExplanation": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Größe und Farbe der Untertitel im Player." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "How subtitles look in the player." + "en": { + "stringUnit": { + "state": "translated", + "value": "How subtitles look in the player." } } } }, - "subtitleStyle.palette" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Darstellung" + "subtitleStyle.palette": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Darstellung" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Appearance" + "en": { + "stringUnit": { + "state": "translated", + "value": "Appearance" } } } }, - "subtitleStyle.palette.blackOnWhite" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Schwarz auf Weiß" + "subtitleStyle.palette.blackOnWhite": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Schwarz auf Weiß" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Black on white" + "en": { + "stringUnit": { + "state": "translated", + "value": "Black on white" } } } }, - "subtitleStyle.palette.whiteOnBlack" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Weiß auf Schwarz" + "subtitleStyle.palette.whiteOnBlack": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Weiß auf Schwarz" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "White on black" + "en": { + "stringUnit": { + "state": "translated", + "value": "White on black" } } } }, - "subtitleStyle.palette.whiteOnClear" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Weiß" + "subtitleStyle.palette.whiteOnClear": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Weiß" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "White" + "en": { + "stringUnit": { + "state": "translated", + "value": "White" } } } }, - "subtitleStyle.palette.yellowOnBlack" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Gelb auf Schwarz" + "subtitleStyle.palette.yellowOnBlack": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Gelb auf Schwarz" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Yellow on black" + "en": { + "stringUnit": { + "state": "translated", + "value": "Yellow on black" } } } }, - "subtitleStyle.palette.yellowOnClear" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Gelb" + "subtitleStyle.palette.yellowOnClear": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Gelb" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Yellow" + "en": { + "stringUnit": { + "state": "translated", + "value": "Yellow" } } } }, - "subtitleStyle.paletteExplanation" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Gilt für den Standardplayer und für Untertitel, die als Text vorliegen. Bilduntertitel von Blu-rays brennt der Server ins Bild — dort gibt es keinen Text zu gestalten, und diese Einstellungen bleiben ohne Wirkung." + "subtitleStyle.paletteExplanation": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Gilt für den Standardplayer und für Untertitel, die als Text vorliegen. Bilduntertitel von Blu-rays brennt der Server ins Bild — dort gibt es keinen Text zu gestalten, und diese Einstellungen bleiben ohne Wirkung." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Applies to the standard player and to subtitles that come as text. Image subtitles from Blu-rays are burned into the picture by the server — there is no text to style, so these settings have no effect there." + "en": { + "stringUnit": { + "state": "translated", + "value": "Applies to the standard player and to subtitles that come as text. Image subtitles from Blu-rays are burned into the picture by the server — there is no text to style, so these settings have no effect there." } } } }, - "subtitleStyle.paletteHeader" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Schrift und Hintergrund" + "subtitleStyle.paletteHeader": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Schrift und Hintergrund" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Text and background" + "en": { + "stringUnit": { + "state": "translated", + "value": "Text and background" } } } }, - "subtitleStyle.previewHeader" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Vorschau" + "subtitleStyle.previewHeader": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Vorschau" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Preview" + "en": { + "stringUnit": { + "state": "translated", + "value": "Preview" } } } }, - "subtitleStyle.previewText" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "So sehen deine Untertitel aus." + "subtitleStyle.previewText": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "So sehen deine Untertitel aus." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "This is how your subtitles look." + "en": { + "stringUnit": { + "state": "translated", + "value": "This is how your subtitles look." } } } }, - "subtitleStyle.size" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Größe" + "subtitleStyle.size": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Größe" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Size" + "en": { + "stringUnit": { + "state": "translated", + "value": "Size" } } } }, - "subtitleStyle.size.huge" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sehr groß" + "subtitleStyle.size.huge": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Sehr groß" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Very large" + "en": { + "stringUnit": { + "state": "translated", + "value": "Very large" } } } }, - "subtitleStyle.size.large" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Groß" + "subtitleStyle.size.large": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Groß" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Large" + "en": { + "stringUnit": { + "state": "translated", + "value": "Large" } } } }, - "subtitleStyle.size.normal" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Normal" + "subtitleStyle.size.normal": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Normal" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Normal" + "en": { + "stringUnit": { + "state": "translated", + "value": "Normal" } } } }, - "subtitleStyle.size.small" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Klein" + "subtitleStyle.size.small": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Klein" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Small" + "en": { + "stringUnit": { + "state": "translated", + "value": "Small" } } } }, - "subtitleStyle.sizeExplanation" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bezogen auf die Vorgabe des Players, nicht auf eine feste Schriftgröße — die Untertitel bleiben damit auf jedem Bildschirm gleich gut lesbar." + "subtitleStyle.sizeExplanation": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Bezogen auf die Vorgabe des Players, nicht auf eine feste Schriftgröße — die Untertitel bleiben damit auf jedem Bildschirm gleich gut lesbar." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Relative to the player’s own default rather than a fixed point size, so subtitles stay readable on any screen." + "en": { + "stringUnit": { + "state": "translated", + "value": "Relative to the player’s own default rather than a fixed point size, so subtitles stay readable on any screen." } } } }, - "subtitleStyle.sizeHeader" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Schriftgröße" + "subtitleStyle.sizeHeader": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Schriftgröße" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Text size" + "en": { + "stringUnit": { + "state": "translated", + "value": "Text size" } } } }, - "subtitleStyle.title" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Untertitel" + "subtitleStyle.title": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Untertitel" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Subtitles" + "en": { + "stringUnit": { + "state": "translated", + "value": "Subtitles" } } } }, - "technical.audio" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tonspur" + "technical.audio": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Tonspur" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Audio" + "en": { + "stringUnit": { + "state": "translated", + "value": "Audio" } } } }, - "technical.bitrate" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bitrate" + "technical.bitrate": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Bitrate" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bitrate" + "en": { + "stringUnit": { + "state": "translated", + "value": "Bitrate" } } } }, - "technical.container" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Container" + "technical.container": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Container" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Container" + "en": { + "stringUnit": { + "state": "translated", + "value": "Container" } } } }, - "technical.directPlayNative" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Direkt (Systemplayer)" + "technical.directPlayNative": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Direkt (Systemplayer)" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Direct (system player)" + "en": { + "stringUnit": { + "state": "translated", + "value": "Direct (system player)" } } } }, - "technical.file" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Datei auf dem Server" + "technical.file": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Datei auf dem Server" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "File on server" + "en": { + "stringUnit": { + "state": "translated", + "value": "File on server" } } } }, - "technical.playbackMethod" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wiedergabe" + "technical.playbackMethod": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Wiedergabe" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Playback" + "en": { + "stringUnit": { + "state": "translated", + "value": "Playback" } } } }, - "technical.reason" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Grund" + "technical.reason": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Grund" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Reason" + "en": { + "stringUnit": { + "state": "translated", + "value": "Reason" } } } }, - "technical.reason.audioCodec %@" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "iOS kann %@-Ton nicht wiedergeben." + "technical.reason.audioCodec %@": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "iOS kann %@-Ton nicht wiedergeben." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "iOS can't play %@ audio." + "en": { + "stringUnit": { + "state": "translated", + "value": "iOS can't play %@ audio." } } } }, - "technical.reason.chosen" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Im Player wurde eine Qualitätsstufe gewählt." + "technical.reason.chosen": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Im Player wurde eine Qualitätsstufe gewählt." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "A quality step was chosen in the player." + "en": { + "stringUnit": { + "state": "translated", + "value": "A quality step was chosen in the player." } } } }, - "technical.reason.container %@" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "iOS öffnet den Container %@ nicht." + "technical.reason.container %@": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "iOS öffnet den Container %@ nicht." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "iOS can't open the %@ container." + "en": { + "stringUnit": { + "state": "translated", + "value": "iOS can't open the %@ container." } } } }, - "technical.reason.unanalyzed" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Der Server hat diese Datei noch nicht analysiert." + "technical.reason.unanalyzed": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Der Server hat diese Datei noch nicht analysiert." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The server hasn't analyzed this file yet." + "en": { + "stringUnit": { + "state": "translated", + "value": "The server hasn't analyzed this file yet." } } } }, - "technical.reason.videoCodec %@" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "iOS kann %@ nicht dekodieren." + "technical.reason.videoCodec %@": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "iOS kann %@ nicht dekodieren." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "iOS can't decode %@." + "en": { + "stringUnit": { + "state": "translated", + "value": "iOS can't decode %@." } } } }, - "technical.remux" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Umgepackt" + "technical.remux": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Umgepackt" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Remuxed" + "en": { + "stringUnit": { + "state": "translated", + "value": "Remuxed" } } } }, - "technical.remuxAudio" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Umgepackt, Ton umgerechnet" + "technical.remuxAudio": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Umgepackt, Ton umgerechnet" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Remuxed, audio converted" + "en": { + "stringUnit": { + "state": "translated", + "value": "Remuxed, audio converted" } } } }, - "technical.resolution" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Auflösung" + "technical.resolution": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Auflösung" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Resolution" + "en": { + "stringUnit": { + "state": "translated", + "value": "Resolution" } } } }, - "technical.size" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Dateigröße" + "technical.size": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Dateigröße" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "File size" + "en": { + "stringUnit": { + "state": "translated", + "value": "File size" } } } }, - "technical.subtitle" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Untertitel" + "technical.subtitle": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Untertitel" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Subtitle" + "en": { + "stringUnit": { + "state": "translated", + "value": "Subtitle" } } } }, - "technical.title" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Technische Angaben" + "technical.title": { + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Technische Angaben" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Technical details" + "en": { + "stringUnit": { + "state": "translated", + "value": "Technical details" } } } }, - "technical.transcode" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Umgerechnet vom Server" + "technical.transcode": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Umgerechnet vom Server" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Transcoded by server" + "en": { + "stringUnit": { + "state": "translated", + "value": "Transcoded by server" } } } }, - "technical.videoCodec" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bildcodec" + "technical.videoCodec": { + "extractionState": "stale", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Bildcodec" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Video codec" + "en": { + "stringUnit": { + "state": "translated", + "value": "Video codec" } } } }, - "tile.play" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Abspielen" + "tile.play": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Abspielen" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Play" + "en": { + "stringUnit": { + "state": "translated", + "value": "Play" } } } }, - "topShelf.noFile" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Noch nichts abgelegt." + "topShelf.noFile": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Noch nichts abgelegt." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nothing stored yet." + "en": { + "stringUnit": { + "state": "translated", + "value": "Nothing stored yet." } } } }, - "topShelf.noGroup" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Keine App-Gruppe — das Regal kann nichts lesen." + "topShelf.noGroup": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Keine App-Gruppe — das Regal kann nichts lesen." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "No app group — the shelf cannot read anything." + "en": { + "stringUnit": { + "state": "translated", + "value": "No app group — the shelf cannot read anything." } } } }, - "topShelf.title" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Regal über der App" + "topShelf.title": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Regal über der App" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Top Shelf" + "en": { + "stringUnit": { + "state": "translated", + "value": "Top Shelf" } } } }, - "topShelf.unreadable" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Datei liegt da, lässt sich aber nicht lesen." + "topShelf.unreadable": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Datei liegt da, lässt sich aber nicht lesen." } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "File is there but cannot be read." + "en": { + "stringUnit": { + "state": "translated", + "value": "File is there but cannot be read." } } } }, - "topShelf.written %lld %lld %@" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld Reihen, %lld Einträge, zuletzt %@" + "topShelf.written %lld %lld %@": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "%lld Reihen, %lld Einträge, zuletzt %@" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld rows, %lld entries, last %@" + "en": { + "stringUnit": { + "state": "translated", + "value": "%lld rows, %lld entries, last %@" } } } }, - "upcoming.from %@" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ab %@ Uhr" + "upcoming.from %@": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Ab %@ Uhr" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "From %@" + "en": { + "stringUnit": { + "state": "translated", + "value": "From %@" } } } }, - "upcoming.title" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bald ist es soweit" + "upcoming.title": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Bald ist es soweit" } }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Coming soon" + "en": { + "stringUnit": { + "state": "translated", + "value": "Coming soon" } } } } }, - "version" : "1.1" + "version": "1.1" } \ No newline at end of file diff --git a/Flaro/Session/PlaybackSettings.swift b/Flaro/Session/PlaybackSettings.swift index af860bb..dabfb44 100644 --- a/Flaro/Session/PlaybackSettings.swift +++ b/Flaro/Session/PlaybackSettings.swift @@ -32,6 +32,7 @@ final class PlaybackSettings { static let cellularQuality = "flaro.playback.quality.cellular" static let autoSkipIntro = "flaro.playback.autoSkipIntro" static let autoSkipCredits = "flaro.playback.autoSkipCredits" + static let skipExtraScenes = "flaro.playback.skipExtraScenes" static let autoPlayNext = "flaro.playback.autoPlayNext" static let locksLandscape = "flaro.playback.locksLandscape" static let rewindSeconds = "flaro.playback.rewindSeconds" @@ -58,6 +59,14 @@ final class PlaybackSettings { var cellularQuality: Quality { didSet { defaults.set(cellularQuality.rawValue, forKey: Key.cellularQuality) } } var autoSkipIntro: Bool { didSet { defaults.set(autoSkipIntro, forKey: Key.autoSkipIntro) } } var autoSkipCredits: Bool { didSet { defaults.set(autoSkipCredits, forKey: Key.autoSkipCredits) } } + + /// Ob „Abspann überspringen" auch über Post-Credit-Szenen hinweggeht. + /// + /// **Eine Gerätesache, keine Servereinstellung.** Ob jemand die Szene + /// sehen will, entscheidet er am Gerät, auf dem er sitzt — wie bei + /// `autoSkipIntro`. Vorgabe aus: wer den Knopf drückt, landet bei der + /// Szene und kann sie wegdrücken, wenn er mag. + var skipExtraScenes: Bool { didSet { defaults.set(skipExtraScenes, forKey: Key.skipExtraScenes) } } var autoPlayNext: Bool { didSet { defaults.set(autoPlayNext, forKey: Key.autoPlayNext) } } /// ISO-Sprachcode, etwa `deu`. Leer heißt: nehmen, was der Server vorgibt. @@ -117,6 +126,7 @@ final class PlaybackSettings { self.autoSkipIntro = defaults.object(forKey: Key.autoSkipIntro) as? Bool ?? false self.autoSkipCredits = defaults.object(forKey: Key.autoSkipCredits) as? Bool ?? false + self.skipExtraScenes = defaults.object(forKey: Key.skipExtraScenes) as? Bool ?? false self.autoPlayNext = defaults.object(forKey: Key.autoPlayNext) as? Bool ?? true self.locksLandscape = defaults.object(forKey: Key.locksLandscape) as? Bool ?? true diff --git a/Flaro/Views/Player/PlayerModel.swift b/Flaro/Views/Player/PlayerModel.swift index 57e4966..fea8749 100644 --- a/Flaro/Views/Player/PlayerModel.swift +++ b/Flaro/Views/Player/PlayerModel.swift @@ -1656,9 +1656,20 @@ final class PlayerModel { /// Springt ans Ende der laufenden Marke. func skipActiveMarker() { - guard let marker = activeMarker else { return } + guard let marker = activeMarker, let item else { return } skippedMarkers.insert(marker.id) - seek(to: .milliseconds(marker.endMilliseconds)) + + // **Mit dem Schalter in einem Zug bis ans Ende.** Sonst endet der + // Sprung an dieser Marke, also bei der Post-Credit-Szene — wer sie + // nicht sehen will, stellt es einmal um und drückt einmal. + let ziel = settings.skipExtraScenes + ? item.markers + .filter { $0.kind == marker.kind && $0.startMilliseconds >= marker.startMilliseconds } + .map(\.endMilliseconds) + .max() ?? marker.endMilliseconds + : marker.endMilliseconds + + seek(to: .milliseconds(ziel)) } /// Ob es bei der Tonspur überhaupt etwas zu wählen gibt. @@ -1838,14 +1849,14 @@ final class PlayerModel { guard let item else { return } let milliseconds = currentTime.milliseconds - activeMarker = item.markers.first { marker in - guard marker.contains(milliseconds) else { return false } - // Der Abspann zählt nur als Marke, wenn danach noch etwas kommt — - // bei einem Film ist „Abspann überspringen" gleichbedeutend mit - // „Film beenden". - if marker.isCredits && item.kind != .episode { return false } - return true - } + // **Was der Server schickt, gilt.** Hier stand eine eigene Regel: + // bei einem Film wurde die Abspannmarke verworfen, weil + // „überspringen" dort gleichbedeutend mit „beenden" war. Das ist + // eine Entscheidung über Marken, und die trifft der Server — er + // schickt sie nur, wenn er einen Abspann gefunden hat, und seit + // ein Abspann geteilt sein darf, führt der Knopf zur + // Post-Credit-Szene statt ins Nichts. + activeMarker = item.markers.first { $0.contains(milliseconds) } guard let marker = activeMarker, !skippedMarkers.contains(marker.id) else { return } let shouldSkip = (marker.isIntro && settings.autoSkipIntro) diff --git a/Flaro/Views/Settings/PlaybackSettingsView.swift b/Flaro/Views/Settings/PlaybackSettingsView.swift index dfb4b47..704ea0a 100644 --- a/Flaro/Views/Settings/PlaybackSettingsView.swift +++ b/Flaro/Views/Settings/PlaybackSettingsView.swift @@ -102,6 +102,7 @@ struct PlaybackSettingsView: View { Section { Toggle("settings.autoSkipIntro", isOn: $settings.autoSkipIntro) Toggle("settings.autoSkipCredits", isOn: $settings.autoSkipCredits) + Toggle("settings.skipExtraScenes", isOn: $settings.skipExtraScenes) Toggle("settings.autoPlayNext", isOn: $settings.autoPlayNext) } header: { Text("settings.episodes") diff --git a/server/src/Flaro.Api/Endpoints/LibraryContentEndpoints.cs b/server/src/Flaro.Api/Endpoints/LibraryContentEndpoints.cs index 6047724..19aed8e 100644 --- a/server/src/Flaro.Api/Endpoints/LibraryContentEndpoints.cs +++ b/server/src/Flaro.Api/Endpoints/LibraryContentEndpoints.cs @@ -1,5 +1,8 @@ +using System.Globalization; +using System.Text; using Flaro.Api.Auth; using Flaro.Core.Library; +using Flaro.Media; using Flaro.Core.Media; using System.Security.Claims; using Flaro.Core.Playback; @@ -152,6 +155,9 @@ public static class LibraryContentEndpoints var roh = await db.Marken.AsNoTracking() .Where(m => m.EpisodeId != null && folgen.Contains(m.EpisodeId!.Value)) + // In Zeitfolge: ein geteilter Abspann kommt als zwei Marken, und + // die Spieler nehmen die erste passende. + .OrderBy(m => m.VonSekunden) .Select(m => new { Id = m.EpisodeId!.Value, m.Art, m.VonSekunden, m.BisSekunden }) .ToListAsync(); @@ -177,6 +183,7 @@ public static class LibraryContentEndpoints var roh = await db.Marken.AsNoTracking() .Where(m => m.MovieId != null && filme.Contains(m.MovieId!.Value)) + .OrderBy(m => m.VonSekunden) .Select(m => new { Id = m.MovieId!.Value, m.Art, m.VonSekunden, m.BisSekunden }) .ToListAsync(); @@ -902,6 +909,42 @@ public static class LibraryContentEndpoints return Results.Accepted(); }).RequireAuthorization(FlaroPolicies.Administrator); + // **Die Messkurve eines Films — zum Nachsehen, nicht zum Erkennen.** + // + // Die Erkennung sagt „kein Abspann" und nennt die Gründe; was sie + // nicht sagen kann, ist, wie die Kurve aussieht. Genau die braucht + // es, bevor eine neue Schwelle gesetzt wird — zum Beispiel für die + // Frage, ob bei Outtakes im Abspann wenigstens die Ränder dunkel + // bleiben. + app.MapGet("/api/v1/movies/{id:guid}/markenkurve", async ( + Guid id, FlaroContext db, Bildabtaster abtaster, CancellationToken ct) => + { + var film = await db.Movies.AsNoTracking() + .Where(m => m.Id == id) + .Join(db.MediaFiles.AsNoTracking(), m => m.Id, d => d.MovieId, + (m, d) => new { m.Title, d.Path, d.Duration }) + .FirstOrDefaultAsync(ct); + + if (film?.Duration is not { TotalSeconds: > 0 } laufzeit) + return Einwand.Bitte("Zu diesem Film gibt es keine Datei mit Laufzeit."); + + var ab = Math.Max(0, laufzeit.TotalSeconds - Markenerkennung.Suchbereich); + var kurve = await abtaster.KurveAsync(film.Path, ab, laufzeit.TotalSeconds - ab, ct); + + var text = new StringBuilder("sekunde;gesamt;links;rechts\n"); + foreach (var punkt in kurve) + { + text.Append(CultureInfo.InvariantCulture, + $"{punkt.Sekunde:0.###};{punkt.Gesamt:0.#};{punkt.Links:0.#};{punkt.Rechts:0.#}\n"); + } + + var name = string.Concat((film.Title ?? "film") + .Select(c => char.IsLetterOrDigit(c) ? c : '-')); + + return Results.File(Encoding.UTF8.GetBytes(text.ToString()), + "text/csv", $"markenkurve-{name}.csv"); + }).RequireAuthorization(FlaroPolicies.Administrator); + // **Eine Marke wieder wegnehmen.** // // Ohne diesen Weg wäre die Erkennung eine Blackbox, die manchmal diff --git a/server/src/Flaro.Api/Endpoints/PlaybackEndpoints.cs b/server/src/Flaro.Api/Endpoints/PlaybackEndpoints.cs index 77e0470..51cf585 100644 --- a/server/src/Flaro.Api/Endpoints/PlaybackEndpoints.cs +++ b/server/src/Flaro.Api/Endpoints/PlaybackEndpoints.cs @@ -100,14 +100,21 @@ public static class PlaybackEndpoints // **Der Abspann, wenn er erkannt wurde.** Er zieht die Schwelle // fürs Häkchen nach vorn — siehe `ProgressRule.Apply`. + // + // **Das früheste Stück, nicht irgendeines.** Teilt eine + // Post-Credit-Szene den Abspann, gibt es zwei Zeilen; ohne + // `OrderBy` entschied die Datenbank, welche gilt. Gemeint ist + // immer die erste — dort ist die Handlung vorbei. var abspannAb = zustand.EpisodeId is { } folge ? await db.Marken.AsNoTracking() .Where(m => m.EpisodeId == folge && m.Art == Markenart.Abspann) + .OrderBy(m => m.VonSekunden) .Select(m => (double?)m.VonSekunden) .FirstOrDefaultAsync() : zustand.MovieId is { } film ? await db.Marken.AsNoTracking() .Where(m => m.MovieId == film && m.Art == Markenart.Abspann) + .OrderBy(m => m.VonSekunden) .Select(m => (double?)m.VonSekunden) .FirstOrDefaultAsync() : null; diff --git a/server/src/Flaro.Api/Markenerkennung.cs b/server/src/Flaro.Api/Markenerkennung.cs index 50ca154..efc6e63 100644 --- a/server/src/Flaro.Api/Markenerkennung.cs +++ b/server/src/Flaro.Api/Markenerkennung.cs @@ -503,7 +503,8 @@ public sealed class Markenerkennung( // allein ist keine Auskunft: ob die Datei zu wenige Schlüsselbilder // hat, ob der Abspann zu hell war oder ob am Schluss eine Tafel // steht, sieht man nur an diesen drei Zahlen — und sonst gar nicht. - if (Abspannkante.Suchen(proben, regeln, out var grund) is not { } stelle) + var stücke = Abspannkante.Suchen(proben, regeln, out var grund); + if (stücke.Count == 0) { // **Mit der Kapitellage.** Ob eine Datei gar keine Kapitel hat // oder nur namenlose, entscheidet, ob sich an diesem Weg @@ -517,16 +518,34 @@ public sealed class Markenerkennung( return false; } - var hell = Abspannkante.HellDavor(proben, stelle, regeln) ?? ab; + // **Nur die erste Kante wird nachgemessen.** Sie ist die, die der + // Zuschauer zuerst sieht, und die, an der „gilt als gesehen" hängt. + // Die übrigen Grenzen liegen an Schlüsselbildern und sind damit auf + // fünf bis zehn Sekunden genau — dafür lohnt keine Sonde. + var hell = Abspannkante.HellDavor(proben, stücke[0].Von, regeln) ?? ab; var kante = await Abspannkante.VerfeinernAsync( - hell, stelle, regeln.Mindestanteil, + hell, stücke[0].Von, regeln.Mindestanteil, wo => abtaster.DunkelanteilAsync(pfad, wo, ct)); - log.LogInformation("„{Titel}“: Abspann im Bild ab {Stelle:h\\:mm\\:ss}.", - name, TimeSpan.FromSeconds(kante)); + var fein = stücke.ToList(); + fein[0] = fein[0] with { Von = kante }; - return await SchreibenAsync(db, titelId, istFilm, Markenart.Abspann, - kante, laufzeit, ct); + if (fein.Count == 1) + { + log.LogInformation("„{Titel}“: Abspann im Bild ab {Stelle:h\\:mm\\:ss}.", + name, TimeSpan.FromSeconds(kante)); + } + else + { + log.LogInformation( + "„{Titel}“: Abspann im Bild in {Stücke} Stücken ab {Stelle:h\\:mm\\:ss} " + + "— dazwischen {Lücken}.", + name, fein.Count, TimeSpan.FromSeconds(kante), + string.Join(", ", fein.Zip(fein.Skip(1), + (a, b) => $"{Zeit(a.Bis)}–{Zeit(b.Von)}"))); + } + + return await SchreibenAsync(db, titelId, istFilm, Markenart.Abspann, fein, ct); } /// Was ein Serienlauf ergeben hat — für die Zusammenfassung. @@ -917,31 +936,53 @@ public sealed class Markenerkennung( }); } + private Task SchreibenAsync( + FlaroContext db, Guid titelId, bool istFilm, Markenart art, + double von, double bis, CancellationToken ct) => + SchreibenAsync(db, titelId, istFilm, art, [new Abspannstück(von, bis)], ct); + + /// Setzt die Marken einer Art auf genau diese Stücke. + /// + /// + /// + /// Ersetzen, nicht umschreiben. Hier stand ein Abgleich auf + /// genau eine Zeile je Titel und Art — das ging, solange ein Abspann + /// eine zusammenhängende Strecke war. Seit eine Post-Credit-Szene ihn + /// teilen darf, sind es ein bis zwei Zeilen, und beim nächsten Lauf + /// vielleicht wieder eine. Die alten zu löschen und die neuen + /// anzulegen ist der einzige Weg, der in beide Richtungen stimmt. + /// + /// + /// In derselben Transaktion — gespeichert wird weiter oben, und + /// bis dahin stehen Löschung und Anlage zusammen. Dazwischen gibt es + /// keinen Augenblick, in dem der Titel gar keine Marke hätte. + /// + /// private async Task SchreibenAsync( FlaroContext db, Guid titelId, bool istFilm, Markenart art, - double von, double bis, CancellationToken ct) + IReadOnlyList stücke, CancellationToken ct) { - var alt = istFilm - ? await db.Marken.FirstOrDefaultAsync(m => m.MovieId == titelId && m.Art == art, ct) - : await db.Marken.FirstOrDefaultAsync(m => m.EpisodeId == titelId && m.Art == art, ct); + if (stücke.Count == 0) return false; - if (alt is not null) + var alte = istFilm + ? await db.Marken.Where(m => m.MovieId == titelId && m.Art == art).ToListAsync(ct) + : await db.Marken.Where(m => m.EpisodeId == titelId && m.Art == art).ToListAsync(ct); + + db.Marken.RemoveRange(alte); + + foreach (var stück in stücke) { - alt.VonSekunden = von; - alt.BisSekunden = bis; - alt.ErkanntAm = clock.GetUtcNow(); - return true; + db.Marken.Add(new Marke + { + EpisodeId = istFilm ? null : titelId, + MovieId = istFilm ? titelId : null, + Art = art, + VonSekunden = stück.Von, + BisSekunden = stück.Bis, + ErkanntAm = clock.GetUtcNow(), + }); } - db.Marken.Add(new Marke - { - EpisodeId = istFilm ? null : titelId, - MovieId = istFilm ? titelId : null, - Art = art, - VonSekunden = von, - BisSekunden = bis, - ErkanntAm = clock.GetUtcNow(), - }); return true; } } diff --git a/server/src/Flaro.Core/Media/Abspannkante.cs b/server/src/Flaro.Core/Media/Abspannkante.cs index e43ab0b..d6d0de3 100644 --- a/server/src/Flaro.Core/Media/Abspannkante.cs +++ b/server/src/Flaro.Core/Media/Abspannkante.cs @@ -22,6 +22,19 @@ namespace Flaro.Core.Media; /// Wie viel Prozent des Bildes dunkel sind, 0 bis 100. public readonly record struct Bildprobe(double Sekunde, double Dunkelanteil); +/// Ein Stück Abspann: von wann bis wann. +/// +/// +/// Mehrere, weil ein Abspann unterbrochen sein kann. Liegt eine +/// Post-Credit-Szene dazwischen, sind es zwei Stücke mit der Szene als +/// Lücke — und der Überspringen-Knopf bringt den Zuschauer genau dorthin +/// statt an ihr vorbei. +/// +public readonly record struct Abspannstück(double Von, double Bis) +{ + public double Länge => Math.Max(0, Bis - Von); +} + /// Die Schwellen, nach denen eine dunkle Strecke ein Abspann ist. /// /// @@ -87,6 +100,28 @@ public readonly record struct Abspannregeln /// public double Schlusstafel { get; init; } + /// Wie kurz ein einzelnes Stück sein darf, um noch zu zählen. + /// + /// + /// Ein Abspann kann aus zwei Stücken bestehen, wenn eine Post-Credit- + /// Szene dazwischenliegt. Zehn Sekunden Schwarz sind aber kein Stück + /// Abspann, sondern eine Blende — die Mindestlänge gilt für die Summe, + /// diese Schranke für das einzelne Stück. + /// + public double Mindeststück { get; init; } + + /// Wie lang eine Post-Credit-Szene höchstens sein darf. + /// + /// + /// Die Lücke, über die hinweg zusammengefasst wird. Eine + /// Post-Credit-Szene im MCU dauert typischerweise eine Minute; die + /// längste gemessene (Thunderbolts*) 2:54. Vier Minuten sind der + /// längste bekannte Wert plus Reserve — und zugleich die Schranke + /// dagegen, dass die Suche rückwärts durch den halben Film läuft. + /// + public double Lückentoleranz { get; init; } + + /// Die Summe aller Stücke, nicht die Länge eines einzelnen. public double Mindestlänge { get; init; } public double Höchstlänge { get; init; } @@ -95,6 +130,8 @@ public readonly record struct Abspannregeln Mindestanteil = 60, Nachsicht = 10, Schlusstafel = 20, + Mindeststück = 15, + Lückentoleranz = 4 * 60, Mindestlänge = 45, Höchstlänge = 12 * 60, }; @@ -124,13 +161,14 @@ public readonly record struct Abspannregeln /// public static class Abspannkante { - /// Die dunkle Strecke, die bis zum Ende durchhält — oder nichts. + /// Die Stücke Abspann am Ende — oder eine leere Liste. /// /// /// Messpunkte über das Ende des Films, aufsteigend nach Zeit. Sie dürfen /// ungleichmässig liegen: Schlüsselbilder tun das. /// - public static double? Suchen(IReadOnlyList proben, Abspannregeln regeln) => + public static IReadOnlyList Suchen( + IReadOnlyList proben, Abspannregeln regeln) => Suchen(proben, regeln, out _); /// @@ -139,68 +177,106 @@ public static class Abspannkante /// sind drei verschiedene Ursachen mit drei verschiedenen Antworten, /// und ohne diese Zeile beginnt jede Nachfrage wieder beim Raten. /// - public static double? Suchen( + public static IReadOnlyList Suchen( IReadOnlyList proben, Abspannregeln regeln, out string grund) { grund = "gefunden"; if (proben.Count < 2) { grund = "zu wenige Proben"; - return null; + return []; } - // **Von hinten.** Der Abspann endet am Dateiende; alles, was davor - // dunkel war und wieder hell wurde, war eine Schwarzblende. - // - // **Das allerletzte Bild muss aber nicht dunkel sein.** Hinter dem - // Abspann steht bei vielen Produktionen noch eine Logotafel — - // Sender, Studio, Förderer —, oft hell und ein paar Sekunden lang. - // Darauf zu bestehen, dass die letzte Probe dunkel ist, hiess: ein - // einziges weisses Schlussbild macht den ganzen Abspann unsichtbar. - var letzte = proben.Count - 1; - var ende = proben[letzte].Sekunde; - while (letzte > 0 && proben[letzte].Dunkelanteil < regeln.Mindestanteil) + var läufe = Läufe(proben, regeln); + if (läufe.Count == 0) { - if (ende - proben[letzte].Sekunde >= regeln.Schlusstafel) - { - grund = "das Ende ist zu lange hell"; - return null; - } - letzte--; + grund = "keine dunkle Strecke im Suchfenster"; + return []; } - if (proben[letzte].Dunkelanteil < regeln.Mindestanteil) + + // **Das letzte Stück muss das Ende erreichen.** Ein Abspann endet am + // Dateiende; was davor dunkel war und wieder hell wurde, war eine + // Schwarzblende. Eine helle Logotafel dahinter ist erlaubt — Sender, + // Studio, Förderer stehen bei vielen Produktionen noch hinter dem + // Abspann. + var ende = proben[^1].Sekunde; + if (ende - läufe[^1].Bis >= regeln.Schlusstafel) { grund = "das Ende ist hell"; - return null; + return []; } - // **Rückwärts, und helle Stellen in Sekunden verzeihen.** Gezählt - // wurden hier früher Proben, und was das bedeutet, hängt allein an - // der Schlüsselbilddichte der Datei — siehe `Nachsicht`. - var anfang = letzte; - for (var i = letzte - 1; i >= 0; i--) + // **Rückwärts zusammenfassen.** Jede Lücke unter der Toleranz ist + // eine Post-Credit-Szene und kein Abbruch; die erste grössere Lücke + // beendet die Suche. + var stücke = new List { läufe[^1] }; + for (var i = läufe.Count - 2; i >= 0; i--) { - if (proben[i].Dunkelanteil >= regeln.Mindestanteil) { anfang = i; continue; } - if (proben[anfang].Sekunde - proben[i].Sekunde > regeln.Nachsicht) break; + if (stücke[^1].Von - läufe[i].Bis >= regeln.Lückentoleranz) break; + stücke.Add(läufe[i]); + } + stücke.Reverse(); + + // **Die Summe entscheidet, nicht das einzelne Stück.** Ein Abspann + // von vier Minuten, den eine Szene in der Mitte teilt, ist ein + // Abspann von vier Minuten — vorher zerfiel er in zwei Reste, von + // denen keiner lang genug war, und der Film bekam gar nichts. + var summe = stücke.Sum(t => t.Länge); + if (summe < regeln.Mindestlänge) + { + grund = $"die dunklen Strecken ergeben zusammen nur {summe:0} s"; + return []; + } + if (summe > regeln.Höchstlänge) + { + grund = $"die dunklen Strecken ergeben zusammen {summe:0} s und damit zu viel"; + return []; } - var länge = proben[letzte].Sekunde - proben[anfang].Sekunde; - if (länge < regeln.Mindestlänge) + // Das letzte Stück reicht bis ans Dateiende: eine Logotafel dahinter + // gehört zum Abspann, nicht zum Film. + stücke[^1] = stücke[^1] with { Bis = ende }; + return stücke; + } + + /// Zusammenhängende dunkle Strecken, kurze Helligkeiten verziehen. + /// + /// + /// Die Nachsicht zählt Sekunden, nicht Proben. Eine Schrifttafel + /// blitzt auf, ein Logo steht zwei Sekunden im Bild; wie viele + /// Messpunkte darauf fallen, hängt allein an der Schlüsselbilddichte + /// der Datei und schwankt um eine Zehnerpotenz. + /// + private static List Läufe( + IReadOnlyList proben, Abspannregeln regeln) + { + var läufe = new List(); + double? von = null; + double bis = 0; + + foreach (var probe in proben) { - grund = $"die dunkle Strecke ist mit {länge:0} s zu kurz"; - return null; - } - if (länge > regeln.Höchstlänge) - { - grund = $"die dunkle Strecke ist mit {länge:0} s zu lang"; - return null; + if (probe.Dunkelanteil >= regeln.Mindestanteil) + { + von ??= probe.Sekunde; + bis = probe.Sekunde; + continue; + } + + // Hell: der Lauf endet erst, wenn die Helligkeit lange genug + // anhält. + if (von is not null && probe.Sekunde - bis > regeln.Nachsicht) + { + läufe.Add(new Abspannstück(von.Value, bis)); + von = null; + } } - // **Die erste dunkle Probe ist nicht der Anfang.** Zwischen ihr und - // der letzten hellen davor liegt die Kante irgendwo; wo genau, sagt - // erst das Nachmessen. Hier steht die späteste Stelle, an der sie - // liegen kann — und die ist als Marke schon brauchbar. - return proben[anfang].Sekunde; + if (von is not null) läufe.Add(new Abspannstück(von.Value, bis)); + + // Zehn Sekunden Schwarz sind eine Blende, kein Stück Abspann. + läufe.RemoveAll(l => l.Länge < regeln.Mindeststück); + return läufe; } /// Die letzte helle Probe vor dem Fund — der Suchbereich fürs Nachmessen. @@ -254,3 +330,19 @@ public static class Abspannkante return dunkel; } } + +/// Ein Messpunkt des Werkzeugs: das ganze Bild und die Ränder. +/// +/// +/// Nur zum Nachsehen, nicht für die Erkennung. Ob die Ränder bei +/// Outtakes im Abspann dunkel bleiben, ist eine Vermutung — und eine, die +/// sich nur an echten Dateien beantworten lässt. Diese Reihe ist die +/// Grundlage dafür. +/// +/// +/// Die Stelle im Film. +/// Dunkelanteil des ganzen Bildes, 0 bis 100. +/// Dunkelanteil der linken fünftel Breite. +/// Dunkelanteil der rechten fünftel Breite. +public readonly record struct Kurvenpunkt( + double Sekunde, double Gesamt, double Links, double Rechts); diff --git a/server/src/Flaro.Data/FlaroContext.cs b/server/src/Flaro.Data/FlaroContext.cs index 4422b6a..bc11140 100644 --- a/server/src/Flaro.Data/FlaroContext.cs +++ b/server/src/Flaro.Data/FlaroContext.cs @@ -311,10 +311,14 @@ public sealed class FlaroContext(DbContextOptions options) : DbCon b.Entity(e => { e.HasKey(x => x.Id); - // Eine Art einmal je Titel: ein zweiter Lauf schreibt um, statt - // eine zweite Zeile anzulegen. - e.HasIndex(x => new { x.EpisodeId, x.Art }).IsUnique().HasFilter("\"EpisodeId\" IS NOT NULL"); - e.HasIndex(x => new { x.MovieId, x.Art }).IsUnique().HasFilter("\"MovieId\" IS NOT NULL"); + // **Mehrere Stücke je Art, und das ist der ganze Zweck.** Hier + // stand ein eindeutiger Index mit dem Vermerk „eine Art einmal + // je Titel" — richtig, solange ein Abspann eine + // zusammenhängende Strecke war. Teilt ihn eine Post-Credit- + // Szene, sind es zwei Zeilen, und der Index wies die zweite ab. + // Zum Nachschlagen bleiben die Indizes, nur ohne `IsUnique`. + e.HasIndex(x => new { x.EpisodeId, x.Art }); + e.HasIndex(x => new { x.MovieId, x.Art }); e.HasOne().WithMany().HasForeignKey(x => x.EpisodeId).OnDelete(DeleteBehavior.Cascade); e.HasOne().WithMany().HasForeignKey(x => x.MovieId).OnDelete(DeleteBehavior.Cascade); // Als Zahl, nicht als Text: die Namen stehen im Quelltext und diff --git a/server/src/Flaro.Data/Migrations/20261015142742_Abspannstuecke.Designer.cs b/server/src/Flaro.Data/Migrations/20261015142742_Abspannstuecke.Designer.cs new file mode 100644 index 0000000..8d8494c --- /dev/null +++ b/server/src/Flaro.Data/Migrations/20261015142742_Abspannstuecke.Designer.cs @@ -0,0 +1,1357 @@ +// +using System; +using Flaro.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Flaro.Data.Migrations +{ + [DbContext(typeof(FlaroContext))] + [Migration("20261015142742_Abspannstuecke")] + partial class Abspannstuecke + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.12"); + + modelBuilder.Entity("Flaro.Core.Auth.AuthCode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApprovedByUserId") + .HasColumnType("TEXT"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.Property("ConsumedAt") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("INTEGER"); + + b.Property("DeviceId") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("DeviceName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("ExpiresAt") + .HasColumnType("INTEGER"); + + b.Property("HandleFingerprint") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Code"); + + b.HasIndex("HandleFingerprint") + .IsUnique(); + + b.ToTable("AuthCodes"); + }); + + modelBuilder.Entity("Flaro.Core.Auth.DeviceToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("INTEGER"); + + b.Property("DeviceId") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("DeviceName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("LastSeenAt") + .HasColumnType("INTEGER"); + + b.Property("RevokedAt") + .HasColumnType("INTEGER"); + + b.Property("TokenFingerprint") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TokenFingerprint") + .IsUnique(); + + b.HasIndex("UserId"); + + b.ToTable("DeviceTokens"); + }); + + modelBuilder.Entity("Flaro.Core.Auth.Einladung", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("INTEGER"); + + b.Property("CreatedByUserId") + .HasColumnType("TEXT"); + + b.Property("ExpiresAt") + .HasColumnType("INTEGER"); + + b.Property("MaxUses") + .HasColumnType("INTEGER"); + + b.Property("RevokedAt") + .HasColumnType("INTEGER"); + + b.Property("TokenFingerprint") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("UsedCount") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("CreatedByUserId"); + + b.HasIndex("TokenFingerprint") + .IsUnique(); + + b.ToTable("Einladungen"); + }); + + modelBuilder.Entity("Flaro.Core.Libraries.Library", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("INTEGER"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Path") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("ReviewNewItems") + .HasColumnType("INTEGER"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Libraries"); + }); + + modelBuilder.Entity("Flaro.Core.Library.Credit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Character") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("MovieId") + .HasColumnType("TEXT"); + + b.Property("Order") + .HasColumnType("INTEGER"); + + b.Property("PersonId") + .HasColumnType("TEXT"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.Property("SeriesId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("MovieId"); + + b.HasIndex("PersonId"); + + b.HasIndex("SeriesId"); + + b.ToTable("Credits"); + }); + + modelBuilder.Entity("Flaro.Core.Library.Episode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AddedAt") + .HasColumnType("INTEGER"); + + b.Property("EpisodeNumber") + .HasColumnType("INTEGER"); + + b.Property("MetadataLanguage") + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.Property("Overview") + .HasColumnType("TEXT"); + + b.Property("PublishAt") + .HasColumnType("INTEGER"); + + b.Property("SeasonNumber") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("TEXT"); + + b.Property("StillImage") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("SeriesId", "SeasonNumber", "EpisodeNumber") + .IsUnique(); + + b.ToTable("Episodes"); + }); + + modelBuilder.Entity("Flaro.Core.Library.Fremdbewertung", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Anzeige") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("MovieId") + .HasColumnType("TEXT"); + + b.Property("Quelle") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("SeriesId") + .HasColumnType("TEXT"); + + b.Property("Wert") + .HasColumnType("REAL"); + + b.HasKey("Id"); + + b.HasIndex("MovieId", "Quelle") + .IsUnique() + .HasFilter("\"MovieId\" IS NOT NULL"); + + b.HasIndex("SeriesId", "Quelle") + .IsUnique() + .HasFilter("\"SeriesId\" IS NOT NULL"); + + b.ToTable("Fremdbewertungen"); + }); + + modelBuilder.Entity("Flaro.Core.Library.Genre", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT") + .UseCollation("NOCASE"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Genres"); + }); + + modelBuilder.Entity("Flaro.Core.Library.GenreLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("GenreId") + .HasColumnType("TEXT"); + + b.Property("MovieId") + .HasColumnType("TEXT"); + + b.Property("SeriesId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("MovieId"); + + b.HasIndex("SeriesId"); + + b.HasIndex("GenreId", "MovieId") + .IsUnique() + .HasFilter("\"MovieId\" IS NOT NULL"); + + b.HasIndex("GenreId", "SeriesId") + .IsUnique() + .HasFilter("\"SeriesId\" IS NOT NULL"); + + b.ToTable("GenreLinks"); + }); + + modelBuilder.Entity("Flaro.Core.Library.ItemTranslation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("EpisodeId") + .HasColumnType("TEXT"); + + b.Property("FetchedAt") + .HasColumnType("INTEGER"); + + b.Property("Language") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.Property("MovieId") + .HasColumnType("TEXT"); + + b.Property("Overview") + .HasColumnType("TEXT"); + + b.Property("SeriesId") + .HasColumnType("TEXT"); + + b.Property("Tagline") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("Title") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EpisodeId", "Language") + .IsUnique() + .HasFilter("\"EpisodeId\" IS NOT NULL"); + + b.HasIndex("MovieId", "Language") + .IsUnique() + .HasFilter("\"MovieId\" IS NOT NULL"); + + b.HasIndex("SeriesId", "Language") + .IsUnique() + .HasFilter("\"SeriesId\" IS NOT NULL"); + + b.ToTable("Translations"); + }); + + modelBuilder.Entity("Flaro.Core.Library.Marke", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Art") + .HasColumnType("INTEGER"); + + b.Property("BisSekunden") + .HasColumnType("REAL"); + + b.Property("EpisodeId") + .HasColumnType("TEXT"); + + b.Property("ErkanntAm") + .HasColumnType("INTEGER"); + + b.Property("MovieId") + .HasColumnType("TEXT"); + + b.Property("VonSekunden") + .HasColumnType("REAL"); + + b.HasKey("Id"); + + b.HasIndex("EpisodeId", "Art"); + + b.HasIndex("MovieId", "Art"); + + b.ToTable("Marken"); + }); + + modelBuilder.Entity("Flaro.Core.Library.Markensuche", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Art") + .HasColumnType("INTEGER"); + + b.Property("Folgen") + .HasColumnType("INTEGER"); + + b.Property("GelaufenAm") + .HasColumnType("INTEGER"); + + b.Property("MovieId") + .HasColumnType("TEXT"); + + b.Property("SeriesId") + .HasColumnType("TEXT"); + + b.Property("Staffel") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MovieId", "Art") + .IsUnique() + .HasFilter("\"MovieId\" IS NOT NULL"); + + b.HasIndex("SeriesId", "Staffel", "Art") + .IsUnique() + .HasFilter("\"SeriesId\" IS NOT NULL"); + + b.ToTable("Markensuchen"); + }); + + modelBuilder.Entity("Flaro.Core.Library.MediaFile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BitRate") + .HasColumnType("INTEGER"); + + b.Property("Container") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("Duration") + .HasColumnType("INTEGER"); + + b.Property("EpisodeId") + .HasColumnType("TEXT"); + + b.Property("LibraryId") + .HasColumnType("TEXT"); + + b.Property("MissingSince") + .HasColumnType("INTEGER"); + + b.Property("ModifiedAt") + .HasColumnType("INTEGER"); + + b.Property("MovieId") + .HasColumnType("TEXT"); + + b.Property("Path") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("ProbedAt") + .HasColumnType("INTEGER"); + + b.Property("SeenAt") + .HasColumnType("INTEGER"); + + b.Property("SizeBytes") + .HasColumnType("INTEGER"); + + b.Property("VersionLabel") + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EpisodeId"); + + b.HasIndex("MovieId"); + + b.HasIndex("LibraryId", "Path") + .IsUnique(); + + b.ToTable("MediaFiles"); + }); + + modelBuilder.Entity("Flaro.Core.Library.MediaStream", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Channels") + .HasColumnType("INTEGER"); + + b.Property("Codec") + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("Height") + .HasColumnType("INTEGER"); + + b.Property("Index") + .HasColumnType("INTEGER"); + + b.Property("IsDefault") + .HasColumnType("INTEGER"); + + b.Property("IsForced") + .HasColumnType("INTEGER"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.Property("Language") + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.Property("MediaFileId") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Width") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MediaFileId", "Index") + .IsUnique(); + + b.ToTable("MediaStreams"); + }); + + modelBuilder.Entity("Flaro.Core.Library.Meldung", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Art") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("EpisodeId") + .HasColumnType("TEXT"); + + b.Property("ErledigtAm") + .HasColumnType("INTEGER"); + + b.Property("ErledigtVon") + .HasColumnType("TEXT"); + + b.Property("GemeldetAm") + .HasColumnType("INTEGER"); + + b.Property("Kommentar") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("MovieId") + .HasColumnType("TEXT"); + + b.Property("SeasonNumber") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("TEXT"); + + b.Property("Stelle") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EpisodeId"); + + b.HasIndex("MovieId"); + + b.HasIndex("SeriesId"); + + b.HasIndex("ErledigtAm", "GemeldetAm"); + + b.HasIndex("UserId", "GemeldetAm"); + + b.ToTable("Meldungen"); + }); + + modelBuilder.Entity("Flaro.Core.Library.Movie", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AddedAt") + .HasColumnType("INTEGER"); + + b.Property("Aufmacherbilder") + .HasColumnType("TEXT"); + + b.Property("BackdropImage") + .HasColumnType("TEXT"); + + b.Property("BackdropPinned") + .HasColumnType("INTEGER"); + + b.Property("ChangedSince") + .HasColumnType("INTEGER"); + + b.Property("ContentRating") + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.Property("FassungenGeprueftBei") + .HasColumnType("INTEGER"); + + b.Property("ImdbId") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("LibraryId") + .HasColumnType("TEXT"); + + b.Property("LogoImage") + .HasColumnType("TEXT"); + + b.Property("LogoPinned") + .HasColumnType("INTEGER"); + + b.Property("MatchLocked") + .HasColumnType("INTEGER"); + + b.Property("MetadataLanguage") + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.Property("OriginalTitle") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("Overview") + .HasColumnType("TEXT"); + + b.Property("ParsedTitle") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ParsedYear") + .HasColumnType("INTEGER"); + + b.Property("PosterImage") + .HasColumnType("TEXT"); + + b.Property("PosterPinned") + .HasColumnType("INTEGER"); + + b.Property("PublishAt") + .HasColumnType("INTEGER"); + + b.Property("Rating") + .HasColumnType("REAL"); + + b.Property("RuntimeMinutes") + .HasColumnType("INTEGER"); + + b.Property("SortTitlePinned") + .HasColumnType("INTEGER"); + + b.Property("SortTitleValue") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("Tagline") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("TitlePinned") + .HasColumnType("INTEGER"); + + b.Property("TmdbId") + .HasColumnType("INTEGER"); + + b.Property("Year") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("LibraryId", "MetadataLanguage"); + + b.HasIndex("LibraryId", "SortTitleValue"); + + b.HasIndex("LibraryId", "TmdbId") + .IsUnique() + .HasFilter("\"TmdbId\" IS NOT NULL"); + + b.ToTable("Movies"); + }); + + modelBuilder.Entity("Flaro.Core.Library.Person", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Image") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("TmdbId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TmdbId") + .IsUnique(); + + b.ToTable("People"); + }); + + modelBuilder.Entity("Flaro.Core.Library.Series", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AddedAt") + .HasColumnType("INTEGER"); + + b.Property("Aufmacherbilder") + .HasColumnType("TEXT"); + + b.Property("BackdropImage") + .HasColumnType("TEXT"); + + b.Property("BackdropPinned") + .HasColumnType("INTEGER"); + + b.Property("ChangedSince") + .HasColumnType("INTEGER"); + + b.Property("ContentRating") + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.Property("ImdbId") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("LibraryId") + .HasColumnType("TEXT"); + + b.Property("LogoImage") + .HasColumnType("TEXT"); + + b.Property("LogoPinned") + .HasColumnType("INTEGER"); + + b.Property("MatchLocked") + .HasColumnType("INTEGER"); + + b.Property("MetadataLanguage") + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.Property("OriginalTitle") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("Overview") + .HasColumnType("TEXT"); + + b.Property("ParsedTitle") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ParsedYear") + .HasColumnType("INTEGER"); + + b.Property("PosterImage") + .HasColumnType("TEXT"); + + b.Property("PosterPinned") + .HasColumnType("INTEGER"); + + b.Property("PublishAt") + .HasColumnType("INTEGER"); + + b.Property("Rating") + .HasColumnType("REAL"); + + b.Property("RuntimeMinutes") + .HasColumnType("INTEGER"); + + b.Property("SortTitlePinned") + .HasColumnType("INTEGER"); + + b.Property("SortTitleValue") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("Tagline") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("TitlePinned") + .HasColumnType("INTEGER"); + + b.Property("TmdbId") + .HasColumnType("INTEGER"); + + b.Property("Year") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("LibraryId", "MetadataLanguage"); + + b.HasIndex("LibraryId", "SortTitleValue"); + + b.HasIndex("LibraryId", "TmdbId") + .IsUnique() + .HasFilter("\"TmdbId\" IS NOT NULL"); + + b.ToTable("Series"); + }); + + modelBuilder.Entity("Flaro.Core.Library.Stichwort", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Stichworte"); + }); + + modelBuilder.Entity("Flaro.Core.Library.Stichwortlink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("MovieId") + .HasColumnType("TEXT"); + + b.Property("SeriesId") + .HasColumnType("TEXT"); + + b.Property("StichwortId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("MovieId"); + + b.HasIndex("SeriesId"); + + b.HasIndex("StichwortId", "MovieId") + .IsUnique() + .HasFilter("\"MovieId\" IS NOT NULL"); + + b.HasIndex("StichwortId", "SeriesId") + .IsUnique() + .HasFilter("\"SeriesId\" IS NOT NULL"); + + b.ToTable("Stichwortlinks"); + }); + + modelBuilder.Entity("Flaro.Core.Playback.Sehtag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("EpisodeId") + .HasColumnType("TEXT"); + + b.Property("MovieId") + .HasColumnType("TEXT"); + + b.Property("Sekunden") + .HasColumnType("REAL"); + + b.Property("Tag") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EpisodeId"); + + b.HasIndex("MovieId"); + + b.HasIndex("UserId", "Tag"); + + b.HasIndex("UserId", "Tag", "EpisodeId") + .IsUnique() + .HasFilter("\"EpisodeId\" IS NOT NULL"); + + b.HasIndex("UserId", "Tag", "MovieId") + .IsUnique() + .HasFilter("\"MovieId\" IS NOT NULL"); + + b.ToTable("Sehtage"); + }); + + modelBuilder.Entity("Flaro.Core.Playback.UserItemState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Dismissed") + .HasColumnType("INTEGER"); + + b.Property("EpisodeId") + .HasColumnType("TEXT"); + + b.Property("Favourite") + .HasColumnType("INTEGER"); + + b.Property("InProgress") + .HasColumnType("INTEGER"); + + b.Property("LastPlayedAt") + .HasColumnType("INTEGER"); + + b.Property("MovieId") + .HasColumnType("TEXT"); + + b.Property("PlayCount") + .HasColumnType("INTEGER"); + + b.Property("Position") + .HasColumnType("INTEGER"); + + b.Property("Rating") + .HasColumnType("INTEGER"); + + b.Property("Runtime") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("Watched") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("EpisodeId"); + + b.HasIndex("MovieId"); + + b.HasIndex("SeriesId"); + + b.HasIndex("UserId", "EpisodeId") + .IsUnique() + .HasFilter("\"EpisodeId\" IS NOT NULL"); + + b.HasIndex("UserId", "MovieId") + .IsUnique() + .HasFilter("\"MovieId\" IS NOT NULL"); + + b.HasIndex("UserId", "SeriesId") + .IsUnique() + .HasFilter("\"SeriesId\" IS NOT NULL"); + + b.HasIndex("UserId", "InProgress", "UpdatedAt"); + + b.ToTable("UserItemStates"); + }); + + modelBuilder.Entity("Flaro.Core.ServerSetting", b => + { + b.Property("Key") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("TEXT"); + + b.HasKey("Key"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("Flaro.Core.Users.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AvatarImage") + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("INTEGER"); + + b.Property("IsAdministrator") + .HasColumnType("INTEGER"); + + b.Property("IsOwner") + .HasColumnType("INTEGER"); + + b.Property("Language") + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("UserName") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT") + .UseCollation("NOCASE"); + + b.HasKey("Id"); + + b.HasIndex("UserName") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Flaro.Core.Auth.DeviceToken", b => + { + b.HasOne("Flaro.Core.Users.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Flaro.Core.Auth.Einladung", b => + { + b.HasOne("Flaro.Core.Users.User", null) + .WithMany() + .HasForeignKey("CreatedByUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Flaro.Core.Library.Credit", b => + { + b.HasOne("Flaro.Core.Library.Movie", null) + .WithMany() + .HasForeignKey("MovieId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Flaro.Core.Library.Person", null) + .WithMany() + .HasForeignKey("PersonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Flaro.Core.Library.Series", null) + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Flaro.Core.Library.Episode", b => + { + b.HasOne("Flaro.Core.Library.Series", null) + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Flaro.Core.Library.Fremdbewertung", b => + { + b.HasOne("Flaro.Core.Library.Movie", null) + .WithMany() + .HasForeignKey("MovieId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Flaro.Core.Library.Series", null) + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Flaro.Core.Library.GenreLink", b => + { + b.HasOne("Flaro.Core.Library.Genre", null) + .WithMany() + .HasForeignKey("GenreId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Flaro.Core.Library.Movie", null) + .WithMany() + .HasForeignKey("MovieId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Flaro.Core.Library.Series", null) + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Flaro.Core.Library.Marke", b => + { + b.HasOne("Flaro.Core.Library.Episode", null) + .WithMany() + .HasForeignKey("EpisodeId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Flaro.Core.Library.Movie", null) + .WithMany() + .HasForeignKey("MovieId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Flaro.Core.Library.Markensuche", b => + { + b.HasOne("Flaro.Core.Library.Movie", null) + .WithMany() + .HasForeignKey("MovieId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Flaro.Core.Library.Series", null) + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Flaro.Core.Library.MediaFile", b => + { + b.HasOne("Flaro.Core.Libraries.Library", null) + .WithMany() + .HasForeignKey("LibraryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Flaro.Core.Library.MediaStream", b => + { + b.HasOne("Flaro.Core.Library.MediaFile", null) + .WithMany() + .HasForeignKey("MediaFileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Flaro.Core.Library.Meldung", b => + { + b.HasOne("Flaro.Core.Library.Episode", null) + .WithMany() + .HasForeignKey("EpisodeId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Flaro.Core.Library.Movie", null) + .WithMany() + .HasForeignKey("MovieId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Flaro.Core.Library.Series", null) + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Flaro.Core.Users.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Flaro.Core.Library.Movie", b => + { + b.HasOne("Flaro.Core.Libraries.Library", null) + .WithMany() + .HasForeignKey("LibraryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Flaro.Core.Library.Series", b => + { + b.HasOne("Flaro.Core.Libraries.Library", null) + .WithMany() + .HasForeignKey("LibraryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Flaro.Core.Library.Stichwortlink", b => + { + b.HasOne("Flaro.Core.Library.Movie", null) + .WithMany() + .HasForeignKey("MovieId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Flaro.Core.Library.Series", null) + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Flaro.Core.Library.Stichwort", null) + .WithMany() + .HasForeignKey("StichwortId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Flaro.Core.Playback.Sehtag", b => + { + b.HasOne("Flaro.Core.Library.Episode", null) + .WithMany() + .HasForeignKey("EpisodeId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Flaro.Core.Library.Movie", null) + .WithMany() + .HasForeignKey("MovieId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Flaro.Core.Users.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Flaro.Core.Playback.UserItemState", b => + { + b.HasOne("Flaro.Core.Library.Episode", null) + .WithMany() + .HasForeignKey("EpisodeId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Flaro.Core.Library.Movie", null) + .WithMany() + .HasForeignKey("MovieId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Flaro.Core.Library.Series", null) + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Flaro.Core.Users.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/server/src/Flaro.Data/Migrations/20261015142742_Abspannstuecke.cs b/server/src/Flaro.Data/Migrations/20261015142742_Abspannstuecke.cs new file mode 100644 index 0000000..eb55123 --- /dev/null +++ b/server/src/Flaro.Data/Migrations/20261015142742_Abspannstuecke.cs @@ -0,0 +1,58 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Flaro.Data.Migrations +{ + /// + public partial class Abspannstuecke : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_Marken_EpisodeId_Art", + table: "Marken"); + + migrationBuilder.DropIndex( + name: "IX_Marken_MovieId_Art", + table: "Marken"); + + migrationBuilder.CreateIndex( + name: "IX_Marken_EpisodeId_Art", + table: "Marken", + columns: new[] { "EpisodeId", "Art" }); + + migrationBuilder.CreateIndex( + name: "IX_Marken_MovieId_Art", + table: "Marken", + columns: new[] { "MovieId", "Art" }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_Marken_EpisodeId_Art", + table: "Marken"); + + migrationBuilder.DropIndex( + name: "IX_Marken_MovieId_Art", + table: "Marken"); + + migrationBuilder.CreateIndex( + name: "IX_Marken_EpisodeId_Art", + table: "Marken", + columns: new[] { "EpisodeId", "Art" }, + unique: true, + filter: "\"EpisodeId\" IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_Marken_MovieId_Art", + table: "Marken", + columns: new[] { "MovieId", "Art" }, + unique: true, + filter: "\"MovieId\" IS NOT NULL"); + } + } +} diff --git a/server/src/Flaro.Data/Migrations/FlaroContextModelSnapshot.cs b/server/src/Flaro.Data/Migrations/FlaroContextModelSnapshot.cs index 8c4cf0e..4753f62 100644 --- a/server/src/Flaro.Data/Migrations/FlaroContextModelSnapshot.cs +++ b/server/src/Flaro.Data/Migrations/FlaroContextModelSnapshot.cs @@ -432,13 +432,9 @@ namespace Flaro.Data.Migrations b.HasKey("Id"); - b.HasIndex("EpisodeId", "Art") - .IsUnique() - .HasFilter("\"EpisodeId\" IS NOT NULL"); + b.HasIndex("EpisodeId", "Art"); - b.HasIndex("MovieId", "Art") - .IsUnique() - .HasFilter("\"MovieId\" IS NOT NULL"); + b.HasIndex("MovieId", "Art"); b.ToTable("Marken"); }); diff --git a/server/src/Flaro.Media/Bildabtaster.cs b/server/src/Flaro.Media/Bildabtaster.cs index ccc6360..985fa85 100644 --- a/server/src/Flaro.Media/Bildabtaster.cs +++ b/server/src/Flaro.Media/Bildabtaster.cs @@ -92,9 +92,56 @@ public sealed partial class Bildabtaster(FfmpegOptions optionen, ILogger p.Dunkelanteil); } + /// + /// Ein crop-Ausdruck vor der Messung, oder null für das + /// ganze Bild. Nur das Messwerkzeug benutzt das. + /// + /// Drei Kurven über denselben Abschnitt — zum Nachsehen. + /// + /// + /// + /// Ein Werkzeug, keine Erkennung. Die Randstreifen sind eine + /// Vermutung: bei Outtakes im Abspann läuft das Bild vielleicht in der + /// Mitte und die Ränder bleiben schwarz. Ob das trägt, sieht man nur an + /// echten Dateien — und dafür braucht es die Zahlen, bevor eine Schwelle + /// gesetzt wird. + /// + /// + /// Drei Durchläufe, nicht einer. Zwei blackframe in einer + /// Kette schrieben denselben Metadatenschlüssel und überschrieben + /// einander; mit split ginge es, kostete aber drei Ausgabewege + /// und eine Zusammenführung. Für etwas, das jemand einmal je Film von + /// Hand anstösst, sind drei Läufe über Schlüsselbilder das günstigere + /// Geschäft. + /// + /// + public async Task> KurveAsync( + string pfad, double ab, double dauer, CancellationToken ct = default) + { + async Task> Lauf(string? zuschnitt) => Lesen( + await LaufenAsync(pfad, ab, dauer, nurSchluesselbilder: true, + TimeSpan.FromMinutes(10), ct, zuschnitt), + ab); + + var gesamt = await Lauf(null); + var links = await Lauf("crop=iw*0.2:ih:0:0"); + var rechts = await Lauf("crop=iw*0.2:ih:iw*0.8:0"); + + // Nach Zeit zusammenführen: die drei Läufe sehen dieselben + // Schlüsselbilder, aber darauf zu bauen wäre eine Annahme. + double BeiZeit(IReadOnlyList reihe, double sekunde) => + reihe.Count == 0 ? double.NaN + : reihe.MinBy(p => Math.Abs(p.Sekunde - sekunde)).Dunkelanteil; + + return gesamt + .Select(p => new Kurvenpunkt( + p.Sekunde, p.Dunkelanteil, BeiZeit(links, p.Sekunde), BeiZeit(rechts, p.Sekunde))) + .ToList(); + } + private async Task LaufenAsync( string pfad, double ab, double dauer, bool nurSchluesselbilder, - TimeSpan frist, CancellationToken ct) + TimeSpan frist, CancellationToken ct, string? zuschnitt = null) { var start = new ProcessStartInfo(optionen.Ffmpeg) { @@ -135,7 +182,8 @@ public sealed partial class Bildabtaster(FfmpegOptions optionen, ILoggerDer Anfang des ersten Stücks — oder nichts. + /// + /// + /// Die meisten Prüfungen hier fragen nur „wo fängt der Abspann an". + /// Seit die Suche Stücke zurückgibt, steht das in [0].Von; ein + /// Helfer hält die Erwartungen lesbar, statt überall zu indizieren. + /// + private static double? Anfang(IReadOnlyList stücke) => + stücke.Count == 0 ? null : stücke[0].Von; + /// Eine Reihe von Messpunkten im Abstand von zehn Sekunden. /// /// Ab wann dunkel. null heisst: nie. @@ -31,7 +41,7 @@ public class AbspannkanteTests { var proben = Reihe(0, 600, abspannAb: 420); - Assert.That(Abspannkante.Suchen(proben, Regeln), Is.EqualTo(420)); + Assert.That(Anfang(Abspannkante.Suchen(proben, Regeln)), Is.EqualTo(420)); } /// Ein Film, der hell endet, hat keinen erkennbaren Abspann. @@ -44,7 +54,7 @@ public class AbspannkanteTests [Test] public void Ohne_dunkles_Ende_kein_Fund() { - Assert.That(Abspannkante.Suchen(Reihe(0, 600, abspannAb: null), Regeln), Is.Null); + Assert.That(Anfang(Abspannkante.Suchen(Reihe(0, 600, abspannAb: null), Regeln)), Is.Null); } /// Eine Schwarzblende mitten im Film zählt nicht. @@ -54,7 +64,7 @@ public class AbspannkanteTests var proben = Reihe(0, 600, abspannAb: null); for (var i = 20; i < 30; i++) proben[i] = proben[i] with { Dunkelanteil = 92 }; - Assert.That(Abspannkante.Suchen(proben, Regeln), Is.Null, + Assert.That(Anfang(Abspannkante.Suchen(proben, Regeln)), Is.Null, "sie hält nicht bis zum Ende durch"); } @@ -70,7 +80,7 @@ public class AbspannkanteTests for (var i = proben.Count - 5; i < proben.Count; i++) proben[i] = proben[i] with { Dunkelanteil = 0 }; - Assert.That(Abspannkante.Suchen(proben, Regeln), Is.Null); + Assert.That(Anfang(Abspannkante.Suchen(proben, Regeln)), Is.Null); } /// Eine helle Schrifttafel mittendrin bricht die Strecke nicht. @@ -89,21 +99,21 @@ public class AbspannkanteTests var proben = Reihe(0, 600, abspannAb: 420); proben[50] = proben[50] with { Dunkelanteil = 0 }; - Assert.That(Abspannkante.Suchen(proben, Regeln), Is.EqualTo(420)); + Assert.That(Anfang(Abspannkante.Suchen(proben, Regeln)), Is.EqualTo(420)); } /// Zwanzig Sekunden Dunkelheit sind kein Abspann. [Test] public void Zu_kurz_zaehlt_nicht() { - Assert.That(Abspannkante.Suchen(Reihe(0, 600, abspannAb: 580), Regeln), Is.Null); + Assert.That(Anfang(Abspannkante.Suchen(Reihe(0, 600, abspannAb: 580), Regeln)), Is.Null); } /// Ein halber Film im Dunkeln ist ein Fehlfund. [Test] public void Zu_lang_zaehlt_auch_nicht() { - Assert.That(Abspannkante.Suchen(Reihe(0, 3600, abspannAb: 600), Regeln), Is.Null); + Assert.That(Anfang(Abspannkante.Suchen(Reihe(0, 3600, abspannAb: 600), Regeln)), Is.Null); } [Test] @@ -199,6 +209,16 @@ public class AbspannAnteilTests { private static readonly Abspannregeln Regeln = Abspannregeln.Vorgabe; + /// Der Anfang des ersten Stücks — oder nichts. + /// + /// + /// Die meisten Prüfungen hier fragen nur „wo fängt der Abspann an". + /// Seit die Suche Stücke zurückgibt, steht das in [0].Von; ein + /// Helfer hält die Erwartungen lesbar, statt überall zu indizieren. + /// + private static double? Anfang(IReadOnlyList stücke) => + stücke.Count == 0 ? null : stücke[0].Von; + /// Ein Suchfenster, wie es im Betrieb entsteht. /// /// @@ -220,7 +240,7 @@ public class AbspannAnteilTests { var proben = Fenster(abspannAb: 2340, imAbspann: 84); - Assert.That(Abspannkante.Suchen(proben, Regeln), Is.EqualTo(2340.0).Within(5)); + Assert.That(Anfang(Abspannkante.Suchen(proben, Regeln)), Is.EqualTo(2340.0).Within(5)); } [Test] @@ -228,7 +248,7 @@ public class AbspannAnteilTests { var proben = Fenster(abspannAb: 2340, imAbspann: 87); - Assert.That(Abspannkante.Suchen(proben, Regeln), Is.EqualTo(2340.0).Within(5)); + Assert.That(Anfang(Abspannkante.Suchen(proben, Regeln)), Is.EqualTo(2340.0).Within(5)); } /// **Eine dunkle Nachtszene ist kein Abspann.** @@ -244,7 +264,7 @@ public class AbspannAnteilTests { var proben = Fenster(abspannAb: 2340, imAbspann: 37, imInhalt: 12); - Assert.That(Abspannkante.Suchen(proben, Regeln), Is.Null); + Assert.That(Anfang(Abspannkante.Suchen(proben, Regeln)), Is.Null); } /// Und ein heller Film bis zum Ende bleibt Handlung. @@ -253,7 +273,7 @@ public class AbspannAnteilTests { var proben = Fenster(abspannAb: double.MaxValue, imAbspann: 99); - Assert.That(Abspannkante.Suchen(proben, Regeln), Is.Null); + Assert.That(Anfang(Abspannkante.Suchen(proben, Regeln)), Is.Null); } } @@ -270,6 +290,16 @@ public class AbspannSchlusstafelTests { private static readonly Abspannregeln Regeln = Abspannregeln.Vorgabe; + /// Der Anfang des ersten Stücks — oder nichts. + /// + /// + /// Die meisten Prüfungen hier fragen nur „wo fängt der Abspann an". + /// Seit die Suche Stücke zurückgibt, steht das in [0].Von; ein + /// Helfer hält die Erwartungen lesbar, statt überall zu indizieren. + /// + private static double? Anfang(IReadOnlyList stücke) => + stücke.Count == 0 ? null : stücke[0].Von; + private static List Fenster(double abspannAb, double tafelAb = double.MaxValue) { var proben = new List(); @@ -288,7 +318,7 @@ public class AbspannSchlusstafelTests // Fünfzehn Sekunden weiss hinter dem Abspann. var proben = Fenster(abspannAb: 2340, tafelAb: 2690); - Assert.That(Abspannkante.Suchen(proben, Regeln), Is.EqualTo(2340).Within(5)); + Assert.That(Anfang(Abspannkante.Suchen(proben, Regeln)), Is.EqualTo(2340).Within(5)); } /// Eine lange helle Strecke am Ende schon. @@ -302,7 +332,7 @@ public class AbspannSchlusstafelTests { var proben = Fenster(abspannAb: 2340, tafelAb: 2600); - Assert.That(Abspannkante.Suchen(proben, Regeln), Is.Null); + Assert.That(Anfang(Abspannkante.Suchen(proben, Regeln)), Is.Null); } } @@ -329,6 +359,16 @@ public class AbspannNachsichtTests { private static readonly Abspannregeln Regeln = Abspannregeln.Vorgabe; + /// Der Anfang des ersten Stücks — oder nichts. + /// + /// + /// Die meisten Prüfungen hier fragen nur „wo fängt der Abspann an". + /// Seit die Suche Stücke zurückgibt, steht das in [0].Von; ein + /// Helfer hält die Erwartungen lesbar, statt überall zu indizieren. + /// + private static double? Anfang(IReadOnlyList stücke) => + stücke.Count == 0 ? null : stücke[0].Von; + /// Abspann ab 2340 s, darin fünf Sekunden helles Logo. private static List Fenster(double takt) { @@ -345,7 +385,7 @@ public class AbspannNachsichtTests [Test] public void Ein_Logo_zerreisst_den_Abspann_nicht_bei_dichten_Proben() { - Assert.That(Abspannkante.Suchen(Fenster(takt: 0.9), Regeln), + Assert.That(Anfang(Abspannkante.Suchen(Fenster(takt: 0.9), Regeln)), Is.EqualTo(2340.0).Within(2)); } @@ -353,18 +393,20 @@ public class AbspannNachsichtTests [Test] public void Und_auch_nicht_bei_duennen_Proben() { - Assert.That(Abspannkante.Suchen(Fenster(takt: 10), Regeln), + Assert.That(Anfang(Abspannkante.Suchen(Fenster(takt: 10), Regeln)), Is.EqualTo(2340.0).Within(11)); } - /// Eine lange helle Strecke bricht weiterhin ab. + /// Eine Minute helles Bild mitten im Abspann ist eine Szene. /// /// - /// Sonst liefe die Suche rückwärts durch jede Nachtszene und meldete - /// den halben Film als Abspann. + /// Früher brach die Suche hier ab und meldete nur das Stück + /// dahinter. Genau daran scheiterten Marvel-Filme: hinter dem Abspann + /// eine Post-Credit-Szene, davor der eigentliche Abspann — und + /// gefunden wurde nur der kurze Rest, der dann als zu kurz durchfiel. /// [Test] - public void Eine_lange_helle_Strecke_bricht_die_Suche() + public void Eine_Minute_hell_mittendrin_teilt_den_Abspann_in_zwei_Stuecke() { var proben = new List(); for (var t = 1500.0; t <= 2700; t += 1) @@ -374,8 +416,17 @@ public class AbspannNachsichtTests proben.Add(new Bildprobe(t, hell ? 0 : 85)); } - // Gefunden wird nur das Stück hinter der hellen Minute. - Assert.That(Abspannkante.Suchen(proben, Regeln), Is.EqualTo(2460.0).Within(2)); + var stücke = Abspannkante.Suchen(proben, Regeln); + + Assert.Multiple(() => + { + Assert.That(stücke, Has.Count.EqualTo(2)); + Assert.That(stücke[0].Von, Is.EqualTo(2340.0).Within(2)); + Assert.That(stücke[0].Bis, Is.EqualTo(2399.0).Within(2)); + Assert.That(stücke[1].Von, Is.EqualTo(2460.0).Within(2)); + Assert.That(stücke[1].Bis, Is.EqualTo(2700.0).Within(2), + "das letzte Stück reicht bis ans Dateiende"); + }); } /// Und der Grund steht für das Protokoll bereit. @@ -386,13 +437,125 @@ public class AbspannNachsichtTests for (var t = 1500.0; t <= 2700; t += 5) hell.Add(new Bildprobe(t, 0)); Abspannkante.Suchen(hell, Regeln, out var grund); - Assert.That(grund, Does.Contain("hell")); + Assert.That(grund, Does.Contain("keine dunkle Strecke")); var kurz = new List(); for (var t = 1500.0; t <= 2700; t += 5) kurz.Add(new Bildprobe(t, t < 2680 ? 0 : 85)); Abspannkante.Suchen(kurz, Regeln, out var zuKurz); - Assert.That(zuKurz, Does.Contain("kurz")); + Assert.That(zuKurz, Does.Contain("zusammen nur")); + } +} + +/// Ein Abspann darf aus Stücken bestehen. +/// +/// +/// +/// Wofür diese Prüfungen stehen. Zwei Fälle fielen durch, beide am +/// Bestand gesehen. Bei einem Marvel-Film liegt hinter dem Abspann eine +/// Post-Credit-Szene: die Suche begann am Dateiende, fand nur den kurzen +/// Rest dahinter und verwarf ihn als zu kurz. Und ein Abspann, den eine +/// Szene in der Mitte teilt, zerfiel in zwei Reste, von denen keiner lang +/// genug war. +/// +/// +/// Die Antwort ist dieselbe: Stücke über die Lücke hinweg zusammenfassen +/// und die Summe über die Mindestlänge entscheiden lassen. +/// +/// +public class AbspannstueckeTests +{ + private static readonly Abspannregeln Regeln = Abspannregeln.Vorgabe; + + /// Eine Reihe im Sekundentakt, dunkel in den angegebenen Spannen. + private static List Reihe(double bis, params (double Von, double Bis)[] dunkel) + { + var proben = new List(); + for (var t = 0.0; t <= bis; t += 1) + { + var istDunkel = Array.Exists(dunkel, d => t >= d.Von && t < d.Bis); + proben.Add(new Bildprobe(t, istDunkel ? 85 : 0)); + } + return proben; + } + + /// Der Marvel-Fall: Abspann, Szene, Abspann. + [Test] + public void Zwei_Stuecke_mit_einer_Szene_dazwischen() + { + // 180 s Abspann, 90 s Szene, 60 s Abspann bis zum Ende. + var stücke = Abspannkante.Suchen(Reihe(600, (330, 510), (540, 601)), Regeln); + + Assert.Multiple(() => + { + Assert.That(stücke, Has.Count.EqualTo(2)); + Assert.That(stücke[0].Von, Is.EqualTo(330.0).Within(2)); + Assert.That(stücke[1].Von, Is.EqualTo(540.0).Within(2)); + Assert.That(stücke[1].Bis, Is.EqualTo(600.0).Within(2)); + }); + } + + /// **Die Summe entscheidet, nicht das einzelne Stück.** + /// + /// + /// Genau hier scheiterte es vorher: 30 s plus 30 s sind zusammen 60 s + /// und damit ein Abspann, einzeln aber zweimal zu wenig. + /// + [Test] + public void Zwei_kurze_Stuecke_ergeben_zusammen_einen_Abspann() + { + var stücke = Abspannkante.Suchen(Reihe(600, (450, 480), (570, 601)), Regeln); + + Assert.Multiple(() => + { + Assert.That(stücke, Has.Count.EqualTo(2)); + Assert.That(stücke.Sum(t => t.Länge), Is.GreaterThanOrEqualTo(Regeln.Mindestlänge)); + }); + } + + /// Eine zu lange Lücke trennt — dann zählt nur das letzte Stück. + /// + /// + /// Sonst liefe die Suche rückwärts durch jede Nachtszene und hinge den + /// halben Film an den Abspann. Hinter einer Lücke von fünf Minuten + /// bleibt hier ein Stück von 30 s übrig, und das ist zu kurz. + /// + [Test] + public void Eine_zu_lange_Luecke_trennt() + { + var stücke = Abspannkante.Suchen(Reihe(900, (200, 400), (870, 901)), Regeln); + + Assert.That(stücke, Is.Empty); + } + + /// Ein zu kurzes Stück zwischen zwei gültigen zählt nicht mit. + [Test] + public void Ein_Stueck_von_zehn_Sekunden_faellt_heraus() + { + // Das mittlere Stück ist 10 s lang und damit unter `Mindeststück`. + var stücke = Abspannkante.Suchen( + Reihe(600, (300, 360), (450, 460), (540, 601)), Regeln); + + Assert.That(stücke, Has.Count.EqualTo(2), "das Zehnsekundenstück gehört nicht dazu"); + } + + /// Und ein durchgehender Abspann bleibt ein Stück. + /// + /// + /// Die wichtigste Prüfung dieser Gruppe: der Normalfall darf sich durch + /// die Stückelung nicht verändern. + /// + [Test] + public void Ein_durchgehender_Abspann_bleibt_ein_Stueck() + { + var stücke = Abspannkante.Suchen(Reihe(600, (420, 601)), Regeln); + + Assert.Multiple(() => + { + Assert.That(stücke, Has.Count.EqualTo(1)); + Assert.That(stücke[0].Von, Is.EqualTo(420.0).Within(2)); + Assert.That(stücke[0].Bis, Is.EqualTo(600.0).Within(2)); + }); } } diff --git a/server/tests/Flaro.Media.Tests/BildabtasterTests.cs b/server/tests/Flaro.Media.Tests/BildabtasterTests.cs index 04da549..c0af0df 100644 --- a/server/tests/Flaro.Media.Tests/BildabtasterTests.cs +++ b/server/tests/Flaro.Media.Tests/BildabtasterTests.cs @@ -127,13 +127,14 @@ public class BildabtasterLaufTests var proben = await Abtaster().UeberblickAsync(_datei, ab: 30, dauer: 120); Assert.That(proben, Is.Not.Empty, "der Filtergraph muss Zahlen liefern"); - var grob = Abspannkante.Suchen(proben, Abspannregeln.Vorgabe); - Assert.That(grob, Is.Not.Null, "eine dunkle Strecke bis zum Ende muss auffallen"); + var stücke = Abspannkante.Suchen(proben, Abspannregeln.Vorgabe); + Assert.That(stücke, Is.Not.Empty, "eine dunkle Strecke bis zum Ende muss auffallen"); - var hell = Abspannkante.HellDavor(proben, grob!.Value, Abspannregeln.Vorgabe) ?? 0; + var grob = stücke[0].Von; + var hell = Abspannkante.HellDavor(proben, grob, Abspannregeln.Vorgabe) ?? 0; var abtaster = Abtaster(); var kante = await Abspannkante.VerfeinernAsync( - hell, grob.Value, Abspannregeln.Vorgabe.Mindestanteil, + hell, grob, Abspannregeln.Vorgabe.Mindestanteil, stelle => abtaster.DunkelanteilAsync(_datei, stelle)); Assert.That(kante, Is.EqualTo(90).Within(4), @@ -146,7 +147,7 @@ public class BildabtasterLaufTests { var proben = await Abtaster().UeberblickAsync(_datei, ab: 0, dauer: 80); - Assert.That(Abspannkante.Suchen(proben, Abspannregeln.Vorgabe), Is.Null); + Assert.That(Abspannkante.Suchen(proben, Abspannregeln.Vorgabe), Is.Empty); } /// Eine einzelne Sekunde lässt sich messen — und liegt richtig. diff --git a/web/app/src/api.ts b/web/app/src/api.ts index 17efbaf..b70cc04 100644 --- a/web/app/src/api.ts +++ b/web/app/src/api.ts @@ -701,6 +701,14 @@ export async function markenHolen(titelId: string): Promise { export const mediathekMarken = (id: string, umfang: Markenumfang) => anfrage(`/libraries/${id}/marken?umfang=${umfang}`, { method: 'POST' }); +/** Die Adresse der Messkurve eines Films — zum Herunterladen. + * + * **Keine Erkennung, sondern Zahlen zum Ansehen.** Welche Schwelle bei + * einem Abspann über laufendem Bild trägt, lässt sich nur an der Kurve + * entscheiden, nicht am Ergebnis der Erkennung. */ +export const markenkurveAdresse = (filmId: string) => + `/api/v1/movies/${filmId}/markenkurve`; + /** Nimmt die Marken eines Films wieder weg — wenn die Erkennung danebenlag. */ export const filmmarkenVerwerfen = (filmId: string) => anfrage(`/movies/${filmId}/marken`, { method: 'DELETE' }); diff --git a/web/app/src/spieler.ts b/web/app/src/spieler.ts index d9312e5..2d3feb8 100644 --- a/web/app/src/spieler.ts +++ b/web/app/src/spieler.ts @@ -886,9 +886,16 @@ export function spieler(anfrage: Spieleranfrage): void { weiter ? 'btn' : 'btn btn-primary'); u.onclick = e => { e.stopPropagation(); - // Bei einem Film und bei der letzten Folge heisst „überspringen" - // Schluss: ans Ende zu spulen liesse ein stehendes Bild zurück. - if (abspann && !weiter) { void beenden(); return; } + + // **Kommt hinter diesem Stück noch eines, ist der Film nicht aus.** + // + // Ein Abspann kann geteilt sein: davor der eine Teil, dann eine + // Post-Credit-Szene, dann der Rest. Hier stand „bei einem Film + // heisst überspringen Schluss" — und damit bekam niemand die Szene + // je zu sehen. Geschlossen wird nur, wenn nach dieser Marke nichts + // mehr kommt. + const danach = marken.some(m => m.startSeconds >= drin.endSeconds - 1); + if (abspann && !weiter && !danach) { void beenden(); return; } void springen(drin.endSeconds); }; markenreihe.appendChild(u); diff --git a/web/app/src/views/detail.ts b/web/app/src/views/detail.ts index e601860..fde35e0 100644 --- a/web/app/src/views/detail.ts +++ b/web/app/src/views/detail.ts @@ -3,7 +3,7 @@ import { ApiFehler, vorschlaege, zuordnen, bildAdresse, istAdmin, gesehen, favorit, weglegen, serienstimmen, type Film, type Serie, type Folge, type Datei, type Stand, type Mitwirkender, type Spur, aehnliche, metadatenAuffrischen, scanstand, markenSuchen, - filmmarkenSuchen, filmmarkenVerwerfen, + filmmarkenSuchen, filmmarkenVerwerfen, markenkurveAdresse, } from '../api'; import { esc, dauer, groesse, knopf, feld, fehlerzeile, leer, anteil, meldung, hinweisZeigen, @@ -521,6 +521,16 @@ export function detailseite(art: Art, id: string, zurueck: () => void): HTMLElem window.setTimeout(() => void laden(), 4000); })); + // **Die Zahlen hinter dem Urteil.** „Kein Abspann" nennt den Grund, + // aber nicht die Kurve — und ohne die lässt sich keine Schwelle + // begründen. Der Knopf lädt sie als Tabelle herunter. + reihe.appendChild(rundKnopf('protokoll', false, 'Messwerte des Abspanns holen', + async () => { + window.location.href = markenkurveAdresse(eintrag.id); + hinweisZeigen('Die Messung läuft — der Server sieht sich dafür die ' + + 'letzten zwanzig Minuten dreimal an.'); + })); + // **Nur, wenn es etwas zu verwerfen gibt.** Ein Knopf, der nichts // wegnehmen kann, ist eine Behauptung über einen Zustand. if ('markers' in eintrag && (eintrag.markers?.length ?? 0) > 0) {