Repository: streamlink/streamlink

Issue:
plugins.ard_mediathek: rewrite plugin
Resolves #4191 

One issue I couldn't fix is the text encoding of the metadata which gets messed up by `validate.parse_html()`. See the VOD title down below...
https://github.com/streamlink/streamlink/blob/175d4748561c7154bb80c5a47dae22039e45d4ce/src/streamlink/utils/parse.py#L54-L55

Some VODs also have a second title, eg. if it's a TV show, but I couldn't be bothered to implement this. Not important.

----

Das Erste - Live:
```
$ streamlink -l debug --title '{author} - {title}' 'https://www.ardmediathek.de/daserste/live/Y3JpZDovL2Rhc2Vyc3RlLmRlL0xpdmVzdHJlYW0tRGFzRXJzdGU/' best
[cli.output][debug] Opening subprocess: mpv "--force-media-title=Das Erste - Das Erste" -
```

WDR - Live:
```
$ streamlink -l debug --title '{author} - {title}' 'https://www.ardmediathek.de/live/Y3JpZDovL3dkci5kZS9CZWl0cmFnLTNkYTY2NGRlLTE4YzItNDY1MC1hNGZmLTRmNjQxNDcyMDcyYg/' best
[cli.output][debug] Opening subprocess: mpv "--force-media-title=WDR - WDR Fernsehen im Livestream" -
```

VOD
```
$ streamlink -l debug --title '{author} - {title}' 'https://www.ardmediathek.de/video/dokus-im-ersten/wirecard-die-milliarden-luege/das-erste/Y3JpZDovL2Rhc2Vyc3RlLmRlL3JlcG9ydGFnZSBfIGRva3VtZW50YXRpb24gaW0gZXJzdGVuL2NlMjQ0OWM4LTQ4YTUtNGIyNC1iMTdlLWNhOTNjMDQ5OTc4Zg/' best
[cli.output][debug] Opening subprocess: mpv "--force-media-title=Das Erste - Wirecard - Die Milliarden-LÃ¼ge" -
```


Discussion:
I don't think the metadata is a big enough deal to be worth fixing.
@bastimeyer My bad for merging too quickly!

Resolving patch:
diff --git a/src/streamlink/plugins/ard_mediathek.py b/src/streamlink/plugins/ard_mediathek.py
--- a/src/streamlink/plugins/ard_mediathek.py
+++ b/src/streamlink/plugins/ard_mediathek.py
@@ -4,6 +4,7 @@
 from streamlink.plugin import Plugin, pluginmatcher
 from streamlink.plugin.api import validate
 from streamlink.stream.hls import HLSStream
+from streamlink.stream.http import HTTPStream
 
 
 log = logging.getLogger(__name__)
@@ -13,6 +14,14 @@
     r"https?://(?:(\w+\.)?ardmediathek\.de/|mediathek\.daserste\.de/)"
 ))
 class ARDMediathek(Plugin):
+    _QUALITY_MAP = {
+        4: "1080p",
+        3: "720p",
+        2: "540p",
+        1: "360p",
+        0: "270p"
+    }
+
     def _get_streams(self):
         data_json = self.session.http.get(self.url, schema=validate.Schema(
             validate.parse_html(),
@@ -34,42 +43,64 @@ def _get_streams(self):
                 [dict],
                 validate.filter(lambda item: item.get("mediaCollection")),
                 validate.get(0),
-                {
-                    "geoblocked": bool,
-                    "publicationService": {
-                        "name": str,
+                validate.any(None, validate.all(
+                    {
+                        "geoblocked": bool,
+                        "publicationService": {
+                            "name": str,
+                        },
+                        "show": validate.any(None, validate.all(
+                            {"title": str},
+                            validate.get("title")
+                        )),
+                        "title": str,
+                        "mediaCollection": {
+                            "embedded": {
+                                "_mediaArray": [validate.all(
+                                    {
+                                        "_mediaStreamArray": [validate.all(
+                                            {
+                                                "_quality": validate.any(str, int),
+                                                "_stream": validate.url(),
+                                            },
+                                            validate.union_get("_quality", "_stream")
+                                        )]
+                                    },
+                                    validate.get("_mediaStreamArray"),
+                                    validate.transform(dict)
+                                )]
+                            }
+                        },
                     },
-                    "title": str,
-                    "mediaCollection": {
-                        "embedded": {
-                            "_mediaArray": [{
-                                "_mediaStreamArray": [{
-                                    "_quality": validate.any(str, int),
-                                    "_stream": validate.url()
-                                }]
-                            }]
-                        }
-                    }
-                }
+                    validate.union_get(
+                        "geoblocked",
+                        ("mediaCollection", "embedded", "_mediaArray", 0),
+                        ("publicationService", "name"),
+                        "title",
+                        "show",
+                    )
+                ))
             )
         })
         data = schema_data.validate(data_json)
 
         log.debug(f"Found media id: {data['id']}")
-        data_media = data["widgets"]
+        if not data["widgets"]:
+            log.info("The content is unavailable")
+            return
 
-        if data_media["geoblocked"]:
+        geoblocked, media, self.author, self.title, show = data["widgets"]
+        if geoblocked:
             log.info("The content is not available in your region")
             return
+        if show:
+            self.title = f"{show}: {self.title}"
 
-        self.author = data_media["publicationService"]["name"]
-        self.title = data_media["title"]
-
-        for media in data_media["mediaCollection"]["embedded"]["_mediaArray"]:
-            for stream in media["_mediaStreamArray"]:
-                if stream["_quality"] != "auto" or ".m3u8" not in stream["_stream"]:
-                    continue
-                return HLSStream.parse_variant_playlist(self.session, stream["_stream"])
+        if media.get("auto"):
+            yield from HLSStream.parse_variant_playlist(self.session, media.get("auto")).items()
+        else:
+            for quality, stream in media.items():
+                yield self._QUALITY_MAP.get(quality, quality), HTTPStream(self.session, stream)
 
 
 __plugin__ = ARDMediathek


Test patch: