Repository: streamlink/streamlink

Issue:
ZDF Mediathek-Plugin not working anymore
The ZDF Mediathek got a relaunch with a change of url structure and a new API. The plugin needs to be adapated to meet the changed platform.

As I am an avid user of this plugin myself, I will definitely take a look into it, but it might take awhile as I never wrote one before.

Is the original developer of this plugin present by chance?


Discussion:
> Is the original developer of this plugin present by chance?

https://github.com/streamlink/streamlink/blame/master/src/streamlink/plugins/zdf_mediathek.py
@skulblakka I hope you don't mind me pinging you here 😄 

Oh, could have looked that up myself - sorry. Didnt take a look at the existing plugin yet, cause i reckon you can't reuse much of it.

@bastimeyer I don't mind ;)

I basically know how to get the streams and everything however I can't wrap my head around the validation stuff @chrippa  implemented into the plugins (that wasn't there when I made the original ZDF plugin).


Resolving patch:
diff --git a/src/streamlink/plugins/zdf_mediathek.py b/src/streamlink/plugins/zdf_mediathek.py
--- a/src/streamlink/plugins/zdf_mediathek.py
+++ b/src/streamlink/plugins/zdf_mediathek.py
@@ -2,9 +2,11 @@
 
 from streamlink.plugin import Plugin
 from streamlink.plugin.api import http, validate
-from streamlink.stream import HDSStream, HLSStream, RTMPStream
+from streamlink.stream import HDSStream, HLSStream
+from streamlink.plugin.api.utils import parse_query
+
+API_URL = "https://api.zdf.de"
 
-API_URL = "http://www.zdf.de/ZDFmediathek/xmlservice/web/beitragsDetails"
 QUALITY_WEIGHTS = {
     "hd": 720,
     "veryhigh": 480,
@@ -12,6 +14,7 @@
     "med": 176,
     "low": 112
 }
+
 STREAMING_TYPES = {
     "h264_aac_f4f_http_f4m_http": (
         "HDS", HDSStream.parse_manifest
@@ -22,25 +25,43 @@
 }
 
 _url_re = re.compile("""
-    http(s)?://(\w+\.)?zdf.de/zdfmediathek(\#)?/.+
-    /(live|video)
-    /(?P<video_id>\d+)
+    http(s)?://(\w+\.)?zdf.de/
 """, re.VERBOSE | re.IGNORECASE)
 
-_schema = validate.Schema(
-    validate.xml_findall("video/formitaeten/formitaet"),
-    [
-        validate.union({
-            "type": validate.get("basetype"),
-            "quality": validate.xml_findtext("quality"),
-            "url": validate.all(
-                validate.xml_findtext("url"),
-                validate.url()
-            )
-        })
-    ]
+_documents_schema = validate.Schema(
+    {
+        "mainVideoContent": {
+            "http://zdf.de/rels/target": {
+                "http://zdf.de/rels/streams/ptmd": validate.text
+            },
+        },
+    }
 )
 
+_schema = validate.Schema(
+    {
+        "priorityList": [
+            {
+                "formitaeten": [
+                    {
+                        "type": validate.text,
+                        "qualities": [
+                            {
+                                "audio": {
+                                    "tracks": [
+                                        {
+                                            "uri": validate.text
+                                        }
+                                    ]
+                                }
+                            }
+                        ]
+                    }
+                ]
+            }
+        ]
+    }
+)
 
 class zdf_mediathek(Plugin):
     @classmethod
@@ -55,40 +76,35 @@ def stream_weight(cls, key):
 
         return Plugin.stream_weight(key)
 
-    def _create_rtmp_stream(self, url):
-        return RTMPStream(self.session, {
-            "rtmp": self._get_meta_url(url),
-            "pageUrl": self.url,
-        })
-
-    def _get_meta_url(self, url):
-        res = http.get(url, exception=IOError)
-        root = http.xml(res, exception=IOError)
-        return root.findtext("default-stream-url")
-
     def _get_streams(self):
         match = _url_re.match(self.url)
-        video_id = match.group("video_id")
-        res = http.get(API_URL, params=dict(ak="web", id=video_id))
-        fmts = http.xml(res, schema=_schema)
+        title = self.url.rsplit('/', 1)[-1]
+        if title.endswith(".html"):
+            title = title[:-5]
+
+        request_url = "https://api.zdf.de/content/documents/%s.json?profile=player" % title
+        res = http.get(request_url, headers={"Api-Auth" : "Bearer d2726b6c8c655e42b68b0db26131b15b22bd1a32"})
+        document = http.json(res, schema=_documents_schema)
+
+        stream_request_url = document["mainVideoContent"]["http://zdf.de/rels/target"]["http://zdf.de/rels/streams/ptmd"]
+        stream_request_url = API_URL + stream_request_url
+
+        res = http.get(stream_request_url)
+        res = http.json(res, schema=_schema)
+        formatList = res["priorityList"]["formitaeten"]
 
         streams = {}
-        for fmt in fmts:
-            if fmt["type"] in STREAMING_TYPES:
-                name, parser = STREAMING_TYPES[fmt["type"]]
-                try:
-                    streams.update(parser(self.session, fmt["url"]))
-                except IOError as err:
-                    self.logger.error("Failed to extract {0} streams: {1}",
-                                      name, err)
-
-            elif fmt["type"] == "h264_aac_mp4_rtmp_zdfmeta_http":
-                name = fmt["quality"]
-                try:
-                    streams[name] = self._create_rtmp_stream(fmt["url"])
-                except IOError as err:
-                    self.logger.error("Failed to extract RTMP stream '{0}': {1}",
-                                      name, err)
+        for format_ in formatList:
+            if format_["type"] in STREAMING_TYPES:
+                name, parser = STREAMING_TYPES[format_["type"]]
+                for quality in format_["qualities"]:
+                    tracks = quality["audio"]["tracks"]
+                    for track in tracks:
+                        try:
+                            streams.update(parser(self.session, track["uri"]))
+                        except IOError as err:
+                            self.logger.error("Failed to extract {0} streams: {1}",
+                                            name, err)
 
         return streams
 


Test patch: