Repository: streamlink/streamlink
Pull request #1919 (2018-07-10 13:18:57)

plugins.tv4play: Fix for updated website
VOD and Livestream fix

closes https://github.com/streamlink/streamlink/issues/1882

ping @Ordval

---

also support for `self.get_title()`


Patch:
diff --git a/src/streamlink/plugins/tv4play.py b/src/streamlink/plugins/tv4play.py
index c6bed2f5..7e131227 100644
--- a/src/streamlink/plugins/tv4play.py
+++ b/src/streamlink/plugins/tv4play.py
@@ -1,73 +1,95 @@
-"""Plugin for TV4 Play, swedish TV channel TV4's streaming service."""
-
+import logging
 import re
 
-from streamlink.compat import urlparse
+from streamlink.compat import urljoin
+from streamlink.exceptions import PluginError
 from streamlink.plugin import Plugin
 from streamlink.plugin.api import http, validate
-from streamlink.stream import HDSStream, RTMPStream
+from streamlink.stream import HLSStream
 
-ASSET_URL = "http://prima.tv4play.se/api/web/asset/{0}/play"
-SWF_URL = "http://www.tv4play.se/flash/tv4video.swf"
+log = logging.getLogger(__name__)
 
-_url_re = re.compile(r"""
-    http(s)?://(www\.)?
-    (?:
-        tv4play.se/program/[^\?/]+|
-        fotbollskanalen.se/video
-    )
-    .+(video_id|videoid)=(?P<video_id>\d+)
-""", re.VERBOSE)
-
-_asset_schema = validate.Schema(
-    validate.xml_findall("items/item"),
-    [
-        validate.all(
-            validate.xml_findall("*"),
-            validate.map(lambda e: (e.tag, e.text)),
-            validate.transform(dict),
-            {
-                "base": validate.text,
-                "bitrate": validate.all(
-                    validate.text, validate.transform(int)
-                ),
-                "url": validate.text
-            }
+
+class TV4Play(Plugin):
+    """Plugin for TV4 Play, swedish TV channel TV4's streaming service."""
+
+    title = None
+    video_id = None
+
+    api_url = "https://playback-api.b17g.net"
+    api_assets = urljoin(api_url, "/asset/{0}")
+
+    _url_re = re.compile(r"""
+        https?://(?:www\.)?
+        (?:
+            tv4play.se/program/[^\?/]+
+            |
+            fotbollskanalen.se/video
         )
-    ]
-)
+        /(?P<video_id>\d+)
+    """, re.VERBOSE)
 
+    _meta_schema = validate.Schema(
+        {
+            "metadata": {
+                "title": validate.text
+            },
+            "mediaUri": validate.text
+        }
+    )
 
-class TV4Play(Plugin):
     @classmethod
     def can_handle_url(cls, url):
-        return _url_re.match(url)
+        return cls._url_re.match(url) is not None
+
+    @property
+    def get_video_id(self):
+        if self.video_id is None:
+            match = self._url_re.match(self.url)
+            self.video_id = match.group("video_id")
+            log.debug("Found video ID: {0}".format(self.video_id))
+        return self.video_id
+
+    def get_metadata(self):
+        params = {
+            "device": "browser",
+            "protocol": "hls",
+            "service": "tv4",
+        }
+        try:
+            res = http.get(self.api_assets.format(self.get_video_id),
+                           params=params)
+        except Exception as e:
+            if "404 Client Error" in str(e):
+                raise PluginError("This Video is not available")
+            raise e
+        log.debug("Found metadata")
+        metadata = http.json(res, schema=self._meta_schema)
+        self.title = metadata["metadata"]["title"]
+        return metadata
+
+    def get_title(self):
+        if self.title is None:
+            self.get_metadata()
+        return self.title
 
     def _get_streams(self):
-        match = _url_re.match(self.url)
-        video_id = match.group("video_id")
-        res = http.get(ASSET_URL.format(video_id))
-        assets = http.xml(res, schema=_asset_schema)
-
-        streams = {}
-        for asset in assets:
-            base = asset["base"]
-            url = asset["url"]
-
-            if urlparse(url).path.endswith(".f4m"):
-                streams.update(
-                    HDSStream.parse_manifest(self.session, url, pvswf=SWF_URL)
-                )
-            elif base.startswith("rtmp"):
-                name = "{0}k".format(asset["bitrate"])
-                params = {
-                    "rtmp": asset["base"],
-                    "playpath": url,
-                    "live": True
-                }
-                streams[name] = RTMPStream(self.session, params)
-
-        return streams
+        metadata = self.get_metadata()
+
+        try:
+            res = http.get(urljoin(self.api_url, metadata["mediaUri"]))
+        except Exception as e:
+            if "401 Client Error" in str(e):
+                raise PluginError("This Video is not available in your country")
+            raise e
+
+        log.debug("Found stream data")
+        data = http.json(res)
+        hls_url = data["playbackItem"]["manifestUrl"]
+        log.debug("URL={0}".format(hls_url))
+        for s in HLSStream.parse_variant_playlist(self.session,
+                                                  hls_url).items():
+            yield s
 
 
 __plugin__ = TV4Play


Test patch:
diff --git a/tests/plugins/test_tv4play.py b/tests/plugins/test_tv4play.py
index 934ca539..63979f41 100644
--- a/tests/plugins/test_tv4play.py
+++ b/tests/plugins/test_tv4play.py
@@ -5,14 +5,15 @@ from streamlink.plugins.tv4play import TV4Play
 
 class TestPluginTV4Play(unittest.TestCase):
 
-    ''' Plugin Broken
     def test_can_handle_url(self):
         should_match = [
             'https://www.tv4play.se/program/fridas-vm-resa/10000884',
+            'https://www.tv4play.se/program/monk/3938989',
+            'https://www.tv4play.se/program/nyheterna/10378590',
+            'https://www.fotbollskanalen.se/video/10395484/ghoddos-fullbordar-vandningen---ger-ofk-ledningen/',
         ]
         for url in should_match:
             self.assertTrue(TV4Play.can_handle_url(url))
-    '''
 
     def test_can_handle_url_negative(self):
         should_not_match = [