From 9955fb9b0e17972637b435081a912781be6b49de Mon Sep 17 00:00:00 2001 From: CuriousApe2020 Date: Thu, 10 Sep 2026 22:18:00 +0000 Subject: [PATCH 1/2] Sprache der Tonspur als eigenes Feld in der Filmliste Die Sprache einer Tonspur liegt beim Crawlen teilweise vor, geht beim Erzeugen des Filmliste-Eintrags aber verloren: kodiert wird nur der deutsche Titelzusatz "(Originalversion)", der die Sprache nicht nennt. Clients koennen sie danach nicht mehr rekonstruieren. Neu ist Film.audioLanguage (ISO 639-2/T, null wenn unbekannt). Woher die Sprache kommt: * Die ARD liefert je Tonspur einen Sprachcode in audio[0].languageCode. Die Streamauswahl probiert davon allerdings nur die fest verdrahteten Codes "deu", "eng" und "fra" durch; alles andere faellt in den Platzhalter "*". * Fuer "eng" und "fra" steht der Code damit ohnehin fest. * Fuer den Platzhalterzweig wird er mit parseAudioLanguageCode aus den Daten gelesen statt aus dem zutreffenden Filter abgeleitet. Damit behaelt auch eine Originalversion in einer anderen Sprache - spanisch, polnisch, tuerkisch - ihre Sprache, ohne dass dafuer je Sprache eine weitere Konstante noetig waere. * normalizeLanguageCode sichert die zugesagte Form: eine etwaige Regionsangabe wird abgeschnitten ("spa-ES" wird zu "spa"), alles was danach kein dreistelliger Buchstabencode ist - ARDs Platzhalter "ov", Leerwerte - ergibt nichts. Lieber kein Wert als einer, auf den sich Clients nicht verlassen koennen. * Nur die Originalfassung traegt die Sprache. Die Hauptfassung bleibt leer: sie ist die uebliche Fassung des Senders, und ein Wert auf jedem Datensatz wuerde die Filmliste unnoetig aufblaehen. Film.merge uebernimmt eine bekannte Sprache, wenn der eigene Stand keine hat. Sonst ginge sie verloren, sobald eine aeltere importierte Filmliste ohne das Feld mit einem frischen Crawl zusammengefuehrt wird. Serialisierung: * Neues Format: Gson serialisiert den Objektgraphen per Reflection, das Feld erscheint automatisch als benannter Schluessel; FilmlistReader liest es symmetrisch wieder ein. * Altes Format: hinten angefuegte Spalte "Sprache" als einfacher String. Bewusst kein Array - der Desktop-Client prueft beim Weiterlesen mit isExpectedStartArrayToken(), ob der naechste Token ein Datensatz ist; ein angehaengtes Array wuerde dort als Filmdatensatz interpretiert. Rueckwaertskompatibilitaet: * MediathekView Desktop 14.0.0 bis 14.5.0 liest je Datensatz genau 20 Felder und sucht danach den naechsten START_ARRAY. Ein angehaengtes Feld faellt durch diese Pruefung und wird uebersprungen - keine Exception, kein Versatz. Die Spaltenkopfzeile wertet der Client gar nicht aus (skipFieldDescriptions). * MediathekViewWeb destrukturiert positionsbasiert bis Index 16 und ignoriert weitere Felder. * FilmlistOldFormatReader liest die neue Spalte optional: Filmlisten aelterer Staende ohne die Spalte werden weiterhin gelesen. Ausserdem: FilmlistOldFormatWriterTest las die vom Writer als UTF-8 geschriebene Datei mit StandardCharsets.UTF_16 ein. Das lief nur durch, solange die Dateilaenge zufaellig gerade war; durch die zusaetzliche Spalte wurde sie ungerade und readString warf eine MalformedInputException. Korrigiert auf UTF-8, passend zum Writer. Tests: Round-Trip durch beide Formate, eine Filmliste ohne die neue Spalte, eine spanische Originalversion, ein Code mit Regionsangabe, die Gegenprobe mit ARDs Platzhalter "ov", das Verhalten von merge, sowie ein Beitrag mit deutscher und englischer Tonspur - dort darf die Hauptfassung die Sprache der Originalversion nicht erben. --- .../crawler/ard/json/ArdFilmDeserializer.java | 120 ++++- .../crawler/ard/json/ArdVideoInfoDto.java | 15 + .../de/mediathekview/mserver/daten/Film.java | 28 + .../reader/FilmlistOldFormatReader.java | 18 + .../writer/FilmlistOldFormatWriter.java | 19 + .../ArdFilmDeserializerAudioLanguageTest.java | 105 ++++ .../FilmlistOldFormatWriterTest.java | 86 ++- .../NewFilmlistAudioLanguageTest.java | 93 ++++ .../ard/ard_item_OV_eng_plus_deu.json | 500 ++++++++++++++++++ .../resources/ard/ard_item_OV_spanish.json | 405 ++++++++++++++ .../ard/ard_item_OV_spanish_region.json | 1 + .../mlib/TestFilmlistOldFormatWriter.json | 2 +- 12 files changed, 1376 insertions(+), 16 deletions(-) create mode 100644 src/test/java/de/mediathekview/mserver/crawler/ard/json/ArdFilmDeserializerAudioLanguageTest.java create mode 100644 src/test/java/de/mediathekview/mserver/filmlisten/NewFilmlistAudioLanguageTest.java create mode 100644 src/test/resources/ard/ard_item_OV_eng_plus_deu.json create mode 100644 src/test/resources/ard/ard_item_OV_spanish.json create mode 100644 src/test/resources/ard/ard_item_OV_spanish_region.json diff --git a/src/main/java/de/mediathekview/mserver/crawler/ard/json/ArdFilmDeserializer.java b/src/main/java/de/mediathekview/mserver/crawler/ard/json/ArdFilmDeserializer.java index 8b16c3611..16b34ebe5 100644 --- a/src/main/java/de/mediathekview/mserver/crawler/ard/json/ArdFilmDeserializer.java +++ b/src/main/java/de/mediathekview/mserver/crawler/ard/json/ArdFilmDeserializer.java @@ -223,19 +223,23 @@ public List deserialize( ArdVideoInfoDto allVideoUrlsOV = new ArdVideoInfoDto(); allVideoUrlsOV.putAll(videoInfo.get().getVideoUrlsOV()); allVideoUrlsOV.setSubtitleUrl(videoInfo.get().getSubtitleUrl()); - final ArdFilmDto filmDtoOV = - new ArdFilmDto( - createFilm( - id.get(), - sender, - topic.get(), - title.get() + " (Originalversion)", - description.orElse(null), - date.orElse(null), - duration.orElse(null), - allVideoUrlsOV, - geoBlocked)); - films.add(filmDtoOV); + allVideoUrlsOV.setOvLanguage(videoInfo.get().getOvLanguage()); + final Film filmOV = + createFilm( + id.get(), + sender, + topic.get(), + title.get() + " (Originalversion)", + description.orElse(null), + date.orElse(null), + duration.orElse(null), + allVideoUrlsOV, + geoBlocked); + // Nur die Originalfassung traegt die Sprache. Die Hauptfassung bleibt leer: sie ist die + // uebliche Fassung des Senders, und ein Wert auf jedem Datensatz wuerde die Filmliste + // unnoetig aufblaehen. + filmOV.setAudioLanguage(videoInfo.get().getOvLanguage()); + films.add(new ArdFilmDto(filmOV)); } return films; @@ -430,15 +434,26 @@ private Optional parseVideos(final JsonObject playerPageObject, if (videoInfoOV.isEmpty() && videoInfoOVAdaptive.isPresent()) { videoInfoOV = getResolutionsFromAdaptiveUrl(videoInfoOVAdaptive); } + // Die Sprache ist hier bekannt - die Streams wurden oben getrennt nach "eng" und "fra" + // eingelesen. Bisher ging sie beim Zusammenfassen zur Originalversion verloren, sodass im + // Ergebnis nur noch der Titelzusatz "(Originalversion)" ohne Sprachangabe stand. + String ovLanguage = null; if (videoInfoOV.isEmpty() && videoInfoEng.isPresent()) { videoInfoOV = videoInfoEng; + ovLanguage = MARKER_VIDEO_ENG; } if (videoInfoOV.isEmpty() && videoInfoFra.isPresent()) { videoInfoOV = videoInfoFra; + ovLanguage = MARKER_VIDEO_FRA; } // flaws - missing proper video marker - mainly tagesschau if ((title.contains(" - (Originalversion)") || title.contains(" (OV)")) && videoInfoOV.isEmpty()) { videoInfoOV = parseVideoUrls(playerPageObject, MARKER_VIDEO_CATEGORY_MAIN, MARKER_VIDEO_STANDARD, MARKER_VIDEO_MP4, "*"); + // Dieser Zweig faengt jede Sprache ab, nicht nur die oben durchprobierten "eng" und "fra". + // Der Code wird deshalb aus den Daten gelesen statt aus dem zutreffenden Filter abgeleitet. + if (videoInfoOV.isPresent()) { + ovLanguage = parseAudioLanguageCode(playerPageObject, MARKER_VIDEO_CATEGORY_MAIN, MARKER_VIDEO_STANDARD, MARKER_VIDEO_MP4, "*").orElse(null); + } } if ((title.contains(" (mit Gebärdensprache)") || title.contains(" mit Gebärdensprache")) && videoInfoStandard.isPresent() && videoInfoDGS.isEmpty()) { videoInfoDGS = videoInfoStandard; @@ -454,6 +469,7 @@ private Optional parseVideos(final JsonObject playerPageObject, videoInfoDGS.ifPresent(allVideoUrls::putAllDGS); videoInfoOVAdaptive.ifPresent(x -> allVideoUrls.setAdaptivUrl(x.entrySet().stream().findFirst().get().getValue())); videoInfoOV.ifPresent(allVideoUrls::putAllOV); + allVideoUrls.setOvLanguage(ovLanguage); subtitles.ifPresent(allVideoUrls::setSubtitleUrl); if (allVideoUrls.getVideoUrls().isEmpty() && allVideoUrls.getVideoUrlsAD().isEmpty() && allVideoUrls.getVideoUrlsDGS().isEmpty() && allVideoUrls.getVideoUrlsOV().isEmpty() ) { @@ -559,4 +575,82 @@ private Optional> parseVideoUrlMap(final JsonObject playerP } return Optional.of(videoInfo); } + + /** + * Liefert den tatsaechlichen Sprachcode ({@code audio[0].languageCode}) des ersten Streams, der + * zum uebergebenen Filter passt. + * + *

Die Auswahl der Streams oben probiert nur die fest verdrahteten Codes "deu", "eng" und "fra" + * durch. Eine Originalversion in einer anderen Sprache - spanisch, polnisch, tuerkisch - landet + * ueber den Platzhalter "*" trotzdem korrekt als Originalversion, ihre Sprache waere aber nicht + * bekannt. Statt sie aus dem zutreffenden Filter zu erraten, wird sie hier aus den Daten gelesen. + * + *

Die Platzhalter "OV" und "*" selbst benennen keine Sprache; ARDs eigener Platzhalter "ov" + * im Feld wird deshalb verworfen. + */ + private Optional parseAudioLanguageCode(final JsonObject playerPageObject, String streamType, String aduioType, String mimeType, String language) { + final Optional mediaCollectionObject = getMediaCollectionObject(playerPageObject); + if (mediaCollectionObject.isEmpty()) { + return Optional.empty(); + } + final Optional streams = JsonUtils.getElement(mediaCollectionObject.get(), ELEMENT_STREAMS); + if (streams.isEmpty() || !streams.get().isJsonArray() || streams.get().getAsJsonArray().isEmpty()) { + return Optional.empty(); + } + for (JsonElement streamsCategory : streams.get().getAsJsonArray()) { + final Optional streamKind = JsonUtils.getElementValueAsString(streamsCategory, ATTRIBUTE_KIND); + final Optional media = JsonUtils.getElement(streamsCategory, ELEMENT_MEDIA); + if (media.isEmpty() || !media.get().isJsonArray() || media.get().getAsJsonArray().isEmpty()) { + continue; + } + if (!streamKind.orElse("").equalsIgnoreCase(streamType)) { + continue; + } + for (JsonElement video : media.get().getAsJsonArray()) { + final Optional mime = JsonUtils.getElementValueAsString(video, ATTRIBUTE_MIME); + if (mime.isEmpty() || !mime.get().equalsIgnoreCase(mimeType)) { + continue; + } + final Optional audios = JsonUtils.getElement(video, ELEMENT_AUDIO); + if (audios.isEmpty() || !audios.get().isJsonArray() || audios.get().getAsJsonArray().isEmpty()) { + continue; + } + final Optional kind = JsonUtils.getElementValueAsString(audios.get().getAsJsonArray().get(0), ATTRIBUTE_KIND); + final Optional languageCode = JsonUtils.getElementValueAsString(audios.get().getAsJsonArray().get(0), ATTRIBUTE_ADUIO_LANG); + if (kind.isEmpty() || !kind.get().equalsIgnoreCase(aduioType)) { + continue; + } + final String code = languageCode.orElse(""); + final boolean matches = code.equalsIgnoreCase(language) + || (language.equalsIgnoreCase("*") && !code.equalsIgnoreCase(MARKER_VIDEO_DE) && !code.equalsIgnoreCase("ov")); + if (matches) { + final Optional normalized = normalizeLanguageCode(code); + if (normalized.isPresent()) { + return normalized; + } + } + } + } + return Optional.empty(); + } + + /** + * Bringt einen Sprachcode aus den Senderdaten in die Form, die das Feld zusichert: dreistelliger + * ISO-639-2/T-Code in Kleinschreibung. + * + *

Eine etwaige Regionsangabe wird abgeschnitten ("spa-ES" wird zu "spa"). Alles, was danach + * kein dreistelliger Buchstabencode ist - ARDs Platzhalter "ov", ein leerer Wert, ein + * zweistelliger Code, den MServer mangels Tabelle nicht zuordnen koennte - ergibt nichts. Lieber + * kein Wert als einer, auf den sich Clients nicht verlassen koennen. + */ + private static Optional normalizeLanguageCode(final String rawCode) { + if (rawCode == null || rawCode.isBlank()) { + return Optional.empty(); + } + final String primary = rawCode.trim().split("-")[0].toLowerCase(Locale.ROOT); + if (primary.length() != 3 || !primary.chars().allMatch(Character::isLetter)) { + return Optional.empty(); + } + return "ov".equals(primary) ? Optional.empty() : Optional.of(primary); + } } diff --git a/src/main/java/de/mediathekview/mserver/crawler/ard/json/ArdVideoInfoDto.java b/src/main/java/de/mediathekview/mserver/crawler/ard/json/ArdVideoInfoDto.java index 7da7b4d7b..1a8dfde54 100644 --- a/src/main/java/de/mediathekview/mserver/crawler/ard/json/ArdVideoInfoDto.java +++ b/src/main/java/de/mediathekview/mserver/crawler/ard/json/ArdVideoInfoDto.java @@ -21,6 +21,13 @@ public class ArdVideoInfoDto { private final Map videoUrlsDGS; private final Map videoUrlsOV; private String adaptivUrl = null; + + /** + * Sprache der Originalversion als ISO-639-2/T-Code, sofern bekannt. Die ARD liefert die Streams + * je Sprache getrennt aus ("eng", "fra"); bisher wurde diese Zuordnung beim Zusammenfassen zur + * Originalversion verworfen. + */ + private String ovLanguage = null; private Set subtitleUrl; @@ -134,6 +141,14 @@ public String getAdaptivUrl() { return adaptivUrl; } + public String getOvLanguage() { + return ovLanguage; + } + + public void setOvLanguage(String ovLanguage) { + this.ovLanguage = ovLanguage; + } + public void setAdaptivUrl(String adaptivUrl) { this.adaptivUrl = adaptivUrl; } diff --git a/src/main/java/de/mediathekview/mserver/daten/Film.java b/src/main/java/de/mediathekview/mserver/daten/Film.java index 911ef6697..37996ee13 100644 --- a/src/main/java/de/mediathekview/mserver/daten/Film.java +++ b/src/main/java/de/mediathekview/mserver/daten/Film.java @@ -14,6 +14,17 @@ public class Film extends Podcast { private Map audioDescriptions; private Map signLanguages; + /** + * Sprache der Tonspur dieses Films als ISO-639-2/T-Code ({@code "deu"}, {@code "eng"}, + * {@code "fra"}), oder {@code null}, wenn der Sender sie nicht benennt. + * + *

Relevant vor allem fuer Originalversionen: die werden bisher nur ueber den Titelzusatz + * "(Originalversion)" kenntlich gemacht, der die Sprache nicht nennt. Sie geht damit verloren, + * obwohl sie beim Crawlen teilweise vorliegt - der ARD-Crawler liest die Streams bereits getrennt + * nach "eng" und "fra" ein. + */ + private String audioLanguage; + public Film( final UUID aUuid, final Sender aSender, @@ -32,6 +43,7 @@ public Film(final Film copyObj) { audioDescriptions = copyObj.audioDescriptions; signLanguages = copyObj.signLanguages; subtitles = copyObj.subtitles; + audioLanguage = copyObj.audioLanguage; } /** DON'T USE! - ONLY FOR GSON! */ @@ -53,9 +65,25 @@ public AbstractMediaResource merge(final AbstractMediaResource || urlEntry.getValue().getFileSize() > getUrls().get(urlEntry.getKey()).getFileSize()) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))); + // Beim Zusammenfuehren zweier Staende - etwa einer importierten aelteren Filmliste ohne das + // Feld und einem frischen Crawl - darf die einmal bekannte Sprache nicht verloren gehen. + if (audioLanguage == null && objToMergeWith instanceof Film filmToMergeWith) { + audioLanguage = filmToMergeWith.getAudioLanguage(); + } return this; } + /** + * @return Sprache der Tonspur als ISO-639-2/T-Code, oder {@code null} wenn unbekannt. + */ + public String getAudioLanguage() { + return audioLanguage; + } + + public void setAudioLanguage(final String aAudioLanguage) { + audioLanguage = aAudioLanguage; + } + public void addAllSubtitleUrls(final Set urlsToAdd) { subtitles.addAll(urlsToAdd); } diff --git a/src/main/java/de/mediathekview/mserver/filmlisten/reader/FilmlistOldFormatReader.java b/src/main/java/de/mediathekview/mserver/filmlisten/reader/FilmlistOldFormatReader.java index e9b457b73..778e7b7fe 100644 --- a/src/main/java/de/mediathekview/mserver/filmlisten/reader/FilmlistOldFormatReader.java +++ b/src/main/java/de/mediathekview/mserver/filmlisten/reader/FilmlistOldFormatReader.java @@ -155,8 +155,19 @@ private void headerColumns(JsonReader jsonReader) throws IOException { jsonReader.nextString(); // Url History jsonReader.nextString(); // Geo jsonReader.nextString(); // neu + readOptionalHeaderColumn(jsonReader); // Sprache jsonReader.endArray(); } + + /** + * Liest eine optionale, hinten angehaengte Spalte. Aeltere Filmlisten fuehren sie nicht, deshalb + * wird nur gelesen, wenn ueberhaupt noch ein Wert vor dem Array-Ende steht. + */ + private void readOptionalHeaderColumn(JsonReader jsonReader) throws IOException { + if (jsonReader.peek() == JsonToken.STRING) { + jsonReader.nextString(); + } + } private Optional readRecord(JsonReader jsonReader) throws IOException { cnt++; @@ -212,6 +223,13 @@ private Optional readRecord(JsonReader jsonReader) throws IOException { f.setGeoLocations(readRecord19Geo(jsonReader.nextString())); // f.setNeu(readRecord20Neu(jsonReader.nextString())); + // Hinten angehaengt und optional: Filmlisten aelterer MServer-Staende haben das Feld nicht. + if (jsonReader.peek() == JsonToken.STRING) { + final String sprache = jsonReader.nextString(); + if (!sprache.isEmpty()) { + f.setAudioLanguage(sprache); + } + } // jsonReader.endArray(); // diff --git a/src/main/java/de/mediathekview/mserver/filmlisten/writer/FilmlistOldFormatWriter.java b/src/main/java/de/mediathekview/mserver/filmlisten/writer/FilmlistOldFormatWriter.java index 192d30f7d..82ba29276 100644 --- a/src/main/java/de/mediathekview/mserver/filmlisten/writer/FilmlistOldFormatWriter.java +++ b/src/main/java/de/mediathekview/mserver/filmlisten/writer/FilmlistOldFormatWriter.java @@ -147,6 +147,10 @@ private void writeColumnHeader(JsonWriter jsonWriter) throws IOException { jsonWriter.value("Url History"); jsonWriter.value("Geo"); jsonWriter.value("neu"); + // Neue Felder werden HINTEN angefuegt: der Desktop-Client liest den Datensatz + // positionsbasiert und ueberspringt die Spaltenkopfzeile vollstaendig + // (FilmListReader.skipFieldDescriptions), ein angehaengtes Feld wird dort daher ignoriert. + jsonWriter.value("Sprache"); jsonWriter.endArray(); } @@ -172,9 +176,24 @@ private void writeRecord(AbstractMediaResource film, JsonWriter jsonWriter) t jsonWriter.value(""); // Url History jsonWriter.value(writeRecord19Geo(film)); jsonWriter.value(writeRecord20Neu(film)); + jsonWriter.value(writeRecord21Sprache(film)); jsonWriter.endArray(); } + /** + * Sprache der Tonspur als ISO-639-2/T-Code, leer wenn der Sender sie nicht benennt. + * + *

