Repository: streamlink/streamlink
Pull request #1569 (2018-03-24 14:00:23)

plugins.welt: add plugin
Plugin for Welt, a German news/docutainment TV channel, previously called N24.
Geo-blocked to Germany...


Patch:
diff --git a/docs/plugin_matrix.rst b/docs/plugin_matrix.rst
index ec982235..b5eec16a 100644
--- a/docs/plugin_matrix.rst
+++ b/docs/plugin_matrix.rst
@@ -252,6 +252,7 @@ vrtbe               vrt.be/vrtnu         Yes   Yes
 webcast_india_gov   webcast.gov.in       Yes   No    You can use #Channel to indicate CH number.
 webtv               web.tv               Yes   --
 weeb                weeb.tv              Yes   --    Requires rtmpdump with K-S-V patches.
+welt                welt.de              Yes   Yes   Streams may be geo-restricted to Germany.
 wwenetwork          network.wwe.com      Yes   Yes   Requires an account to access any content.
 younow              younow.com           Yes   --
 youtube             - youtube.com        Yes   Yes   Protected videos are not supported.
diff --git a/src/streamlink/plugins/welt.py b/src/streamlink/plugins/welt.py
new file mode 100644
index 00000000..bdc0a90b
--- /dev/null
+++ b/src/streamlink/plugins/welt.py
@@ -0,0 +1,85 @@
+import re
+
+from streamlink.compat import quote
+from streamlink.plugin import Plugin
+from streamlink.plugin.api import http, useragents, validate
+from streamlink.stream import HLSStream
+from streamlink.utils import parse_json
+
+
+def _filter_url(url):
+    try:
+        Welt._schema_url.validate(url)
+        return True
+    except:
+        return False
+
+
+class Welt(Plugin):
+    _url_vod = "https://www.welt.de/onward/video/token/{0}"
+    _re_url = re.compile(
+        r"""https?://(\w+\.)?welt\.de/?""",
+        re.IGNORECASE
+    )
+    _re_url_vod = re.compile(
+        r"""mediathek""",
+        re.IGNORECASE
+    )
+    _re_json = re.compile(
+        r"""
+            <script>\s*
+            var\s+funkotron\s*=\s*
+            \{\s*
+                config\s*:\s*(?P<json>\{.+?\})\s*
+            \}\s*;?\s*
+            </script>
+        """,
+        re.VERBOSE | re.DOTALL | re.IGNORECASE
+    )
+    _schema = validate.Schema(
+        validate.transform(_re_json.search),
+        validate.get("json"),
+        validate.transform(parse_json),
+        validate.get("page"),
+        validate.get("content"),
+        validate.get("media"),
+        validate.get(0),
+        validate.get("sources"),
+        validate.map(lambda obj: obj["file"]),
+        validate.filter(_filter_url),
+        validate.get(0)
+    )
+    _schema_url = validate.Schema(
+        validate.url(
+            scheme="https",
+            path=validate.endswith(".m3u8")
+        )
+    )
+    _schema_vod = validate.Schema(
+        validate.transform(parse_json),
+        validate.get("urlWithToken"),
+        _schema_url
+    )
+
+    @classmethod
+    def can_handle_url(cls, url):
+        return cls._re_url.match(url) is not None
+
+    def __init__(self, url):
+        Plugin.__init__(self, url)
+        self.url = url
+        self.isVod = self._re_url_vod.search(url) is not None
+
+    def _get_streams(self):
+        headers = {"User-Agent": useragents.CHROME}
+        hls_url = http.get(self.url, headers=headers, schema=self._schema)
+        headers["Referer"] = self.url
+
+        if self.isVod:
+            url = self._url_vod.format(quote(hls_url, safe=""))
+            hls_url = http.get(url, headers=headers, schema=self._schema_vod)
+
+        return HLSStream.parse_variant_playlist(self.session, hls_url, headers=headers)
+
+
+__plugin__ = Welt


Test patch:
diff --git a/tests/test_plugin_welt.py b/tests/test_plugin_welt.py
new file mode 100644
index 00000000..979872d4
--- /dev/null
+++ b/tests/test_plugin_welt.py
@@ -0,0 +1,69 @@
+import unittest
+
+from streamlink.plugins.welt import Welt
+
+
+class TestPluginWelt(unittest.TestCase):
+    def test_can_handle_url(self):
+        # should match
+        self.assertTrue(Welt.can_handle_url("http://welt.de"))
+        self.assertTrue(Welt.can_handle_url("http://welt.de/"))
+        self.assertTrue(Welt.can_handle_url("http://welt.de/tv-programm-live-stream/"))
+        self.assertTrue(Welt.can_handle_url("http://www.welt.de"))
+        self.assertTrue(Welt.can_handle_url("http://www.welt.de/"))
+        self.assertTrue(Welt.can_handle_url("http://www.welt.de/tv-programm-live-stream/"))
+        self.assertTrue(Welt.can_handle_url("https://welt.de"))
+        self.assertTrue(Welt.can_handle_url("https://welt.de/"))
+        self.assertTrue(Welt.can_handle_url("https://welt.de/tv-programm-live-stream/"))
+        self.assertTrue(Welt.can_handle_url("https://www.welt.de"))
+        self.assertTrue(Welt.can_handle_url("https://www.welt.de/"))
+        self.assertTrue(Welt.can_handle_url("https://www.welt.de/tv-programm-live-stream/"))
+
+        # shouldn't match
+        self.assertFalse(Welt.can_handle_url("http://www.youtube.com/"))
+        self.assertFalse(Welt.can_handle_url("http://youtube.com/"))
+
+    def test_validate_live(self):
+        hls_url = Welt._schema.validate("""
+            foo
+            <script>
+            var funkotron = { config: {
+                "page": {
+                    "content": {
+                        "media": [
+                            {
+                                "sources": [
+                                    {
+                                        "file": "https://foo.bar/baz.mp4?qux"
+                                    },
+                                    {
+                                        "file": "https://foo.bar/baz.m3u8?qux"
+                                    }
+                                ]
+                            },
+                            {
+                                "sources": [
+                                    {
+                                        "file": "https://qux.baz/bar.mp4?foo"
+                                    },
+                                    {
+                                        "file": "https://qux.baz/bar.m3u8?foo"
+                                    }
+                                ]
+                            }
+                        ]
+                    }
+                }
+            }}
+            </script>
+            bar
+        """)
+        self.assertEqual(hls_url, "https://foo.bar/baz.m3u8?qux", "Finds the correct HLS live URL")
+
+    def test_validate_vod(self):
+        hls_url = Welt._schema_vod.validate("""
+            {
+                "urlWithToken": "https://foo.bar/baz.m3u8?qux"
+            }
+        """)
+        self.assertEqual(hls_url, "https://foo.bar/baz.m3u8?qux", "Finds the correct HLS VOD URL")