Bewusst ein einfacher String und kein Array: der Desktop-Client prueft beim Weiterlesen mit + * {@code isExpectedStartArrayToken()}, ob der naechste Token ein Datensatz ist. Ein angehaengtes + * Array wuerde dort als Filmdatensatz interpretiert. + */ + private String writeRecord21Sprache(AbstractMediaResource in) { + if (in instanceof Film film && film.getAudioLanguage() != null) { + return film.getAudioLanguage(); + } + return ""; + } + private String writeRecord01Sender(AbstractMediaResource in, String aSender) { if (!aSender.equalsIgnoreCase(in.getSenderName())) { this.sender = in.getSenderName(); diff --git a/src/test/java/de/mediathekview/mserver/crawler/ard/json/ArdFilmDeserializerAudioLanguageTest.java b/src/test/java/de/mediathekview/mserver/crawler/ard/json/ArdFilmDeserializerAudioLanguageTest.java new file mode 100644 index 000000000..838021b07 --- /dev/null +++ b/src/test/java/de/mediathekview/mserver/crawler/ard/json/ArdFilmDeserializerAudioLanguageTest.java @@ -0,0 +1,105 @@ +package de.mediathekview.mserver.crawler.ard.json; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.google.gson.JsonElement; +import de.mediathekview.mserver.base.config.MServerConfigManager; +import de.mediathekview.mserver.base.messages.listener.MessageListener; +import de.mediathekview.mserver.crawler.ard.ArdCrawler; +import de.mediathekview.mserver.crawler.ard.ArdFilmDto; +import de.mediathekview.mserver.daten.Film; +import de.mediathekview.mserver.progress.listeners.SenderProgressListener; +import de.mediathekview.mserver.testhelper.JsonFileReader; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ForkJoinPool; +import org.junit.jupiter.api.Test; + +/** + * Prueft, dass die Sprache der Originalversion aus den Daten gelesen wird und nicht nur fuer die + * beiden fest verdrahteten Codes "eng" und "fra" ankommt. + */ +class ArdFilmDeserializerAudioLanguageTest { + + @Test + void originalVersionInAnyLanguageKeepsItsLanguageCode() { + // Dieselbe Sendung wie ard_item_OV.json, aber die Originalversion ist als "spa" ausgezeichnet + // statt mit ARDs Platzhalter "ov". Weder die OV- noch die eng-/fra-Abfrage greift hier; der + // Treffer kommt ueber den Platzhalter "*", und die Sprache wird aus audio[0].languageCode + // gelesen. + final Film originalVersion = findOriginalVersion("/ard/ard_item_OV_spanish.json"); + + assertThat(originalVersion, notNullValue()); + assertThat(originalVersion.getAudioLanguage(), equalTo("spa")); + } + + @Test + void originalVersionWithoutNamedLanguageStaysUnset() { + // Gegenprobe mit dem unveraenderten Fixture: dort meldet die ARD nur ihren eigenen Platzhalter + // "ov", der keine Sprache benennt. Dann darf auch nichts gesetzt werden. + final Film originalVersion = findOriginalVersion("/ard/ard_item_OV.json"); + + assertThat(originalVersion, notNullValue()); + assertThat(originalVersion.getAudioLanguage(), nullValue()); + } + + + @Test + void standardVersionMustNotInheritTheOriginalVersionLanguage() { + // Regression: createFilm hat das Feld frueher unbedingt gesetzt und wird fuer beide Filme mit + // demselben ArdVideoInfoDto aufgerufen. Dadurch bekam die deutsche Hauptfassung die Sprache + // der Originalversion - bei einem Beitrag mit deutscher und englischer Tonspur also "eng". + final List alle = allFilms("/ard/ard_item_OV_eng_plus_deu.json"); + assertThat(alle.size(), equalTo(2)); + + final Film standard = + alle.stream().filter(f -> !f.getTitel().contains("(Originalversion)")).findFirst().orElse(null); + final Film originalVersion = + alle.stream().filter(f -> f.getTitel().contains("(Originalversion)")).findFirst().orElse(null); + + assertThat(standard, notNullValue()); + assertThat(originalVersion, notNullValue()); + assertThat(standard.getAudioLanguage(), nullValue()); + assertThat(originalVersion.getAudioLanguage(), equalTo("eng")); + } + + private List allFilms(final String jsonFile) { + final JsonElement jsonElement = JsonFileReader.readJson(jsonFile); + return new ArdFilmDeserializer(createCrawler()).deserialize(jsonElement, null, null).stream() + .map(ArdFilmDto::getFilm) + .toList(); + } + + @Test + void languageCodeWithRegionIsReducedToTheLanguage() { + // Sollte die ARD den Code je mit Regionsangabe liefern, muss trotzdem der reine + // ISO-639-2/T-Code im Feld stehen - "spa-ES" also als "spa". + final Film originalVersion = findOriginalVersion("/ard/ard_item_OV_spanish_region.json"); + + assertThat(originalVersion, notNullValue()); + assertThat(originalVersion.getAudioLanguage(), equalTo("spa")); + } + + private Film findOriginalVersion(final String jsonFile) { + final JsonElement jsonElement = JsonFileReader.readJson(jsonFile); + final List films = + new ArdFilmDeserializer(createCrawler()).deserialize(jsonElement, null, null); + + return films.stream() + .map(ArdFilmDto::getFilm) + .filter(film -> film.getTitel().contains("(Originalversion)")) + .findFirst() + .orElse(null); + } + + private ArdCrawler createCrawler() { + return new ArdCrawler( + new ForkJoinPool(), + new ArrayList(), + new ArrayList(), + new MServerConfigManager("MServer-JUnit-Config.yaml")); + } +} diff --git a/src/test/java/de/mediathekview/mserver/filmlisten/FilmlistOldFormatWriterTest.java b/src/test/java/de/mediathekview/mserver/filmlisten/FilmlistOldFormatWriterTest.java index 66ae9d558..9fb2edccf 100644 --- a/src/test/java/de/mediathekview/mserver/filmlisten/FilmlistOldFormatWriterTest.java +++ b/src/test/java/de/mediathekview/mserver/filmlisten/FilmlistOldFormatWriterTest.java @@ -1,6 +1,7 @@ package de.mediathekview.mserver.filmlisten; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import de.mediathekview.mserver.daten.Film; @@ -30,6 +31,84 @@ class FilmlistOldFormatWriterTest { @TempDir Path tempDir; + @Test + void audioLanguageSurvivesWriteAndRead() throws IOException { + // Kern des Ganzen: die Sprache muss den Weg durch das alte Format ueberstehen. Ohne das Feld + // bleibt von einer englischen Originalfassung nur der Titelzusatz "(Originalversion)" uebrig, + // der die Sprache nicht nennt. + final Filmlist input = new Filmlist(); + final Film withLanguage = buildMinimalFilm("Mit Sprache (Originalversion)"); + withLanguage.setAudioLanguage("eng"); + final Film withoutLanguage = buildMinimalFilm("Ohne Sprache"); + input.add(withLanguage); + input.add(withoutLanguage); + + final Filmlist afterRoundTrip = writeAndRead(input); + + assertEquals(2, afterRoundTrip.getFilms().size()); + final Film readWithLanguage = findByTitle(afterRoundTrip, "Mit Sprache (Originalversion)"); + final Film readWithoutLanguage = findByTitle(afterRoundTrip, "Ohne Sprache"); + assertEquals("eng", readWithLanguage.getAudioLanguage()); + // Kein Sender-Wert darf nicht als leerer String zurueckkommen, sondern gar nicht gesetzt sein. + assertNull(readWithoutLanguage.getAudioLanguage()); + } + + @Test + void filmlistWithoutLanguageColumnStillReads() throws IOException { + // Rueckwaertskompatibilitaet in die andere Richtung: eine Filmliste eines aelteren + // MServer-Standes fuehrt die Spalte nicht. Der Reader muss sie weiterhin lesen koennen. + final Path testFilePath = + new File( + Thread.currentThread() + .getContextClassLoader() + .getResource("mlib/TestFilmlistOldFormatWriter.json") + .getFile()) + .toPath(); + final String withColumn = Files.readString(testFilePath, StandardCharsets.UTF_8); + final String withoutColumn = withColumn.replace(",\"Sprache\"]", "]").replace(",\"\"]", "]"); + + final Path legacyFile = Files.createTempFile(tempDir, "LegacyFilmlist", ".json"); + Files.writeString(legacyFile, withoutColumn, StandardCharsets.UTF_8); + + final Optional read = + new FilmlistOldFormatReader().read(new FileInputStream(legacyFile.toString())); + + assertTrue(read.isPresent()); + assertTrue(read.get().getFilms().size() > 0); + read.get().getFilms().values().forEach(film -> assertNull(film.getAudioLanguage())); + } + + private Film buildMinimalFilm(final String titel) throws java.net.MalformedURLException { + final Film film = + new Film( + UUID.randomUUID(), + Sender.ARD, + titel, + "Testthema", + LocalDateTime.of(2026, 1, 1, 20, 15), + Duration.ofMinutes(90)); + film.addUrl(Resolution.NORMAL, new FilmUrl("https://example.org/v.mp4", 1L)); + film.addGeolocation(GeoLocations.GEO_NONE); + return film; + } + + private Filmlist writeAndRead(final Filmlist input) throws IOException { + final Path tempFile = Files.createTempFile(tempDir, "AudioLanguageRoundTrip", ".json"); + new FilmlistOldFormatWriter().write(input, tempFile); + final Optional read = + new FilmlistOldFormatReader().read(new FileInputStream(tempFile.toString())); + Files.deleteIfExists(tempFile); + assertTrue(read.isPresent()); + return read.get(); + } + + private Film findByTitle(final Filmlist list, final String titel) { + return list.getFilms().values().stream() + .filter(film -> titel.equals(film.getTitel())) + .findFirst() + .orElseThrow(); + } + @Test void readFilmlistOldFormatIncludingBrokenRecords() throws IOException { @@ -43,8 +122,11 @@ void readFilmlistOldFormatIncludingBrokenRecords() assertTrue( Files.exists(tempFile)); // - String actualData = Files.readString(tempFile, StandardCharsets.UTF_16).substring(100); - String expectedData = Files.readString(testFilePath, StandardCharsets.UTF_16).substring(100); + // FilmlistOldFormatWriter schreibt UTF-8 (OutputStreamWriter mit StandardCharsets.UTF_8). + // Das Einlesen als UTF-16 lief bisher nur deshalb durch, weil die Dateilaenge zufaellig + // gerade war; bei ungerader Byteanzahl wirft readString eine MalformedInputException. + String actualData = Files.readString(tempFile, StandardCharsets.UTF_8).substring(100); + String expectedData = Files.readString(testFilePath, StandardCharsets.UTF_8).substring(100); assertEquals(expectedData, actualData, "Filmlisten stimmen überein."); // Files.deleteIfExists(tempFile); diff --git a/src/test/java/de/mediathekview/mserver/filmlisten/NewFilmlistAudioLanguageTest.java b/src/test/java/de/mediathekview/mserver/filmlisten/NewFilmlistAudioLanguageTest.java new file mode 100644 index 000000000..6d0d34bae --- /dev/null +++ b/src/test/java/de/mediathekview/mserver/filmlisten/NewFilmlistAudioLanguageTest.java @@ -0,0 +1,93 @@ +package de.mediathekview.mserver.filmlisten; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import de.mediathekview.mserver.daten.Film; +import de.mediathekview.mserver.daten.FilmUrl; +import de.mediathekview.mserver.daten.Filmlist; +import de.mediathekview.mserver.daten.Resolution; +import de.mediathekview.mserver.daten.Sender; +import de.mediathekview.mserver.filmlisten.reader.FilmlistReader; +import de.mediathekview.mserver.filmlisten.writer.FilmlistWriter; +import java.io.FileInputStream; +import java.io.IOException; +import java.net.MalformedURLException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.Optional; +import java.util.UUID; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** Die Sprache muss auch durch das neue Format und ueber merge() hinweg erhalten bleiben. */ +class NewFilmlistAudioLanguageTest { + + @TempDir Path tempDir; + + @Test + void audioLanguageSurvivesTheNewFormat() throws IOException { + final Filmlist input = new Filmlist(); + final Film withLanguage = buildFilm("Mit Sprache (Originalversion)"); + withLanguage.setAudioLanguage("eng"); + input.add(withLanguage); + input.add(buildFilm("Ohne Sprache")); + + final Path tempFile = Files.createTempFile(tempDir, "NewFormatAudioLanguage", ".json"); + new FilmlistWriter().write(input, tempFile); + final Optional read = + new FilmlistReader().read(new FileInputStream(tempFile.toString())); + Files.deleteIfExists(tempFile); + + assertTrue(read.isPresent()); + assertEquals("eng", findByTitle(read.get(), "Mit Sprache (Originalversion)").getAudioLanguage()); + assertNull(findByTitle(read.get(), "Ohne Sprache").getAudioLanguage()); + } + + @Test + void mergeKeepsAKnownLanguage() throws MalformedURLException { + // Eine importierte aeltere Filmliste kennt das Feld nicht. Wird sie mit einem frischen Crawl + // zusammengefuehrt, darf die dort bekannte Sprache nicht verloren gehen. + final Film ausAelteremStand = buildFilm("Folge (Originalversion)"); + final Film ausFrischemCrawl = buildFilm("Folge (Originalversion)"); + ausFrischemCrawl.setAudioLanguage("fra"); + + ausAelteremStand.merge(ausFrischemCrawl); + + assertEquals("fra", ausAelteremStand.getAudioLanguage()); + } + + @Test + void mergeDoesNotOverwriteAnExistingLanguage() throws MalformedURLException { + final Film bekannt = buildFilm("Folge (Originalversion)"); + bekannt.setAudioLanguage("eng"); + final Film ohne = buildFilm("Folge (Originalversion)"); + + bekannt.merge(ohne); + + assertEquals("eng", bekannt.getAudioLanguage()); + } + + private Film buildFilm(final String titel) throws MalformedURLException { + final Film film = + new Film( + UUID.randomUUID(), + Sender.ARD, + titel, + "Testthema", + LocalDateTime.of(2026, 1, 1, 20, 15), + Duration.ofMinutes(90)); + film.addUrl(Resolution.NORMAL, new FilmUrl("https://example.org/v.mp4", 1L)); + return film; + } + + private Film findByTitle(final Filmlist list, final String titel) { + return list.getFilms().values().stream() + .filter(film -> titel.equals(film.getTitel())) + .findFirst() + .orElseThrow(); + } +} diff --git a/src/test/resources/ard/ard_item_OV_eng_plus_deu.json b/src/test/resources/ard/ard_item_OV_eng_plus_deu.json new file mode 100644 index 000000000..d8b8bb5ee --- /dev/null +++ b/src/test/resources/ard/ard_item_OV_eng_plus_deu.json @@ -0,0 +1,500 @@ +{ + "coreAssetType": "EXTRA_BONUS_CONTENT", + "fskRating": "FSK12", + "id": "player_ondemand-page-ard", + "isChildContent": false, + "personalized": false, + "links": { + "self": { + "id": "player_ondemand-page-ard", + "urlId": "player_ondemand-page-ard", + "title": "Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)", + "href": "https://api.ardmediathek.de/page-gateway/pages/ard/item/Y3JpZDovL3dkci5kZS9CZWl0cmFnLXNvcGhvcmEtY2UwODAxZjMtNjIyMy00ZGUyLTg3YjMtNjE1MjJkYjJkNjVi?embedded=true&mcV6=true", + "type": "application/vnd.ard.page+json", + "partner": "ard" + } + }, + "title": "Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)", + "tracking": { + "aggregationLevelId": 38, + "atiCustomVars": { + "clipTitle": "Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)", + "mediaType": "video", + "lra": "ONE", + "channel": "ONE", + "show": "Murdoch Mysteries", + "contentTypes": "UT", + "mediaDistributionType": 1, + "contentId": 13320735, + "metadataId": "crid://wdr.de/Beitrag-sophora-ce0801f3-6223-4de2-87b3-61522db2d65b", + "clipLength": 2744 + }, + "chapter2": "Player", + "chapter3": "Murdoch Mysteries", + "environmentId": 511893, + "pageTitle": "Mediathek/Player/Murdoch Mysteries/Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)/13320735/20240424_2050", + "szmType": "CP" + }, + "trackingPiano": { + "page_chapter1": "Player", + "page_content_id": "urn:ard:publication:0c1dc4d2e066396c", + "page_content_title": "Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)", + "page_id": "player_ondemand-page-ard", + "page_institution": "ONE", + "page_institution_id": "urn:ard:institution:9edd94d68de90140", + "page_publisher": "ONE", + "page_publisher_id": "urn:ard:publisher:892e3c758ca56a9d", + "page_show": "Murdoch Mysteries", + "page_show_id": "urn:ard:show:85088a146d3740b7", + "page_title": "Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)", + "site": "634408" + }, + "widgets": [ + { + "availableTo": "2024-05-24T20:50:00Z", + "binaryFeatures": [ + "OV", + "UT" + ], + "blockedByFsk": false, + "broadcastedOn": "2024-04-24T20:50:00Z", + "embeddable": false, + "geoblocked": false, + "id": "Y3JpZDovL3dkci5kZS9CZWl0cmFnLXNvcGhvcmEtY2UwODAxZjMtNjIyMy00ZGUyLTg3YjMtNjE1MjJkYjJkNjVi", + "image": { + "alt": "Detective Murdoch (Yannick Bisson) und Constable Crabtree (Jonny Harris)", + "producerName": "WDR", + "src": "https://api.ardmediathek.de/image-service/images/urn:ard:image:2a3352f634c1614c?w={width}&ch=ec0aa7bc7271383a", + "title": "Murdoch Mysteries (S04/E12) - OV" + }, + "isChildContent": false, + "maturityContentRating": "FSK12", + "mediaCollection": { + "embedded": { + "isGeoBlocked": true, + "meta": { + "availableToDateTime": "2024-05-24T20:50:00Z", + "broadcastedOnDateTime": "2024-04-24T20:50:00Z", + "clipSourceName": "ONE", + "durationSeconds": 2744, + "images": [ + { + "alt": "Detective Murdoch (Yannick Bisson) und Constable Crabtree (Jonny Harris)", + "aspectRatio": "16x9", + "imageSourceName": "WDR", + "kind": "preview", + "title": "Murdoch Mysteries (S04/E12) - OV", + "url": "https://api.ardmediathek.de/image-service/images/urn:ard:image:2a3352f634c1614c?w=960&ch=ec0aa7bc7271383a" + } + ], + "maturityContentRating": { + "age": 12, + "isBlocked": false, + "kind": "other" + }, + "publicationService": { + "id": "ard", + "name": "ONE", + "partner": "one" + }, + "synopsis": "Während sich Dr. Ogden auf ihre Hochzeit vorbereitet, muss Murdoch versuchen, den 'Küssenden Banditen' aufzuhalten, einen umstrittenen Bankräuber, der schnell zum Volkshelden aufsteigt.", + "title": "Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)" + }, + "pluginData": { + "recommendation@all": { + "isAutoplay": true, + "timerSeconds": 10, + "url": "https://api.ardmediathek.de/page-gateway/compilations/ard/recommendations?contextItemId=Y3JpZDovL3dkci5kZS9CZWl0cmFnLXNvcGhvcmEtY2UwODAxZjMtNjIyMy00ZGUyLTg3YjMtNjE1MjJkYjJkNjVi" + }, + "trackingAgf@all": { + "appId": "PA02DC09C-B8D3-4098-87D2-2C023682D6D5", + "clipData": { + "assetid": "13320735", + "length": "2744", + "nol_c0": "p0,0", + "nol_c10": "p10,ONE", + "nol_c12": "p12,Content", + "nol_c15": "p15,POC_2002987622", + "nol_c16": "p16,", + "nol_c18": "p18,N", + "nol_c19": "p19,ONE", + "nol_c2": "p2,N", + "nol_c5": "p5,ARD Mediathek", + "nol_c7": "p7,crid://wdr.de/Beitrag-sophora-ce0801f3-6223-4de2-87b3-61522db2d65b", + "nol_c8": "p8,2744", + "nol_c9": "p9,Murdoch Mysteries_Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)_24.04.2024 20:50", + "program": "Murdoch Mysteries", + "title": "ONE_Murdoch Mysteries_Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)_24.04.2024 20:50", + "type": "content" + }, + "tracker": "AGF" + }, + "trackingAti@all": { + "config": { + "site": 511893 + }, + "isEnabled": true, + "richMedia": { + "broadcastMode": "clip", + "duration": "2744", + "mediaLabel": "Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)__13320735__OV | UT", + "mediaLevel2": "{richMediaLevelTwo}", + "mediaTheme1": "ONE", + "mediaTheme2": "ONE", + "mediaTheme3": "Murdoch Mysteries", + "mediaType": "video", + "playerId": "urn:ard:publication:0c1dc4d2e066396c", + "refreshDuration": { + "0": 5, + "1": 12, + "12": 15, + "18": 30 + } + }, + "tracker": "ATI" + }, + "trackingPiano@all": { + "avContent": { + "av_broadcasting_type": "OnDemand", + "av_content": "Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)", + "av_content_duration": 2744000, + "av_content_id": "urn:ard:publication:0c1dc4d2e066396c", + "av_content_type": "video", + "av_institution": "ONE", + "av_institution_id": "urn:ard:institution:9edd94d68de90140", + "av_publisher": "ONE", + "av_publisher_id": "urn:ard:publisher:892e3c758ca56a9d", + "av_show": "Murdoch Mysteries", + "av_show_id": "urn:ard:show:85088a146d3740b7" + }, + "config": { + "events": [ + "av.error", + "av.speed", + "av.quality", + "av.jumpmark", + "av.share", + "av.playermode", + "av.subtitle", + "av.language", + "av.audiodescription", + "av.signlanguage", + "av.volume.mute", + "av.recommendation" + ], + "site": 634408 + }, + "isEnabled": true + } + }, + "streams": [ + { + "kind": "main", + "kindName": "Normal", + "media": [ + { + "aspectRatio": "16:9", + "audios": [ + { + "kind": "standard", + "languageCode": "eng" + } + ], + "forcedLabel": "SD", + "hasEmbeddedSubtitles": false, + "isAdaptiveQualitySelectable": false, + "isHighDynamicRange": false, + "maxHResolutionPx": 640, + "maxVResolutionPx": 360, + "mimeType": "video/mp4", + "subtitles": [], + "url": "https://wdrmedien-a.akamaihd.net/medp/ondemand/de/fsk12/310/3103788/3103788_57016296.mp4", + "videoCodec": "H.264" + }, + { + "aspectRatio": "16:9", + "audios": [ + { + "kind": "standard", + "languageCode": "eng" + } + ], + "forcedLabel": "SD 480p", + "hasEmbeddedSubtitles": false, + "isAdaptiveQualitySelectable": false, + "isHighDynamicRange": false, + "maxHResolutionPx": 960, + "maxVResolutionPx": 540, + "mimeType": "video/mp4", + "subtitles": [], + "url": "https://wdrmedien-a.akamaihd.net/medp/ondemand/de/fsk12/310/3103788/3103788_57016297.mp4", + "videoCodec": "H.264" + }, + { + "aspectRatio": "16:9", + "audios": [ + { + "kind": "standard", + "languageCode": "eng" + } + ], + "forcedLabel": "Full HD", + "hasEmbeddedSubtitles": false, + "isAdaptiveQualitySelectable": false, + "isHighDynamicRange": false, + "maxHResolutionPx": 1920, + "maxVResolutionPx": 1080, + "mimeType": "video/mp4", + "subtitles": [], + "url": "https://wdrmedien-a.akamaihd.net/medp/ondemand/de/fsk12/310/3103788/3103788_57016294.mp4", + "videoCodec": "H.264" + }, + { + "aspectRatio": "16:9", + "audios": [ + { + "kind": "standard", + "languageCode": "eng" + } + ], + "forcedLabel": "Auto", + "hasEmbeddedSubtitles": false, + "isAdaptiveQualitySelectable": false, + "isHighDynamicRange": false, + "maxHResolutionPx": 1920, + "maxVResolutionPx": 1080, + "mimeType": "application/vnd.apple.mpegurl", + "subtitles": [], + "url": "https://wdradaptiv-vh.akamaihd.net/i/medp/ondemand/de/fsk12/310/3103788/,3103788_57016296,3103788_57016297,3103788_57016295,3103788_57016298,3103788_57016294,.mp4.csmil/master.m3u8", + "videoCodec": "H.264" + }, + { + "aspectRatio": "16:9", + "audios": [ + { + "kind": "standard", + "languageCode": "eng" + } + ], + "forcedLabel": "HD Ready", + "hasEmbeddedSubtitles": false, + "isAdaptiveQualitySelectable": false, + "isHighDynamicRange": false, + "maxHResolutionPx": 1280, + "maxVResolutionPx": 720, + "mimeType": "video/mp4", + "subtitles": [], + "url": "https://wdrmedien-a.akamaihd.net/medp/ondemand/de/fsk12/310/3103788/3103788_57016298.mp4", + "videoCodec": "H.264" + }, + { + "aspectRatio": "16:9", + "audios": [ + { + "kind": "standard", + "languageCode": "deu" + } + ], + "forcedLabel": "SD", + "hasEmbeddedSubtitles": false, + "isAdaptiveQualitySelectable": false, + "isHighDynamicRange": false, + "maxHResolutionPx": 640, + "maxVResolutionPx": 360, + "mimeType": "video/mp4", + "subtitles": [], + "url": "https://wdrmedien-a.akamaihd.net/medp/ondemand/de/fsk12/310/3103788/3103788_57016296_deu.mp4", + "videoCodec": "H.264" + }, + { + "aspectRatio": "16:9", + "audios": [ + { + "kind": "standard", + "languageCode": "deu" + } + ], + "forcedLabel": "SD 480p", + "hasEmbeddedSubtitles": false, + "isAdaptiveQualitySelectable": false, + "isHighDynamicRange": false, + "maxHResolutionPx": 960, + "maxVResolutionPx": 540, + "mimeType": "video/mp4", + "subtitles": [], + "url": "https://wdrmedien-a.akamaihd.net/medp/ondemand/de/fsk12/310/3103788/3103788_57016297_deu.mp4", + "videoCodec": "H.264" + }, + { + "aspectRatio": "16:9", + "audios": [ + { + "kind": "standard", + "languageCode": "deu" + } + ], + "forcedLabel": "Full HD", + "hasEmbeddedSubtitles": false, + "isAdaptiveQualitySelectable": false, + "isHighDynamicRange": false, + "maxHResolutionPx": 1920, + "maxVResolutionPx": 1080, + "mimeType": "video/mp4", + "subtitles": [], + "url": "https://wdrmedien-a.akamaihd.net/medp/ondemand/de/fsk12/310/3103788/3103788_57016294_deu.mp4", + "videoCodec": "H.264" + }, + { + "aspectRatio": "16:9", + "audios": [ + { + "kind": "standard", + "languageCode": "deu" + } + ], + "forcedLabel": "Auto", + "hasEmbeddedSubtitles": false, + "isAdaptiveQualitySelectable": false, + "isHighDynamicRange": false, + "maxHResolutionPx": 1920, + "maxVResolutionPx": 1080, + "mimeType": "application/vnd.apple.mpegurl", + "subtitles": [], + "url": "https://wdradaptiv-vh.akamaihd.net/i/medp/ondemand/de/fsk12/310/3103788/,3103788_57016296,3103788_57016297,3103788_57016295,3103788_57016298,3103788_57016294,_deu.mp4.csmil/master.m3u8", + "videoCodec": "H.264" + }, + { + "aspectRatio": "16:9", + "audios": [ + { + "kind": "standard", + "languageCode": "deu" + } + ], + "forcedLabel": "HD Ready", + "hasEmbeddedSubtitles": false, + "isAdaptiveQualitySelectable": false, + "isHighDynamicRange": false, + "maxHResolutionPx": 1280, + "maxVResolutionPx": 720, + "mimeType": "video/mp4", + "subtitles": [], + "url": "https://wdrmedien-a.akamaihd.net/medp/ondemand/de/fsk12/310/3103788/3103788_57016298_deu.mp4", + "videoCodec": "H.264" + } + ], + "videoLanguageCode": "" + } + ], + "subtitles": [ + { + "kind": "normal", + "languageCode": "deu", + "sources": [ + { + "kind": "ebutt", + "url": "https://api.ardmediathek.de/player-service/subtitle/ebutt/urn:ard:subtitle:d0e38dd26e6cc85e" + }, + { + "kind": "webvtt", + "url": "https://api.ardmediathek.de/player-service/subtitle/webvtt/urn:ard:subtitle:d0e38dd26e6cc85e.vtt" + } + ] + } + ] + }, + "href": "https://api.ardmediathek.de/page-gateway/mediacollectionv6/Y3JpZDovL3dkci5kZS9CZWl0cmFnLXNvcGhvcmEtY2UwODAxZjMtNjIyMy00ZGUyLTg3YjMtNjE1MjJkYjJkNjVi?isTv=false" + }, + "pagination": null, + "personalized": false, + "playerConfig": null, + "publicationService": { + "name": "ONE", + "logo": { + "title": "Logo ONE", + "alt": "Logo ONE", + "producerName": "Logo ONE", + "src": "https://api.ardmediathek.de/image-service/image-collections/urn:ard:image-collection:2213972a8d101de1/16x9?w={width}", + "aspectRatio": "16x9" + }, + "publisherType": "TV", + "partner": "one", + "id": "b3JnYW5pemF0aW9uX09ORQ", + "coreId": "urn:ard:publisher:892e3c758ca56a9d" + }, + "links": { + "self": { + "id": "player_on_demand_widget_ard", + "urlId": "player_on_demand_widget_ard", + "title": "Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)", + "href": "https://api.ardmediathek.de/page-gateway/widgets/ard/editorials/player_on_demand_widget_ard/item/Y3JpZDovL3dkci5kZS9CZWl0cmFnLXNvcGhvcmEtY2UwODAxZjMtNjIyMy00ZGUyLTg3YjMtNjE1MjJkYjJkNjVi?pageNumber=0&pageSize=100&embedded=true&mcV6=true", + "type": "application/vnd.ard.widget+json", + "partner": "ard" + } + }, + "show": { + "id": "Y3JpZDovL3dkci5kZTdvbmUvbXVyZG9jaG15c3Rlcmllcw", + "title": "Murdoch Mysteries", + "image": { + "alt": "Sendungslogo \"Murdoch Mysteries\"", + "producerName": "Shaftesbury Murdoch II Inc", + "src": "https://api.ardmediathek.de/image-service/images/urn:ard:image:559937f982bde0f5?w={width}&ch=dc9ee6ab714f2b88", + "title": "Murdoch Mysteries" + }, + "availableSeasons": [ + "1", + "4" + ], + "binaryFeatures": [ + "UT", + "OV" + ], + "coreAssetType": "SEASON_SERIES" + }, + "synopsis": "Während sich Dr. Ogden auf ihre Hochzeit vorbereitet, muss Murdoch versuchen, den 'Küssenden Banditen' aufzuhalten, einen umstrittenen Bankräuber, der schnell zum Volkshelden aufsteigt.", + "title": "Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)", + "trackingPiano": { + "teaser_recommended": false, + "widget_id": "player_on_demand_widget_ard", + "widget_title": "Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)", + "widget_typ": "player_ondemand" + }, + "type": "player_ondemand" + }, + { + "aZContent": false, + "compilationType": "itemsOfShow", + "id": "Y3JpZDovL3dkci5kZTdvbmUvbXVyZG9jaG15c3Rlcmllcw", + "isChildContent": false, + "pagination": { + "pageNumber": 0, + "pageSize": 24, + "totalElements": 7 + }, + "personalized": false, + "links": { + "self": { + "id": "Y3JpZDovL3dkci5kZTdvbmUvbXVyZG9jaG15c3Rlcmllcw", + "urlId": "Y3JpZDovL3dkci5kZTdvbmUvbXVyZG9jaG15c3Rlcmllcw", + "title": "Murdoch Mysteries", + "href": "https://api.ardmediathek.de/page-gateway/widgets/ard/asset/Y3JpZDovL3dkci5kZTdvbmUvbXVyZG9jaG15c3Rlcmllcw?excludedAssetIds=Y3JpZDovL3dkci5kZS9CZWl0cmFnLXNvcGhvcmEtY2UwODAxZjMtNjIyMy00ZGUyLTg3YjMtNjE1MjJkYjJkNjVi&pageNumber=0&pageSize=24&embedded=true&seasoned=false&seasonNumber=&withAudiodescription=false&withOriginalWithSubtitle=false&withOriginalversion=false&single=false", + "type": "application/vnd.ard.widget+json", + "partner": "ard" + } + }, + "size": "m", + "swipeable": true, + "teasers": [], + "title": "Murdoch Mysteries", + "titleVisible": true, + "trackingPiano": { + "teaser_recommended": false, + "widget_id": "Y3JpZDovL3dkci5kZTdvbmUvbXVyZG9jaG15c3Rlcmllcw", + "widget_title": "Murdoch Mysteries", + "widget_typ": "gridlist" + }, + "type": "gridlist", + "userVisibility": "ALWAYS" + } + ] +} \ No newline at end of file diff --git a/src/test/resources/ard/ard_item_OV_spanish.json b/src/test/resources/ard/ard_item_OV_spanish.json new file mode 100644 index 000000000..50ccda464 --- /dev/null +++ b/src/test/resources/ard/ard_item_OV_spanish.json @@ -0,0 +1,405 @@ +{ + "coreAssetType": "EXTRA_BONUS_CONTENT", + "fskRating": "FSK12", + "id": "player_ondemand-page-ard", + "isChildContent": false, + "personalized": false, + "links": { + "self": { + "id": "player_ondemand-page-ard", + "urlId": "player_ondemand-page-ard", + "title": "Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)", + "href": "https://api.ardmediathek.de/page-gateway/pages/ard/item/Y3JpZDovL3dkci5kZS9CZWl0cmFnLXNvcGhvcmEtY2UwODAxZjMtNjIyMy00ZGUyLTg3YjMtNjE1MjJkYjJkNjVi?embedded=true&mcV6=true", + "type": "application/vnd.ard.page+json", + "partner": "ard" + } + }, + "title": "Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)", + "tracking": { + "aggregationLevelId": 38, + "atiCustomVars": { + "clipTitle": "Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)", + "mediaType": "video", + "lra": "ONE", + "channel": "ONE", + "show": "Murdoch Mysteries", + "contentTypes": "UT", + "mediaDistributionType": 1, + "contentId": 13320735, + "metadataId": "crid://wdr.de/Beitrag-sophora-ce0801f3-6223-4de2-87b3-61522db2d65b", + "clipLength": 2744 + }, + "chapter2": "Player", + "chapter3": "Murdoch Mysteries", + "environmentId": 511893, + "pageTitle": "Mediathek/Player/Murdoch Mysteries/Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)/13320735/20240424_2050", + "szmType": "CP" + }, + "trackingPiano": { + "page_chapter1": "Player", + "page_content_id": "urn:ard:publication:0c1dc4d2e066396c", + "page_content_title": "Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)", + "page_id": "player_ondemand-page-ard", + "page_institution": "ONE", + "page_institution_id": "urn:ard:institution:9edd94d68de90140", + "page_publisher": "ONE", + "page_publisher_id": "urn:ard:publisher:892e3c758ca56a9d", + "page_show": "Murdoch Mysteries", + "page_show_id": "urn:ard:show:85088a146d3740b7", + "page_title": "Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)", + "site": "634408" + }, + "widgets": [ + { + "availableTo": "2024-05-24T20:50:00Z", + "binaryFeatures": [ + "OV", + "UT" + ], + "blockedByFsk": false, + "broadcastedOn": "2024-04-24T20:50:00Z", + "embeddable": false, + "geoblocked": false, + "id": "Y3JpZDovL3dkci5kZS9CZWl0cmFnLXNvcGhvcmEtY2UwODAxZjMtNjIyMy00ZGUyLTg3YjMtNjE1MjJkYjJkNjVi", + "image": { + "alt": "Detective Murdoch (Yannick Bisson) und Constable Crabtree (Jonny Harris)", + "producerName": "WDR", + "src": "https://api.ardmediathek.de/image-service/images/urn:ard:image:2a3352f634c1614c?w={width}&ch=ec0aa7bc7271383a", + "title": "Murdoch Mysteries (S04/E12) - OV" + }, + "isChildContent": false, + "maturityContentRating": "FSK12", + "mediaCollection": { + "embedded": { + "isGeoBlocked": true, + "meta": { + "availableToDateTime": "2024-05-24T20:50:00Z", + "broadcastedOnDateTime": "2024-04-24T20:50:00Z", + "clipSourceName": "ONE", + "durationSeconds": 2744, + "images": [ + { + "alt": "Detective Murdoch (Yannick Bisson) und Constable Crabtree (Jonny Harris)", + "aspectRatio": "16x9", + "imageSourceName": "WDR", + "kind": "preview", + "title": "Murdoch Mysteries (S04/E12) - OV", + "url": "https://api.ardmediathek.de/image-service/images/urn:ard:image:2a3352f634c1614c?w=960&ch=ec0aa7bc7271383a" + } + ], + "maturityContentRating": { + "age": 12, + "isBlocked": false, + "kind": "other" + }, + "publicationService": { + "id": "ard", + "name": "ONE", + "partner": "one" + }, + "synopsis": "Während sich Dr. Ogden auf ihre Hochzeit vorbereitet, muss Murdoch versuchen, den 'Küssenden Banditen' aufzuhalten, einen umstrittenen Bankräuber, der schnell zum Volkshelden aufsteigt.", + "title": "Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)" + }, + "pluginData": { + "recommendation@all": { + "isAutoplay": true, + "timerSeconds": 10, + "url": "https://api.ardmediathek.de/page-gateway/compilations/ard/recommendations?contextItemId=Y3JpZDovL3dkci5kZS9CZWl0cmFnLXNvcGhvcmEtY2UwODAxZjMtNjIyMy00ZGUyLTg3YjMtNjE1MjJkYjJkNjVi" + }, + "trackingAgf@all": { + "appId": "PA02DC09C-B8D3-4098-87D2-2C023682D6D5", + "clipData": { + "assetid": "13320735", + "length": "2744", + "nol_c0": "p0,0", + "nol_c10": "p10,ONE", + "nol_c12": "p12,Content", + "nol_c15": "p15,POC_2002987622", + "nol_c16": "p16,", + "nol_c18": "p18,N", + "nol_c19": "p19,ONE", + "nol_c2": "p2,N", + "nol_c5": "p5,ARD Mediathek", + "nol_c7": "p7,crid://wdr.de/Beitrag-sophora-ce0801f3-6223-4de2-87b3-61522db2d65b", + "nol_c8": "p8,2744", + "nol_c9": "p9,Murdoch Mysteries_Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)_24.04.2024 20:50", + "program": "Murdoch Mysteries", + "title": "ONE_Murdoch Mysteries_Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)_24.04.2024 20:50", + "type": "content" + }, + "tracker": "AGF" + }, + "trackingAti@all": { + "config": { + "site": 511893 + }, + "isEnabled": true, + "richMedia": { + "broadcastMode": "clip", + "duration": "2744", + "mediaLabel": "Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)__13320735__OV | UT", + "mediaLevel2": "{richMediaLevelTwo}", + "mediaTheme1": "ONE", + "mediaTheme2": "ONE", + "mediaTheme3": "Murdoch Mysteries", + "mediaType": "video", + "playerId": "urn:ard:publication:0c1dc4d2e066396c", + "refreshDuration": { + "0": 5, + "1": 12, + "12": 15, + "18": 30 + } + }, + "tracker": "ATI" + }, + "trackingPiano@all": { + "avContent": { + "av_broadcasting_type": "OnDemand", + "av_content": "Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)", + "av_content_duration": 2744000, + "av_content_id": "urn:ard:publication:0c1dc4d2e066396c", + "av_content_type": "video", + "av_institution": "ONE", + "av_institution_id": "urn:ard:institution:9edd94d68de90140", + "av_publisher": "ONE", + "av_publisher_id": "urn:ard:publisher:892e3c758ca56a9d", + "av_show": "Murdoch Mysteries", + "av_show_id": "urn:ard:show:85088a146d3740b7" + }, + "config": { + "events": [ + "av.error", + "av.speed", + "av.quality", + "av.jumpmark", + "av.share", + "av.playermode", + "av.subtitle", + "av.language", + "av.audiodescription", + "av.signlanguage", + "av.volume.mute", + "av.recommendation" + ], + "site": 634408 + }, + "isEnabled": true + } + }, + "streams": [ + { + "kind": "main", + "kindName": "Normal", + "media": [ + { + "aspectRatio": "16:9", + "audios": [ + { + "kind": "standard", + "languageCode": "spa" + } + ], + "forcedLabel": "SD", + "hasEmbeddedSubtitles": false, + "isAdaptiveQualitySelectable": false, + "isHighDynamicRange": false, + "maxHResolutionPx": 640, + "maxVResolutionPx": 360, + "mimeType": "video/mp4", + "subtitles": [], + "url": "https://wdrmedien-a.akamaihd.net/medp/ondemand/de/fsk12/310/3103788/3103788_57016296.mp4", + "videoCodec": "H.264" + }, + { + "aspectRatio": "16:9", + "audios": [ + { + "kind": "standard", + "languageCode": "spa" + } + ], + "forcedLabel": "SD 480p", + "hasEmbeddedSubtitles": false, + "isAdaptiveQualitySelectable": false, + "isHighDynamicRange": false, + "maxHResolutionPx": 960, + "maxVResolutionPx": 540, + "mimeType": "video/mp4", + "subtitles": [], + "url": "https://wdrmedien-a.akamaihd.net/medp/ondemand/de/fsk12/310/3103788/3103788_57016297.mp4", + "videoCodec": "H.264" + }, + { + "aspectRatio": "16:9", + "audios": [ + { + "kind": "standard", + "languageCode": "spa" + } + ], + "forcedLabel": "Full HD", + "hasEmbeddedSubtitles": false, + "isAdaptiveQualitySelectable": false, + "isHighDynamicRange": false, + "maxHResolutionPx": 1920, + "maxVResolutionPx": 1080, + "mimeType": "video/mp4", + "subtitles": [], + "url": "https://wdrmedien-a.akamaihd.net/medp/ondemand/de/fsk12/310/3103788/3103788_57016294.mp4", + "videoCodec": "H.264" + }, + { + "aspectRatio": "16:9", + "audios": [ + { + "kind": "standard", + "languageCode": "spa" + } + ], + "forcedLabel": "Auto", + "hasEmbeddedSubtitles": false, + "isAdaptiveQualitySelectable": false, + "isHighDynamicRange": false, + "maxHResolutionPx": 1920, + "maxVResolutionPx": 1080, + "mimeType": "application/vnd.apple.mpegurl", + "subtitles": [], + "url": "https://wdradaptiv-vh.akamaihd.net/i/medp/ondemand/de/fsk12/310/3103788/,3103788_57016296,3103788_57016297,3103788_57016295,3103788_57016298,3103788_57016294,.mp4.csmil/master.m3u8", + "videoCodec": "H.264" + }, + { + "aspectRatio": "16:9", + "audios": [ + { + "kind": "standard", + "languageCode": "spa" + } + ], + "forcedLabel": "HD Ready", + "hasEmbeddedSubtitles": false, + "isAdaptiveQualitySelectable": false, + "isHighDynamicRange": false, + "maxHResolutionPx": 1280, + "maxVResolutionPx": 720, + "mimeType": "video/mp4", + "subtitles": [], + "url": "https://wdrmedien-a.akamaihd.net/medp/ondemand/de/fsk12/310/3103788/3103788_57016298.mp4", + "videoCodec": "H.264" + } + ], + "videoLanguageCode": "" + } + ], + "subtitles": [ + { + "kind": "normal", + "languageCode": "deu", + "sources": [ + { + "kind": "ebutt", + "url": "https://api.ardmediathek.de/player-service/subtitle/ebutt/urn:ard:subtitle:d0e38dd26e6cc85e" + }, + { + "kind": "webvtt", + "url": "https://api.ardmediathek.de/player-service/subtitle/webvtt/urn:ard:subtitle:d0e38dd26e6cc85e.vtt" + } + ] + } + ] + }, + "href": "https://api.ardmediathek.de/page-gateway/mediacollectionv6/Y3JpZDovL3dkci5kZS9CZWl0cmFnLXNvcGhvcmEtY2UwODAxZjMtNjIyMy00ZGUyLTg3YjMtNjE1MjJkYjJkNjVi?isTv=false" + }, + "pagination": null, + "personalized": false, + "playerConfig": null, + "publicationService": { + "name": "ONE", + "logo": { + "title": "Logo ONE", + "alt": "Logo ONE", + "producerName": "Logo ONE", + "src": "https://api.ardmediathek.de/image-service/image-collections/urn:ard:image-collection:2213972a8d101de1/16x9?w={width}", + "aspectRatio": "16x9" + }, + "publisherType": "TV", + "partner": "one", + "id": "b3JnYW5pemF0aW9uX09ORQ", + "coreId": "urn:ard:publisher:892e3c758ca56a9d" + }, + "links": { + "self": { + "id": "player_on_demand_widget_ard", + "urlId": "player_on_demand_widget_ard", + "title": "Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)", + "href": "https://api.ardmediathek.de/page-gateway/widgets/ard/editorials/player_on_demand_widget_ard/item/Y3JpZDovL3dkci5kZS9CZWl0cmFnLXNvcGhvcmEtY2UwODAxZjMtNjIyMy00ZGUyLTg3YjMtNjE1MjJkYjJkNjVi?pageNumber=0&pageSize=100&embedded=true&mcV6=true", + "type": "application/vnd.ard.widget+json", + "partner": "ard" + } + }, + "show": { + "id": "Y3JpZDovL3dkci5kZTdvbmUvbXVyZG9jaG15c3Rlcmllcw", + "title": "Murdoch Mysteries", + "image": { + "alt": "Sendungslogo \"Murdoch Mysteries\"", + "producerName": "Shaftesbury Murdoch II Inc", + "src": "https://api.ardmediathek.de/image-service/images/urn:ard:image:559937f982bde0f5?w={width}&ch=dc9ee6ab714f2b88", + "title": "Murdoch Mysteries" + }, + "availableSeasons": [ + "1", + "4" + ], + "binaryFeatures": [ + "UT", + "OV" + ], + "coreAssetType": "SEASON_SERIES" + }, + "synopsis": "Während sich Dr. Ogden auf ihre Hochzeit vorbereitet, muss Murdoch versuchen, den 'Küssenden Banditen' aufzuhalten, einen umstrittenen Bankräuber, der schnell zum Volkshelden aufsteigt.", + "title": "Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)", + "trackingPiano": { + "teaser_recommended": false, + "widget_id": "player_on_demand_widget_ard", + "widget_title": "Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)", + "widget_typ": "player_ondemand" + }, + "type": "player_ondemand" + }, + { + "aZContent": false, + "compilationType": "itemsOfShow", + "id": "Y3JpZDovL3dkci5kZTdvbmUvbXVyZG9jaG15c3Rlcmllcw", + "isChildContent": false, + "pagination": { + "pageNumber": 0, + "pageSize": 24, + "totalElements": 7 + }, + "personalized": false, + "links": { + "self": { + "id": "Y3JpZDovL3dkci5kZTdvbmUvbXVyZG9jaG15c3Rlcmllcw", + "urlId": "Y3JpZDovL3dkci5kZTdvbmUvbXVyZG9jaG15c3Rlcmllcw", + "title": "Murdoch Mysteries", + "href": "https://api.ardmediathek.de/page-gateway/widgets/ard/asset/Y3JpZDovL3dkci5kZTdvbmUvbXVyZG9jaG15c3Rlcmllcw?excludedAssetIds=Y3JpZDovL3dkci5kZS9CZWl0cmFnLXNvcGhvcmEtY2UwODAxZjMtNjIyMy00ZGUyLTg3YjMtNjE1MjJkYjJkNjVi&pageNumber=0&pageSize=24&embedded=true&seasoned=false&seasonNumber=&withAudiodescription=false&withOriginalWithSubtitle=false&withOriginalversion=false&single=false", + "type": "application/vnd.ard.widget+json", + "partner": "ard" + } + }, + "size": "m", + "swipeable": true, + "teasers": [], + "title": "Murdoch Mysteries", + "titleVisible": true, + "trackingPiano": { + "teaser_recommended": false, + "widget_id": "Y3JpZDovL3dkci5kZTdvbmUvbXVyZG9jaG15c3Rlcmllcw", + "widget_title": "Murdoch Mysteries", + "widget_typ": "gridlist" + }, + "type": "gridlist", + "userVisibility": "ALWAYS" + } + ] +} \ No newline at end of file diff --git a/src/test/resources/ard/ard_item_OV_spanish_region.json b/src/test/resources/ard/ard_item_OV_spanish_region.json new file mode 100644 index 000000000..ace2817fd --- /dev/null +++ b/src/test/resources/ard/ard_item_OV_spanish_region.json @@ -0,0 +1 @@ +{"coreAssetType": "EXTRA_BONUS_CONTENT", "fskRating": "FSK12", "id": "player_ondemand-page-ard", "isChildContent": false, "personalized": false, "links": {"self": {"id": "player_ondemand-page-ard", "urlId": "player_ondemand-page-ard", "title": "Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)", "href": "https://api.ardmediathek.de/page-gateway/pages/ard/item/Y3JpZDovL3dkci5kZS9CZWl0cmFnLXNvcGhvcmEtY2UwODAxZjMtNjIyMy00ZGUyLTg3YjMtNjE1MjJkYjJkNjVi?embedded=true&mcV6=true", "type": "application/vnd.ard.page+json", "partner": "ard"}}, "title": "Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)", "tracking": {"aggregationLevelId": 38, "atiCustomVars": {"clipTitle": "Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)", "mediaType": "video", "lra": "ONE", "channel": "ONE", "show": "Murdoch Mysteries", "contentTypes": "UT", "mediaDistributionType": 1, "contentId": 13320735, "metadataId": "crid://wdr.de/Beitrag-sophora-ce0801f3-6223-4de2-87b3-61522db2d65b", "clipLength": 2744}, "chapter2": "Player", "chapter3": "Murdoch Mysteries", "environmentId": 511893, "pageTitle": "Mediathek/Player/Murdoch Mysteries/Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)/13320735/20240424_2050", "szmType": "CP"}, "trackingPiano": {"page_chapter1": "Player", "page_content_id": "urn:ard:publication:0c1dc4d2e066396c", "page_content_title": "Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)", "page_id": "player_ondemand-page-ard", "page_institution": "ONE", "page_institution_id": "urn:ard:institution:9edd94d68de90140", "page_publisher": "ONE", "page_publisher_id": "urn:ard:publisher:892e3c758ca56a9d", "page_show": "Murdoch Mysteries", "page_show_id": "urn:ard:show:85088a146d3740b7", "page_title": "Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)", "site": "634408"}, "widgets": [{"availableTo": "2024-05-24T20:50:00Z", "binaryFeatures": ["OV", "UT"], "blockedByFsk": false, "broadcastedOn": "2024-04-24T20:50:00Z", "embeddable": false, "geoblocked": false, "id": "Y3JpZDovL3dkci5kZS9CZWl0cmFnLXNvcGhvcmEtY2UwODAxZjMtNjIyMy00ZGUyLTg3YjMtNjE1MjJkYjJkNjVi", "image": {"alt": "Detective Murdoch (Yannick Bisson) und Constable Crabtree (Jonny Harris)", "producerName": "WDR", "src": "https://api.ardmediathek.de/image-service/images/urn:ard:image:2a3352f634c1614c?w={width}&ch=ec0aa7bc7271383a", "title": "Murdoch Mysteries (S04/E12) - OV"}, "isChildContent": false, "maturityContentRating": "FSK12", "mediaCollection": {"embedded": {"isGeoBlocked": true, "meta": {"availableToDateTime": "2024-05-24T20:50:00Z", "broadcastedOnDateTime": "2024-04-24T20:50:00Z", "clipSourceName": "ONE", "durationSeconds": 2744, "images": [{"alt": "Detective Murdoch (Yannick Bisson) und Constable Crabtree (Jonny Harris)", "aspectRatio": "16x9", "imageSourceName": "WDR", "kind": "preview", "title": "Murdoch Mysteries (S04/E12) - OV", "url": "https://api.ardmediathek.de/image-service/images/urn:ard:image:2a3352f634c1614c?w=960&ch=ec0aa7bc7271383a"}], "maturityContentRating": {"age": 12, "isBlocked": false, "kind": "other"}, "publicationService": {"id": "ard", "name": "ONE", "partner": "one"}, "synopsis": "Während sich Dr. Ogden auf ihre Hochzeit vorbereitet, muss Murdoch versuchen, den 'Küssenden Banditen' aufzuhalten, einen umstrittenen Bankräuber, der schnell zum Volkshelden aufsteigt.", "title": "Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)"}, "pluginData": {"recommendation@all": {"isAutoplay": true, "timerSeconds": 10, "url": "https://api.ardmediathek.de/page-gateway/compilations/ard/recommendations?contextItemId=Y3JpZDovL3dkci5kZS9CZWl0cmFnLXNvcGhvcmEtY2UwODAxZjMtNjIyMy00ZGUyLTg3YjMtNjE1MjJkYjJkNjVi"}, "trackingAgf@all": {"appId": "PA02DC09C-B8D3-4098-87D2-2C023682D6D5", "clipData": {"assetid": "13320735", "length": "2744", "nol_c0": "p0,0", "nol_c10": "p10,ONE", "nol_c12": "p12,Content", "nol_c15": "p15,POC_2002987622", "nol_c16": "p16,", "nol_c18": "p18,N", "nol_c19": "p19,ONE", "nol_c2": "p2,N", "nol_c5": "p5,ARD Mediathek", "nol_c7": "p7,crid://wdr.de/Beitrag-sophora-ce0801f3-6223-4de2-87b3-61522db2d65b", "nol_c8": "p8,2744", "nol_c9": "p9,Murdoch Mysteries_Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)_24.04.2024 20:50", "program": "Murdoch Mysteries", "title": "ONE_Murdoch Mysteries_Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)_24.04.2024 20:50", "type": "content"}, "tracker": "AGF"}, "trackingAti@all": {"config": {"site": 511893}, "isEnabled": true, "richMedia": {"broadcastMode": "clip", "duration": "2744", "mediaLabel": "Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)__13320735__OV | UT", "mediaLevel2": "{richMediaLevelTwo}", "mediaTheme1": "ONE", "mediaTheme2": "ONE", "mediaTheme3": "Murdoch Mysteries", "mediaType": "video", "playerId": "urn:ard:publication:0c1dc4d2e066396c", "refreshDuration": {"0": 5, "1": 12, "12": 15, "18": 30}}, "tracker": "ATI"}, "trackingPiano@all": {"avContent": {"av_broadcasting_type": "OnDemand", "av_content": "Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)", "av_content_duration": 2744000, "av_content_id": "urn:ard:publication:0c1dc4d2e066396c", "av_content_type": "video", "av_institution": "ONE", "av_institution_id": "urn:ard:institution:9edd94d68de90140", "av_publisher": "ONE", "av_publisher_id": "urn:ard:publisher:892e3c758ca56a9d", "av_show": "Murdoch Mysteries", "av_show_id": "urn:ard:show:85088a146d3740b7"}, "config": {"events": ["av.error", "av.speed", "av.quality", "av.jumpmark", "av.share", "av.playermode", "av.subtitle", "av.language", "av.audiodescription", "av.signlanguage", "av.volume.mute", "av.recommendation"], "site": 634408}, "isEnabled": true}}, "streams": [{"kind": "main", "kindName": "Normal", "media": [{"aspectRatio": "16:9", "audios": [{"kind": "standard", "languageCode": "spa-ES"}], "forcedLabel": "SD", "hasEmbeddedSubtitles": false, "isAdaptiveQualitySelectable": false, "isHighDynamicRange": false, "maxHResolutionPx": 640, "maxVResolutionPx": 360, "mimeType": "video/mp4", "subtitles": [], "url": "https://wdrmedien-a.akamaihd.net/medp/ondemand/de/fsk12/310/3103788/3103788_57016296.mp4", "videoCodec": "H.264"}, {"aspectRatio": "16:9", "audios": [{"kind": "standard", "languageCode": "spa-ES"}], "forcedLabel": "SD 480p", "hasEmbeddedSubtitles": false, "isAdaptiveQualitySelectable": false, "isHighDynamicRange": false, "maxHResolutionPx": 960, "maxVResolutionPx": 540, "mimeType": "video/mp4", "subtitles": [], "url": "https://wdrmedien-a.akamaihd.net/medp/ondemand/de/fsk12/310/3103788/3103788_57016297.mp4", "videoCodec": "H.264"}, {"aspectRatio": "16:9", "audios": [{"kind": "standard", "languageCode": "spa-ES"}], "forcedLabel": "Full HD", "hasEmbeddedSubtitles": false, "isAdaptiveQualitySelectable": false, "isHighDynamicRange": false, "maxHResolutionPx": 1920, "maxVResolutionPx": 1080, "mimeType": "video/mp4", "subtitles": [], "url": "https://wdrmedien-a.akamaihd.net/medp/ondemand/de/fsk12/310/3103788/3103788_57016294.mp4", "videoCodec": "H.264"}, {"aspectRatio": "16:9", "audios": [{"kind": "standard", "languageCode": "spa-ES"}], "forcedLabel": "Auto", "hasEmbeddedSubtitles": false, "isAdaptiveQualitySelectable": false, "isHighDynamicRange": false, "maxHResolutionPx": 1920, "maxVResolutionPx": 1080, "mimeType": "application/vnd.apple.mpegurl", "subtitles": [], "url": "https://wdradaptiv-vh.akamaihd.net/i/medp/ondemand/de/fsk12/310/3103788/,3103788_57016296,3103788_57016297,3103788_57016295,3103788_57016298,3103788_57016294,.mp4.csmil/master.m3u8", "videoCodec": "H.264"}, {"aspectRatio": "16:9", "audios": [{"kind": "standard", "languageCode": "spa-ES"}], "forcedLabel": "HD Ready", "hasEmbeddedSubtitles": false, "isAdaptiveQualitySelectable": false, "isHighDynamicRange": false, "maxHResolutionPx": 1280, "maxVResolutionPx": 720, "mimeType": "video/mp4", "subtitles": [], "url": "https://wdrmedien-a.akamaihd.net/medp/ondemand/de/fsk12/310/3103788/3103788_57016298.mp4", "videoCodec": "H.264"}], "videoLanguageCode": ""}], "subtitles": [{"kind": "normal", "languageCode": "deu", "sources": [{"kind": "ebutt", "url": "https://api.ardmediathek.de/player-service/subtitle/ebutt/urn:ard:subtitle:d0e38dd26e6cc85e"}, {"kind": "webvtt", "url": "https://api.ardmediathek.de/player-service/subtitle/webvtt/urn:ard:subtitle:d0e38dd26e6cc85e.vtt"}]}]}, "href": "https://api.ardmediathek.de/page-gateway/mediacollectionv6/Y3JpZDovL3dkci5kZS9CZWl0cmFnLXNvcGhvcmEtY2UwODAxZjMtNjIyMy00ZGUyLTg3YjMtNjE1MjJkYjJkNjVi?isTv=false"}, "pagination": null, "personalized": false, "playerConfig": null, "publicationService": {"name": "ONE", "logo": {"title": "Logo ONE", "alt": "Logo ONE", "producerName": "Logo ONE", "src": "https://api.ardmediathek.de/image-service/image-collections/urn:ard:image-collection:2213972a8d101de1/16x9?w={width}", "aspectRatio": "16x9"}, "publisherType": "TV", "partner": "one", "id": "b3JnYW5pemF0aW9uX09ORQ", "coreId": "urn:ard:publisher:892e3c758ca56a9d"}, "links": {"self": {"id": "player_on_demand_widget_ard", "urlId": "player_on_demand_widget_ard", "title": "Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)", "href": "https://api.ardmediathek.de/page-gateway/widgets/ard/editorials/player_on_demand_widget_ard/item/Y3JpZDovL3dkci5kZS9CZWl0cmFnLXNvcGhvcmEtY2UwODAxZjMtNjIyMy00ZGUyLTg3YjMtNjE1MjJkYjJkNjVi?pageNumber=0&pageSize=100&embedded=true&mcV6=true", "type": "application/vnd.ard.widget+json", "partner": "ard"}}, "show": {"id": "Y3JpZDovL3dkci5kZTdvbmUvbXVyZG9jaG15c3Rlcmllcw", "title": "Murdoch Mysteries", "image": {"alt": "Sendungslogo \"Murdoch Mysteries\"", "producerName": "Shaftesbury Murdoch II Inc", "src": "https://api.ardmediathek.de/image-service/images/urn:ard:image:559937f982bde0f5?w={width}&ch=dc9ee6ab714f2b88", "title": "Murdoch Mysteries"}, "availableSeasons": ["1", "4"], "binaryFeatures": ["UT", "OV"], "coreAssetType": "SEASON_SERIES"}, "synopsis": "Während sich Dr. Ogden auf ihre Hochzeit vorbereitet, muss Murdoch versuchen, den 'Küssenden Banditen' aufzuhalten, einen umstrittenen Bankräuber, der schnell zum Volkshelden aufsteigt.", "title": "Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)", "trackingPiano": {"teaser_recommended": false, "widget_id": "player_on_demand_widget_ard", "widget_title": "Folge 12: Der küssende Bandit (S04/E12) - (Originalversion)", "widget_typ": "player_ondemand"}, "type": "player_ondemand"}, {"aZContent": false, "compilationType": "itemsOfShow", "id": "Y3JpZDovL3dkci5kZTdvbmUvbXVyZG9jaG15c3Rlcmllcw", "isChildContent": false, "pagination": {"pageNumber": 0, "pageSize": 24, "totalElements": 7}, "personalized": false, "links": {"self": {"id": "Y3JpZDovL3dkci5kZTdvbmUvbXVyZG9jaG15c3Rlcmllcw", "urlId": "Y3JpZDovL3dkci5kZTdvbmUvbXVyZG9jaG15c3Rlcmllcw", "title": "Murdoch Mysteries", "href": "https://api.ardmediathek.de/page-gateway/widgets/ard/asset/Y3JpZDovL3dkci5kZTdvbmUvbXVyZG9jaG15c3Rlcmllcw?excludedAssetIds=Y3JpZDovL3dkci5kZS9CZWl0cmFnLXNvcGhvcmEtY2UwODAxZjMtNjIyMy00ZGUyLTg3YjMtNjE1MjJkYjJkNjVi&pageNumber=0&pageSize=24&embedded=true&seasoned=false&seasonNumber=&withAudiodescription=false&withOriginalWithSubtitle=false&withOriginalversion=false&single=false", "type": "application/vnd.ard.widget+json", "partner": "ard"}}, "size": "m", "swipeable": true, "teasers": [], "title": "Murdoch Mysteries", "titleVisible": true, "trackingPiano": {"teaser_recommended": false, "widget_id": "Y3JpZDovL3dkci5kZTdvbmUvbXVyZG9jaG15c3Rlcmllcw", "widget_title": "Murdoch Mysteries", "widget_typ": "gridlist"}, "type": "gridlist", "userVisibility": "ALWAYS"}]} \ No newline at end of file diff --git a/src/test/resources/mlib/TestFilmlistOldFormatWriter.json b/src/test/resources/mlib/TestFilmlistOldFormatWriter.json index f9e3a0e37..7dc5178e2 100644 --- a/src/test/resources/mlib/TestFilmlistOldFormatWriter.json +++ b/src/test/resources/mlib/TestFilmlistOldFormatWriter.json @@ -1 +1 @@ -{"Filmliste":["30.05.2023, 19:21","30.05.2023, 17:21","4","MSearch [Vers.: 4.0.1]","7bc3d924-974f-42c4-8994-9d98dde17862"],"Filmliste":["Sender","Thema","Titel","Datum","Zeit","Dauer","Größe [MB]","Beschreibung","Url","Website","Url Untertitel","Url RTMP","Url Klein","Url RTMP Klein","Url HD","Url RTMP HD","DatumL","Url History","Geo","neu"],"X":["3Sat","37 Grad","37° Totgerast (Audiodeskription)","30.05.2023","22:15:00","00:28:45","405","Wer plötzlich Angehörige bei einem Unfall verliert, wacht in einem anderen Leben auf. Antonia verlor ihre Eltern und ihre Schwester, Steffen seine Frau und seinen Sohn.","https://rodlzdf-a.akamaihd.net/none/zdf/23/05/230530_2215_sendung_37g/2/230530_2215_sendung_37g_a3a4_2360k_p35v17.mp4","https://www.3sat.de/gesellschaft/37-grad/37-totgerast-102.html","","","101|808k_p11v17.mp4","","8|nrodlzdf-a.akamaihd.net/none/zdf/23/05/230530_2215_sendung_37g/2/230530_2215_sendung_37g_a3a4_6660k_p37v17.mp4","","1685477700","","","false"],"X":["","","37°: Ein Gen verändert unser Leben (Audiodeskription)","16.05.2023","22:15:00","00:28:41","414","Eine Diagnose, die alles verändert: Milo hat einen Gendefekt. Wie wird das Leben werden mit einem Kind, das extrem viel Betreuung braucht? \"37°\" begleitet die Familie.","https://rodlzdf-a.akamaihd.net/none/zdf/23/05/230502_2215_sendung_37g/4/230502_2215_sendung_37g_a3a4_2360k_p35v17.mp4","https://www.3sat.de/gesellschaft/37-grad/37-ein-gen-veraendert-unser-leben-100.html","","","101|808k_p11v17.mp4","","8|nrodlzdf-a.akamaihd.net/none/zdf/23/05/230502_2215_sendung_37g/4/230502_2215_sendung_37g_a3a4_6660k_p37v17.mp4","","1684268100","","","false"],"X":["","","37°: Mein Tanz, mein Battle (Audiodeskription)","11.10.2022","22:15:00","00:28:42","416","Joanna und Serhat teilen dieselbe Leidenschaft: Breakdance. 2024 feiert Breaken in Paris olympische Premiere. Eine Herausforderung für die Tänzer, wirklich alles zu geben.","https://rodlzdf-a.akamaihd.net/none/zdf/22/10/221011_2215_sendung_37g/3/221011_2215_sendung_37g_a3a4_2360k_p35v15.mp4","https://www.3sat.de/gesellschaft/37-grad/37-mein-tanz-mein-battle-100.html","","","101|808k_p11v15.mp4","","8|nrodlzdf-a.akamaihd.net/none/zdf/22/10/221011_2215_sendung_37g/3/221011_2215_sendung_37g_a3a4_3360k_p36v15.mp4","","1665519300","","","false"],"X":["","37 Grad Leben","Stroke Survivor – Ich geb' nicht auf! -","30.05.2023","09:03:00","00:27:07","377","Kaum ins Leben gestartet – und dann die Diagnose Schlaganfall. Wie kommen vor allem junge Betroffene, die sich gesund ernähren und sportlich leben, damit zurecht?","https://rodlzdf-a.akamaihd.net/none/zdf/23/05/230507_0903_sendung_sgl/1/230507_0903_sendung_sgl_2360k_p35v17.mp4","https://www.3sat.de/gesellschaft/37-grad-leben/stroke-survivor-ich-gebe-nicht-auf-102.html","https://utstreaming.zdf.de/mtt/zdf/23/05/230507_0903_sendung_sgl/2/F1039138_hoh_deu_37_Leben_Stroke_Survivors_neu.xml","","96|808k_p11v17.mp4","","8|nrodlzdf-a.akamaihd.net/none/zdf/23/05/230507_0903_sendung_sgl/1/230507_0903_sendung_sgl_6660k_p37v17.mp4","","1685430180","","","false"],"X":["","","Ich lass mich taufen -","28.05.2023","09:03:00","00:27:08","390","Trotz vieler Kirchenaustritte gibt es immer wieder Menschen, die einen Weg in die Kirche finden, sich im Erwachsenenalter taufen lassen. \"37°Leben\" begleitet zwei junge Frauen bei ihrer Taufe.","https://rodlzdf-a.akamaihd.net/none/zdf/23/05/230528_0903_sendung_sgl/1/230528_0903_sendung_sgl_2360k_p35v17.mp4","https://www.3sat.de/gesellschaft/37-grad-leben/ich-lass-mich-taufen-102.html","https://utstreaming.zdf.de/mtt/zdf/23/05/230528_0903_sendung_sgl/2/F1039826_hoh_deu_37_Leben_Ich_lass_mich_taufen_280523.xml","","96|808k_p11v17.mp4","","8|nrodlzdf-a.akamaihd.net/none/zdf/23/05/230528_0903_sendung_sgl/1/230528_0903_sendung_sgl_6660k_p37v17.mp4","","1685257380","","","false"],"X":["","","Körperideale: so what? -","14.05.2023","09:03:00","00:26:58","371","Federico ist 1,63 Meter groß. Als kleiner Mann selbstbewusst zu sein und sich begehrenswert zu fühlen, ist sein Weg. Bis heute muss er sich mit verletzenden Kommentaren auseinandersetzen.","https://rodlzdf-a.akamaihd.net/none/zdf/23/05/230514_0903_sendung_sgl/1/230514_0903_sendung_sgl_2360k_p35v17.mp4","https://www.3sat.de/gesellschaft/37-grad-leben/koerperideale-so-what-102.html","https://utstreaming.zdf.de/mtt/zdf/23/05/230514_0903_sendung_sgl/2/F1039665_hoh_deu_37_Grad_Leben_Koerperideale_so_what_ap_140523.xml","","96|808k_p11v17.mp4","","8|nrodlzdf-a.akamaihd.net/none/zdf/23/05/230514_0903_sendung_sgl/1/230514_0903_sendung_sgl_6660k_p37v17.mp4","","1684047780","","","false"],"X":["","Besonders normal","Leben in Zeiten von Corona","08.07.2020","11:44:00","00:29:01","499","Menschen mit Behinderungen sind gerade in größter Sorge. Sie haben Angst vor Ansteckung und einem schweren Verlauf, vor der Einsamkeit in Isolation und einem Assistenz- und Pflegekräftemangel.","https://rodlzdf-a.akamaihd.net/none/3sat/20/05/200508__1140_sendung_besondersnormal/2/200508__1140_sendung_besondersnormal_2328k_p35v13.mp4","https://www.3sat.de/gesellschaft/besonders-normal/besonders-normal-vom-8-mai-2020-102.html","https://utstreaming.zdf.de/mtt/3sat/20/05/200508__1140_sendung_besondersnormal/2/F1026017_hoh_deu_Besonders_normal_Leben_in_Zeiten_von_Corona_080520.xml","","123|776k_p11v13.mp4","","123|3328k_p36v13.mp4","","1594201440","","","false"],"X":["","","Corona - Zwischen Sorge und Zuversicht","05.06.2020","11:30:00","00:28:56","499","Die Corona-Pandemie wirkt immer noch nach. Auch bei den Menschen mit Behinderungen und sozial Schwachen. Sie können nicht wie gewohnt arbeiten oder bangen um die berufliche Existenz.","https://rodlzdf-a.akamaihd.net/none/3sat/20/06/200605_1130_sendung_besondersnormal/2/200605_1130_sendung_besondersnormal_2328k_p35v13.mp4","https://www.3sat.de/gesellschaft/besonders-normal/besonders-normal-vom-5-juni-2020-100.html","https://utstreaming.zdf.de/mtt/3sat/20/06/200605_1130_sendung_besondersnormal/2/F1026188_hoh_deu_Besonders_normal_Corona_Zwischen_Zuversicht_und_Sorge_050620.xml","","121|776k_p11v13.mp4","","121|3328k_p36v13.mp4","","1591349400","","","false"],"X":["","","Beste Freunde","07.06.2019","12:29:00","00:29:27","509","Während die Politik und Gesellschaft über die Möglichkeiten von Inklusion und Teilhabe diskutieren, haben zwei kleine Mädchen, den Traum schon längst umgesetzt.","https://rodlzdf-a.akamaihd.net/none/3sat/19/06/190607_beste_freunde/1/190607_beste_freunde_2328k_p35v13.mp4","https://www.3sat.de/gesellschaft/besonders-normal/beste-freunde-102.html","","","91|776k_p11v13.mp4","","91|3296k_p15v13.mp4","","1559903340","","","false"],"X":["","Bier Royal","Bier Royal (1/2) - Familienkomödie, Deutschland 2019","30.01.2019","20:15:00","01:25:05","1467","München. Bier. Society. Und mittendrin eine alteingesessene Bierdynastie, in der zwei Frauen um Macht, Liebe und Anerkennung kämpfen.","https://rodlzdf-a.akamaihd.net/dach/3sat/19/12/191227_bier_royal1_fernsehfilm/2/191227_bier_royal1_fernsehfilm_2328k_p35v13.mp4","https://www.3sat.de/film/bier-royal/bier-royal-1-100.html","","","111|776k_p11v13.mp4","","111|3328k_p36v13.mp4","","1548875700","","DE-AT-CH","false"],"X":["","","Bier Royal (2/2) - Familienkomödie, Deutschland 2019","28.01.2019","20:15:00","01:27:30","1510","Die Münchner Brauerei droht im erbitterten Erbstreit unterzugehen. Gisela und Vicky müssen zusammenarbeiten, um das Unternehmen zu retten.","https://rodlzdf-a.akamaihd.net/dach/3sat/19/12/191227_bier_royal2_fernsehfilm/4/191227_bier_royal2_fernsehfilm_2328k_p35v13.mp4","https://www.3sat.de/film/bier-royal/bier-royal-2-100.html","","","111|776k_p11v13.mp4","","111|3328k_p36v13.mp4","","1548702900","","DE-AT-CH","false"],"X":["","Bilder aus Südtirol","Bilder aus Südtirol vom 20. Mai 2023","20.05.2023","09:35:00","00:23:32","327","\"Bilder aus Südtirol\" ist das Bundesländermagazin des ORF-Landesstudios Tirol.","https://rodlzdf-a.akamaihd.net/none/3sat/23/05/230520_0935_sendung_suedtirol/1/230520_0935_sendung_suedtirol_2360k_p35v17.mp4","https://www.3sat.de/gesellschaft/bilder-aus-suedtirol/bilder-aus-suedtirol-vom-20-mai-2023-100.html","","","109|808k_p11v17.mp4","","8|nrodlzdf-a.akamaihd.net/none/3sat/23/05/230520_0935_sendung_suedtirol/1/230520_0935_sendung_suedtirol_6660k_p37v17.mp4","","1684568100","","","false"],"X":["","","Bilder aus Südtirol vom 13. Mai 2023","13.05.2023","09:37:00","00:22:38","314","\"Bilder aus Südtirol\" ist das Bundesländermagazin des ORF-Landesstudios Tirol.","https://rodlzdf-a.akamaihd.net/none/3sat/23/05/230513_0935_sendung_suedtirol/1/230513_0935_sendung_suedtirol_2360k_p35v17.mp4","https://www.3sat.de/gesellschaft/bilder-aus-suedtirol/bilder-aus-suedtirol-vom-13-mai-2023-100.html","","","109|808k_p11v17.mp4","","8|nrodlzdf-a.akamaihd.net/none/3sat/23/05/230513_0935_sendung_suedtirol/1/230513_0935_sendung_suedtirol_6660k_p37v17.mp4","","1683963420","","","false"],"X":["","","Bilder aus Südtirol vom 6. Mai 2023","06.05.2023","09:39:00","00:24:21","336","\"Bilder aus Südtirol\" ist das Bundesländermagazin des ORF-Landesstudios Tirol.","https://rodlzdf-a.akamaihd.net/none/3sat/23/05/230506_0935_sendung_suedtirol/1/230506_0935_sendung_suedtirol_2360k_p35v17.mp4","https://www.3sat.de/gesellschaft/bilder-aus-suedtirol/bilder-aus-suedtirol-vom-6-mai-2023-100.html","","","109|808k_p11v17.mp4","","8|nrodlzdf-a.akamaihd.net/none/3sat/23/05/230506_0935_sendung_suedtirol/1/230506_0935_sendung_suedtirol_6660k_p37v17.mp4","","1683358740","","","false"],"X":["","","Bilder aus Südtirol vom 22. April 2023","22.04.2023","09:39:00","00:23:52","337","\"Bilder aus Südtirol\" ist das Bundesländermagazin des ORF-Landesstudios Tirol.","https://rodlzdf-a.akamaihd.net/none/3sat/23/04/230422_0935_sendung_suedtirol/1/230422_0935_sendung_suedtirol_2360k_p35v17.mp4","https://www.3sat.de/gesellschaft/bilder-aus-suedtirol/bilder-aus-suedtirol-vom-22-april-2023-100.html","","","109|808k_p11v17.mp4","","8|nrodlzdf-a.akamaihd.net/none/3sat/23/04/230422_0935_sendung_suedtirol/1/230422_0935_sendung_suedtirol_6660k_p37v17.mp4","","1682149140","","","false"],"X":["","","Bilder aus Südtirol vom 15. April 2023","15.04.2023","09:37:00","00:19:42","273","\"Bilder aus Südtirol\" ist das Bundesländermagazin des ORF-Landesstudios Tirol.","https://rodlzdf-a.akamaihd.net/none/3sat/23/04/230415_0935_sendung_suedtirol/1/230415_0935_sendung_suedtirol_2360k_p35v17.mp4","https://www.3sat.de/gesellschaft/bilder-aus-suedtirol/bilder-aus-suedtirol-vom-15-april-2023-100.html","","","109|808k_p11v17.mp4","","8|nrodlzdf-a.akamaihd.net/none/3sat/23/04/230415_0935_sendung_suedtirol/1/230415_0935_sendung_suedtirol_6660k_p37v17.mp4","","1681544220","","","false"],"X":["","","Bilder aus Südtirol vom 8. April 2023","08.04.2023","09:36:00","00:22:56","323","\"Bilder aus Südtirol\" ist das Bundesländermagazin des ORF-Landesstudios Tirol.","https://rodlzdf-a.akamaihd.net/none/3sat/23/04/230408_0935_sendung_suedtirol/1/230408_0935_sendung_suedtirol_2360k_p35v17.mp4","https://www.3sat.de/gesellschaft/bilder-aus-suedtirol/bilder-aus-suedtirol-vom-8-april-2023-100.html","","","109|808k_p11v17.mp4","","8|nrodlzdf-a.akamaihd.net/none/3sat/23/04/230408_0935_sendung_suedtirol/1/230408_0935_sendung_suedtirol_6660k_p37v17.mp4","","1680939360","","","false"],"X":["","","Bilder aus Südtirol vom 11. Dezember 2021","11.12.2021","09:35:00","00:24:35","335","\"Bilder aus Südtirol\" ist das Bundesländermagazin des ORF-Landesstudios Tirol.","https://rodlzdf-a.akamaihd.net/none/3sat/21/12/211211_0935_sendung_suedtirol/1/211211_0935_sendung_suedtirol_2360k_p35v15.mp4","https://www.3sat.de/gesellschaft/bilder-aus-suedtirol/bilder-aus-suedtirol-vom-11-dezember-2021-100.html","","","109|808k_p11v15.mp4","","109|3360k_p36v15.mp4","","1639211700","","","false"],"X":["","","Bilder aus Südtirol vom 6. November 2021","06.11.2021","09:35:00","00:23:58","333","\"Bilder aus Südtirol\" ist das Bundesländermagazin des ORF-Landesstudios Tirol.","https://rodlzdf-a.akamaihd.net/none/3sat/21/11/211106_0935_sendung_suedtirol/1/211106_0935_sendung_suedtirol_2360k_p35v15.mp4","https://www.3sat.de/gesellschaft/bilder-aus-suedtirol/bilder-aus-suedtirol-vom-6-november-2021-100.html","","","109|808k_p11v15.mp4","","109|3360k_p36v15.mp4","","1636187700","","","false"],"X":["","","Bilder aus Südtirol vom 25. Juli 2020","25.07.2020","09:35:00","00:23:41","407","\"Bilder aus Südtirol\" ist das Bundesländermagazin des ORF-Landesstudios Tirol.","https://rodlzdf-a.akamaihd.net/none/3sat/20/07/200725_0935_sendung_suedtirol/1/200725_0935_sendung_suedtirol_2328k_p35v13.mp4","https://www.3sat.de/gesellschaft/bilder-aus-suedtirol/bilder-aus-suedtirol-vom-25-juli-2020-100.html","","","109|776k_p11v13.mp4","","109|3328k_p36v13.mp4","","1595662500","","","false"],"X":["","","Bilder aus Südtirol vom 20. Juni 2020","20.06.2020","09:36:00","00:24:16","416","\"Bilder aus Südtirol\" ist das Bundesländermagazin des ORF-Landesstudios Tirol.","https://rodlzdf-a.akamaihd.net/none/3sat/20/06/200620_0935_sendung_suedtirol/1/200620_0935_sendung_suedtirol_2328k_p35v13.mp4","https://www.3sat.de/gesellschaft/bilder-aus-suedtirol/bilder-aus-suedtirol-vom-20-juni-2020-100.html","","","109|776k_p11v13.mp4","","109|3328k_p36v13.mp4","","1592638560","","","false"]} \ No newline at end of file +{"Filmliste":["30.05.2023, 19:21","30.05.2023, 17:21","4","MSearch [Vers.: 4.0.1]","7bc3d924-974f-42c4-8994-9d98dde17862"],"Filmliste":["Sender","Thema","Titel","Datum","Zeit","Dauer","Größe [MB]","Beschreibung","Url","Website","Url Untertitel","Url RTMP","Url Klein","Url RTMP Klein","Url HD","Url RTMP HD","DatumL","Url History","Geo","neu","Sprache"],"X":["3Sat","37 Grad","37° Totgerast (Audiodeskription)","30.05.2023","22:15:00","00:28:45","405","Wer plötzlich Angehörige bei einem Unfall verliert, wacht in einem anderen Leben auf. Antonia verlor ihre Eltern und ihre Schwester, Steffen seine Frau und seinen Sohn.","https://rodlzdf-a.akamaihd.net/none/zdf/23/05/230530_2215_sendung_37g/2/230530_2215_sendung_37g_a3a4_2360k_p35v17.mp4","https://www.3sat.de/gesellschaft/37-grad/37-totgerast-102.html","","","101|808k_p11v17.mp4","","8|nrodlzdf-a.akamaihd.net/none/zdf/23/05/230530_2215_sendung_37g/2/230530_2215_sendung_37g_a3a4_6660k_p37v17.mp4","","1685477700","","","false",""],"X":["","","37°: Ein Gen verändert unser Leben (Audiodeskription)","16.05.2023","22:15:00","00:28:41","414","Eine Diagnose, die alles verändert: Milo hat einen Gendefekt. Wie wird das Leben werden mit einem Kind, das extrem viel Betreuung braucht? \"37°\" begleitet die Familie.","https://rodlzdf-a.akamaihd.net/none/zdf/23/05/230502_2215_sendung_37g/4/230502_2215_sendung_37g_a3a4_2360k_p35v17.mp4","https://www.3sat.de/gesellschaft/37-grad/37-ein-gen-veraendert-unser-leben-100.html","","","101|808k_p11v17.mp4","","8|nrodlzdf-a.akamaihd.net/none/zdf/23/05/230502_2215_sendung_37g/4/230502_2215_sendung_37g_a3a4_6660k_p37v17.mp4","","1684268100","","","false",""],"X":["","","37°: Mein Tanz, mein Battle (Audiodeskription)","11.10.2022","22:15:00","00:28:42","416","Joanna und Serhat teilen dieselbe Leidenschaft: Breakdance. 2024 feiert Breaken in Paris olympische Premiere. Eine Herausforderung für die Tänzer, wirklich alles zu geben.","https://rodlzdf-a.akamaihd.net/none/zdf/22/10/221011_2215_sendung_37g/3/221011_2215_sendung_37g_a3a4_2360k_p35v15.mp4","https://www.3sat.de/gesellschaft/37-grad/37-mein-tanz-mein-battle-100.html","","","101|808k_p11v15.mp4","","8|nrodlzdf-a.akamaihd.net/none/zdf/22/10/221011_2215_sendung_37g/3/221011_2215_sendung_37g_a3a4_3360k_p36v15.mp4","","1665519300","","","false",""],"X":["","37 Grad Leben","Stroke Survivor – Ich geb' nicht auf! -","30.05.2023","09:03:00","00:27:07","377","Kaum ins Leben gestartet – und dann die Diagnose Schlaganfall. Wie kommen vor allem junge Betroffene, die sich gesund ernähren und sportlich leben, damit zurecht?","https://rodlzdf-a.akamaihd.net/none/zdf/23/05/230507_0903_sendung_sgl/1/230507_0903_sendung_sgl_2360k_p35v17.mp4","https://www.3sat.de/gesellschaft/37-grad-leben/stroke-survivor-ich-gebe-nicht-auf-102.html","https://utstreaming.zdf.de/mtt/zdf/23/05/230507_0903_sendung_sgl/2/F1039138_hoh_deu_37_Leben_Stroke_Survivors_neu.xml","","96|808k_p11v17.mp4","","8|nrodlzdf-a.akamaihd.net/none/zdf/23/05/230507_0903_sendung_sgl/1/230507_0903_sendung_sgl_6660k_p37v17.mp4","","1685430180","","","false",""],"X":["","","Ich lass mich taufen -","28.05.2023","09:03:00","00:27:08","390","Trotz vieler Kirchenaustritte gibt es immer wieder Menschen, die einen Weg in die Kirche finden, sich im Erwachsenenalter taufen lassen. \"37°Leben\" begleitet zwei junge Frauen bei ihrer Taufe.","https://rodlzdf-a.akamaihd.net/none/zdf/23/05/230528_0903_sendung_sgl/1/230528_0903_sendung_sgl_2360k_p35v17.mp4","https://www.3sat.de/gesellschaft/37-grad-leben/ich-lass-mich-taufen-102.html","https://utstreaming.zdf.de/mtt/zdf/23/05/230528_0903_sendung_sgl/2/F1039826_hoh_deu_37_Leben_Ich_lass_mich_taufen_280523.xml","","96|808k_p11v17.mp4","","8|nrodlzdf-a.akamaihd.net/none/zdf/23/05/230528_0903_sendung_sgl/1/230528_0903_sendung_sgl_6660k_p37v17.mp4","","1685257380","","","false",""],"X":["","","Körperideale: so what? -","14.05.2023","09:03:00","00:26:58","371","Federico ist 1,63 Meter groß. Als kleiner Mann selbstbewusst zu sein und sich begehrenswert zu fühlen, ist sein Weg. Bis heute muss er sich mit verletzenden Kommentaren auseinandersetzen.","https://rodlzdf-a.akamaihd.net/none/zdf/23/05/230514_0903_sendung_sgl/1/230514_0903_sendung_sgl_2360k_p35v17.mp4","https://www.3sat.de/gesellschaft/37-grad-leben/koerperideale-so-what-102.html","https://utstreaming.zdf.de/mtt/zdf/23/05/230514_0903_sendung_sgl/2/F1039665_hoh_deu_37_Grad_Leben_Koerperideale_so_what_ap_140523.xml","","96|808k_p11v17.mp4","","8|nrodlzdf-a.akamaihd.net/none/zdf/23/05/230514_0903_sendung_sgl/1/230514_0903_sendung_sgl_6660k_p37v17.mp4","","1684047780","","","false",""],"X":["","Besonders normal","Leben in Zeiten von Corona","08.07.2020","11:44:00","00:29:01","499","Menschen mit Behinderungen sind gerade in größter Sorge. Sie haben Angst vor Ansteckung und einem schweren Verlauf, vor der Einsamkeit in Isolation und einem Assistenz- und Pflegekräftemangel.","https://rodlzdf-a.akamaihd.net/none/3sat/20/05/200508__1140_sendung_besondersnormal/2/200508__1140_sendung_besondersnormal_2328k_p35v13.mp4","https://www.3sat.de/gesellschaft/besonders-normal/besonders-normal-vom-8-mai-2020-102.html","https://utstreaming.zdf.de/mtt/3sat/20/05/200508__1140_sendung_besondersnormal/2/F1026017_hoh_deu_Besonders_normal_Leben_in_Zeiten_von_Corona_080520.xml","","123|776k_p11v13.mp4","","123|3328k_p36v13.mp4","","1594201440","","","false",""],"X":["","","Corona - Zwischen Sorge und Zuversicht","05.06.2020","11:30:00","00:28:56","499","Die Corona-Pandemie wirkt immer noch nach. Auch bei den Menschen mit Behinderungen und sozial Schwachen. Sie können nicht wie gewohnt arbeiten oder bangen um die berufliche Existenz.","https://rodlzdf-a.akamaihd.net/none/3sat/20/06/200605_1130_sendung_besondersnormal/2/200605_1130_sendung_besondersnormal_2328k_p35v13.mp4","https://www.3sat.de/gesellschaft/besonders-normal/besonders-normal-vom-5-juni-2020-100.html","https://utstreaming.zdf.de/mtt/3sat/20/06/200605_1130_sendung_besondersnormal/2/F1026188_hoh_deu_Besonders_normal_Corona_Zwischen_Zuversicht_und_Sorge_050620.xml","","121|776k_p11v13.mp4","","121|3328k_p36v13.mp4","","1591349400","","","false",""],"X":["","","Beste Freunde","07.06.2019","12:29:00","00:29:27","509","Während die Politik und Gesellschaft über die Möglichkeiten von Inklusion und Teilhabe diskutieren, haben zwei kleine Mädchen, den Traum schon längst umgesetzt.","https://rodlzdf-a.akamaihd.net/none/3sat/19/06/190607_beste_freunde/1/190607_beste_freunde_2328k_p35v13.mp4","https://www.3sat.de/gesellschaft/besonders-normal/beste-freunde-102.html","","","91|776k_p11v13.mp4","","91|3296k_p15v13.mp4","","1559903340","","","false",""],"X":["","Bier Royal","Bier Royal (1/2) - Familienkomödie, Deutschland 2019","30.01.2019","20:15:00","01:25:05","1467","München. Bier. Society. Und mittendrin eine alteingesessene Bierdynastie, in der zwei Frauen um Macht, Liebe und Anerkennung kämpfen.","https://rodlzdf-a.akamaihd.net/dach/3sat/19/12/191227_bier_royal1_fernsehfilm/2/191227_bier_royal1_fernsehfilm_2328k_p35v13.mp4","https://www.3sat.de/film/bier-royal/bier-royal-1-100.html","","","111|776k_p11v13.mp4","","111|3328k_p36v13.mp4","","1548875700","","DE-AT-CH","false",""],"X":["","","Bier Royal (2/2) - Familienkomödie, Deutschland 2019","28.01.2019","20:15:00","01:27:30","1510","Die Münchner Brauerei droht im erbitterten Erbstreit unterzugehen. Gisela und Vicky müssen zusammenarbeiten, um das Unternehmen zu retten.","https://rodlzdf-a.akamaihd.net/dach/3sat/19/12/191227_bier_royal2_fernsehfilm/4/191227_bier_royal2_fernsehfilm_2328k_p35v13.mp4","https://www.3sat.de/film/bier-royal/bier-royal-2-100.html","","","111|776k_p11v13.mp4","","111|3328k_p36v13.mp4","","1548702900","","DE-AT-CH","false",""],"X":["","Bilder aus Südtirol","Bilder aus Südtirol vom 20. Mai 2023","20.05.2023","09:35:00","00:23:32","327","\"Bilder aus Südtirol\" ist das Bundesländermagazin des ORF-Landesstudios Tirol.","https://rodlzdf-a.akamaihd.net/none/3sat/23/05/230520_0935_sendung_suedtirol/1/230520_0935_sendung_suedtirol_2360k_p35v17.mp4","https://www.3sat.de/gesellschaft/bilder-aus-suedtirol/bilder-aus-suedtirol-vom-20-mai-2023-100.html","","","109|808k_p11v17.mp4","","8|nrodlzdf-a.akamaihd.net/none/3sat/23/05/230520_0935_sendung_suedtirol/1/230520_0935_sendung_suedtirol_6660k_p37v17.mp4","","1684568100","","","false",""],"X":["","","Bilder aus Südtirol vom 13. Mai 2023","13.05.2023","09:37:00","00:22:38","314","\"Bilder aus Südtirol\" ist das Bundesländermagazin des ORF-Landesstudios Tirol.","https://rodlzdf-a.akamaihd.net/none/3sat/23/05/230513_0935_sendung_suedtirol/1/230513_0935_sendung_suedtirol_2360k_p35v17.mp4","https://www.3sat.de/gesellschaft/bilder-aus-suedtirol/bilder-aus-suedtirol-vom-13-mai-2023-100.html","","","109|808k_p11v17.mp4","","8|nrodlzdf-a.akamaihd.net/none/3sat/23/05/230513_0935_sendung_suedtirol/1/230513_0935_sendung_suedtirol_6660k_p37v17.mp4","","1683963420","","","false",""],"X":["","","Bilder aus Südtirol vom 6. Mai 2023","06.05.2023","09:39:00","00:24:21","336","\"Bilder aus Südtirol\" ist das Bundesländermagazin des ORF-Landesstudios Tirol.","https://rodlzdf-a.akamaihd.net/none/3sat/23/05/230506_0935_sendung_suedtirol/1/230506_0935_sendung_suedtirol_2360k_p35v17.mp4","https://www.3sat.de/gesellschaft/bilder-aus-suedtirol/bilder-aus-suedtirol-vom-6-mai-2023-100.html","","","109|808k_p11v17.mp4","","8|nrodlzdf-a.akamaihd.net/none/3sat/23/05/230506_0935_sendung_suedtirol/1/230506_0935_sendung_suedtirol_6660k_p37v17.mp4","","1683358740","","","false",""],"X":["","","Bilder aus Südtirol vom 22. April 2023","22.04.2023","09:39:00","00:23:52","337","\"Bilder aus Südtirol\" ist das Bundesländermagazin des ORF-Landesstudios Tirol.","https://rodlzdf-a.akamaihd.net/none/3sat/23/04/230422_0935_sendung_suedtirol/1/230422_0935_sendung_suedtirol_2360k_p35v17.mp4","https://www.3sat.de/gesellschaft/bilder-aus-suedtirol/bilder-aus-suedtirol-vom-22-april-2023-100.html","","","109|808k_p11v17.mp4","","8|nrodlzdf-a.akamaihd.net/none/3sat/23/04/230422_0935_sendung_suedtirol/1/230422_0935_sendung_suedtirol_6660k_p37v17.mp4","","1682149140","","","false",""],"X":["","","Bilder aus Südtirol vom 15. April 2023","15.04.2023","09:37:00","00:19:42","273","\"Bilder aus Südtirol\" ist das Bundesländermagazin des ORF-Landesstudios Tirol.","https://rodlzdf-a.akamaihd.net/none/3sat/23/04/230415_0935_sendung_suedtirol/1/230415_0935_sendung_suedtirol_2360k_p35v17.mp4","https://www.3sat.de/gesellschaft/bilder-aus-suedtirol/bilder-aus-suedtirol-vom-15-april-2023-100.html","","","109|808k_p11v17.mp4","","8|nrodlzdf-a.akamaihd.net/none/3sat/23/04/230415_0935_sendung_suedtirol/1/230415_0935_sendung_suedtirol_6660k_p37v17.mp4","","1681544220","","","false",""],"X":["","","Bilder aus Südtirol vom 8. April 2023","08.04.2023","09:36:00","00:22:56","323","\"Bilder aus Südtirol\" ist das Bundesländermagazin des ORF-Landesstudios Tirol.","https://rodlzdf-a.akamaihd.net/none/3sat/23/04/230408_0935_sendung_suedtirol/1/230408_0935_sendung_suedtirol_2360k_p35v17.mp4","https://www.3sat.de/gesellschaft/bilder-aus-suedtirol/bilder-aus-suedtirol-vom-8-april-2023-100.html","","","109|808k_p11v17.mp4","","8|nrodlzdf-a.akamaihd.net/none/3sat/23/04/230408_0935_sendung_suedtirol/1/230408_0935_sendung_suedtirol_6660k_p37v17.mp4","","1680939360","","","false",""],"X":["","","Bilder aus Südtirol vom 11. Dezember 2021","11.12.2021","09:35:00","00:24:35","335","\"Bilder aus Südtirol\" ist das Bundesländermagazin des ORF-Landesstudios Tirol.","https://rodlzdf-a.akamaihd.net/none/3sat/21/12/211211_0935_sendung_suedtirol/1/211211_0935_sendung_suedtirol_2360k_p35v15.mp4","https://www.3sat.de/gesellschaft/bilder-aus-suedtirol/bilder-aus-suedtirol-vom-11-dezember-2021-100.html","","","109|808k_p11v15.mp4","","109|3360k_p36v15.mp4","","1639211700","","","false",""],"X":["","","Bilder aus Südtirol vom 6. November 2021","06.11.2021","09:35:00","00:23:58","333","\"Bilder aus Südtirol\" ist das Bundesländermagazin des ORF-Landesstudios Tirol.","https://rodlzdf-a.akamaihd.net/none/3sat/21/11/211106_0935_sendung_suedtirol/1/211106_0935_sendung_suedtirol_2360k_p35v15.mp4","https://www.3sat.de/gesellschaft/bilder-aus-suedtirol/bilder-aus-suedtirol-vom-6-november-2021-100.html","","","109|808k_p11v15.mp4","","109|3360k_p36v15.mp4","","1636187700","","","false",""],"X":["","","Bilder aus Südtirol vom 25. Juli 2020","25.07.2020","09:35:00","00:23:41","407","\"Bilder aus Südtirol\" ist das Bundesländermagazin des ORF-Landesstudios Tirol.","https://rodlzdf-a.akamaihd.net/none/3sat/20/07/200725_0935_sendung_suedtirol/1/200725_0935_sendung_suedtirol_2328k_p35v13.mp4","https://www.3sat.de/gesellschaft/bilder-aus-suedtirol/bilder-aus-suedtirol-vom-25-juli-2020-100.html","","","109|776k_p11v13.mp4","","109|3328k_p36v13.mp4","","1595662500","","","false",""],"X":["","","Bilder aus Südtirol vom 20. Juni 2020","20.06.2020","09:36:00","00:24:16","416","\"Bilder aus Südtirol\" ist das Bundesländermagazin des ORF-Landesstudios Tirol.","https://rodlzdf-a.akamaihd.net/none/3sat/20/06/200620_0935_sendung_suedtirol/1/200620_0935_sendung_suedtirol_2328k_p35v13.mp4","https://www.3sat.de/gesellschaft/bilder-aus-suedtirol/bilder-aus-suedtirol-vom-20-juni-2020-100.html","","","109|776k_p11v13.mp4","","109|3328k_p36v13.mp4","","1592638560","","","false",""]} \ No newline at end of file From 3180d63e850682fa7cce6c30e189226b8e22bbf7 Mon Sep 17 00:00:00 2001 From: CuriousApe2020 Date: Thu, 17 Sep 2026 21:29:28 +0000 Subject: [PATCH 2/2] Sprache der Tonspur auch fuer ZDF Baut auf dem ARD-Teil auf und fuellt dasselbe Feld Film.audioLanguage fuer das ZDF. Das ZDF fuehrt die Downloads ohnehin je Sprache (DownloadDto haelt sie als Map Sprache auf Aufloesung auf URL) und erzeugt daraus je Sprache einen eigenen Film. Den Code kennt es dabei bereits in der Form, die das Feld zusichert - ZdfConstants fuehrt LANGUAGE_ENGLISH als "eng" und LANGUAGE_FRENCH als "fra". Verwendet wurde er bisher aber nur, um einen deutschen Titelzusatz zu bilden: case ZdfConstants.LANGUAGE_ENGLISH: title += " (Englisch)"; Danach war der Code nicht mehr vorhanden. Er wird jetzt an derselben Stelle zusaetzlich als Feld gesetzt - in ZdfFilmDetailTask und ZdfFilmTask, die beide dieselbe clone/updateTitle-Paarung haben. Passend zum ARD-Teil bleibt die deutsche Fassung leer: sie ist die uebliche Fassung des Senders, und ein Wert auf jedem Datensatz wuerde die Filmliste unnoetig aufblaehen. Das entspricht dem Titel, der fuer sie ebenfalls unveraendert bleibt. Die Suffixe "-ad" und "-dgs", die das ZDF an den Sprachcode haengt, um Audiodeskription und Gebaerdensprache zu unterscheiden, werden abgeschnitten: sie benennen die Art der Tonspur, nicht ihre Sprache. Damit sind auch 3sat und phoenix abgedeckt, die dieselbe Infrastruktur nutzen. Test: englische Fassung behaelt "eng", die deutsche bleibt leer - am vorhandenen Fixture fuer mehrsprachige Beitraege. --- .../crawler/zdf/tasks/ZdfFilmDetailTask.java | 31 ++++++++++ .../crawler/zdf/tasks/ZdfFilmTask.java | 31 ++++++++++ ...dfFilmDetailTaskMultipleLanguagesTest.java | 59 +++++++++++++++++++ 3 files changed, 121 insertions(+) diff --git a/src/main/java/de/mediathekview/mserver/crawler/zdf/tasks/ZdfFilmDetailTask.java b/src/main/java/de/mediathekview/mserver/crawler/zdf/tasks/ZdfFilmDetailTask.java index e8d567e46..2ff2f7d7c 100644 --- a/src/main/java/de/mediathekview/mserver/crawler/zdf/tasks/ZdfFilmDetailTask.java +++ b/src/main/java/de/mediathekview/mserver/crawler/zdf/tasks/ZdfFilmDetailTask.java @@ -64,10 +64,41 @@ private static Film clone(final Film aFilm, final String aLanguage) { film.setWebsite(aFilm.getWebsite().orElse(null)); updateTitle(aLanguage, film); + setAudioLanguage(aLanguage, film); return film; } + /** + * Haelt die Sprache der Tonspur als Feld fest, dort wo sie bisher nur in den Titel geschrieben + * wurde. + * + *

Das ZDF fuehrt die Downloads ohnehin je Sprache und kennt den Code bereits in der Form, die + * das Feld zusichert ("deu", "eng", "fra"). Aus dem Code wurde bisher nur ein deutscher + * Titelzusatz; danach war er nicht mehr vorhanden. + * + *

Die deutsche Fassung bleibt leer - sie ist die uebliche Fassung des Senders, und ein Wert + * auf jedem Datensatz wuerde die Filmliste unnoetig aufblaehen. Das entspricht dem Titel, der + * fuer sie ebenfalls unveraendert bleibt. + * + *

Die Suffixe fuer Audiodeskription und Gebaerdensprache werden abgeschnitten: sie benennen + * die Art der Tonspur, nicht ihre Sprache. + */ + private static void setAudioLanguage(final String aLanguage, final Film aFilm) { + final String languageCode = aLanguage.split("-")[0]; + if (ZdfConstants.LANGUAGE_GERMAN.equals(languageCode)) { + return; + } + // Das Feld sichert den dreistelligen ISO-639-2/T-Code zu. Die Tracks des ZDF sind bisher + // ausnahmslos so ausgezeichnet ("deu", "eng"); sollte doch einmal etwas anderes kommen, bleibt + // das Feld lieber leer, als einen Wert zu fuehren, auf den sich Clients nicht verlassen + // koennen. + if (languageCode.length() != 3 || !languageCode.chars().allMatch(Character::isLetter)) { + return; + } + aFilm.setAudioLanguage(languageCode); + } + private static void updateTitle(final String aLanguage, final Film aFilm) { String title = aFilm.getTitel(); switch (aLanguage) { diff --git a/src/main/java/de/mediathekview/mserver/crawler/zdf/tasks/ZdfFilmTask.java b/src/main/java/de/mediathekview/mserver/crawler/zdf/tasks/ZdfFilmTask.java index a103d8900..fafbc9f69 100644 --- a/src/main/java/de/mediathekview/mserver/crawler/zdf/tasks/ZdfFilmTask.java +++ b/src/main/java/de/mediathekview/mserver/crawler/zdf/tasks/ZdfFilmTask.java @@ -83,10 +83,41 @@ private static Film clone(final Film aFilm, final String aLanguage) { film.setWebsite(aFilm.getWebsite().orElse(null)); updateTitle(aLanguage, film); + setAudioLanguage(aLanguage, film); return film; } + /** + * Haelt die Sprache der Tonspur als Feld fest, dort wo sie bisher nur in den Titel geschrieben + * wurde. + * + *

Das ZDF fuehrt die Downloads ohnehin je Sprache und kennt den Code bereits in der Form, die + * das Feld zusichert ("deu", "eng", "fra"). Aus dem Code wurde bisher nur ein deutscher + * Titelzusatz; danach war er nicht mehr vorhanden. + * + *

Die deutsche Fassung bleibt leer - sie ist die uebliche Fassung des Senders, und ein Wert + * auf jedem Datensatz wuerde die Filmliste unnoetig aufblaehen. Das entspricht dem Titel, der + * fuer sie ebenfalls unveraendert bleibt. + * + *

Die Suffixe fuer Audiodeskription und Gebaerdensprache werden abgeschnitten: sie benennen + * die Art der Tonspur, nicht ihre Sprache. + */ + private static void setAudioLanguage(final String aLanguage, final Film aFilm) { + final String languageCode = aLanguage.split("-")[0]; + if (ZdfConstants.LANGUAGE_GERMAN.equals(languageCode)) { + return; + } + // Das Feld sichert den dreistelligen ISO-639-2/T-Code zu. Die Tracks des ZDF sind bisher + // ausnahmslos so ausgezeichnet ("deu", "eng"); sollte doch einmal etwas anderes kommen, bleibt + // das Feld lieber leer, als einen Wert zu fuehren, auf den sich Clients nicht verlassen + // koennen. + if (languageCode.length() != 3 || !languageCode.chars().allMatch(Character::isLetter)) { + return; + } + aFilm.setAudioLanguage(languageCode); + } + private static void updateTitle(final String aLanguage, final Film aFilm) { String title = aFilm.getTitel(); switch (aLanguage) { diff --git a/src/test/java/de/mediathekview/mserver/crawler/zdf/tasks/ZdfFilmDetailTaskMultipleLanguagesTest.java b/src/test/java/de/mediathekview/mserver/crawler/zdf/tasks/ZdfFilmDetailTaskMultipleLanguagesTest.java index 7e37f4601..e09d4c8e8 100644 --- a/src/test/java/de/mediathekview/mserver/crawler/zdf/tasks/ZdfFilmDetailTaskMultipleLanguagesTest.java +++ b/src/test/java/de/mediathekview/mserver/crawler/zdf/tasks/ZdfFilmDetailTaskMultipleLanguagesTest.java @@ -13,10 +13,69 @@ import java.util.concurrent.ConcurrentLinkedQueue; import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; class ZdfFilmDetailTaskMultipleLanguagesTest extends ZdfTaskTestBase { + @Test + void audioLanguageIsOnlySetForAThreeLetterCode() throws Exception { + // Das Feld sichert den dreistelligen ISO-639-2/T-Code zu. Die Tracks des ZDF sind bisher + // ausnahmslos so ausgezeichnet - kaeme doch einmal ein zweistelliger Code, darf er nicht + // ungeprueft in der Filmliste landen. + final java.lang.reflect.Method setAudioLanguage = + ZdfFilmDetailTask.class.getDeclaredMethod("setAudioLanguage", String.class, Film.class); + setAudioLanguage.setAccessible(true); + + assertThat(audioLanguageFor(setAudioLanguage, "eng"), equalTo("eng")); + assertThat(audioLanguageFor(setAudioLanguage, "fra"), equalTo("fra")); + // Deutsch ist die uebliche Fassung und bleibt leer. + assertThat(audioLanguageFor(setAudioLanguage, "deu"), nullValue()); + // Die Art der Tonspur ist keine Sprache - Suffix wird abgeschnitten. + assertThat(audioLanguageFor(setAudioLanguage, "deu-ad"), nullValue()); + assertThat(audioLanguageFor(setAudioLanguage, "eng-ad"), equalTo("eng")); + // Alles, was kein dreistelliger Buchstabencode ist, ergibt nichts. + assertThat(audioLanguageFor(setAudioLanguage, "de"), nullValue()); + assertThat(audioLanguageFor(setAudioLanguage, "en"), nullValue()); + assertThat(audioLanguageFor(setAudioLanguage, "123"), nullValue()); + } + + private static String audioLanguageFor( + final java.lang.reflect.Method setAudioLanguage, final String language) throws Exception { + final Film film = + new Film( + UUID.randomUUID(), + Sender.ZDF, + "Titel", + "Thema", + LocalDateTime.of(2026, 1, 1, 20, 15), + Duration.ofMinutes(90)); + setAudioLanguage.invoke(null, language, film); + return film.getAudioLanguage(); + } + + @Test + void audioLanguageIsKeptForTheNonGermanVersion() { + // Das ZDF fuehrt die Downloads je Sprache und kennt den Code bereits als "eng". Bisher wurde + // daraus nur der Titelzusatz "(Englisch)"; der Code selbst war danach nicht mehr vorhanden. + final String filmUrl = "/content/documents/zdf/serien/hardsun/hard-sun-1-100.json"; + final String videoUrl = "/tmd/2/android_native_5/vod/ptmd/mediathek/180416_2215_sendung_hsn"; + setupSuccessfulJsonResponse(filmUrl, "/zdf/zdf_film_details_english.json"); + setupSuccessfulJsonResponse(videoUrl, "/zdf/zdf_video_details_english.json"); + setupHeadResponse(404); + + final Set actual = executeTask(filmUrl); + + final Film german = + actual.stream().filter(f -> !f.getTitel().contains("(Englisch)")).findFirst().orElseThrow(); + final Film english = + actual.stream().filter(f -> f.getTitel().contains("(Englisch)")).findFirst().orElseThrow(); + + assertThat(english.getAudioLanguage(), equalTo("eng")); + // Die deutsche Fassung bleibt leer - wie ihr Titel, der ebenfalls keinen Zusatz bekommt. + assertThat(german.getAudioLanguage(), nullValue()); + } + @Test void testGermanAndEnglish() {