Repository: streamlink/streamlink
Pull request #390 (2017-01-09 15:24:03)

plugins.tv8cat: added support for tv8.cat live stream
Adds support for http://tv8.cat/directe 

codecov-io: ## [Current coverage](https://codecov.io/gh/streamlink/streamlink/pull/390?src=pr) is 48.42% (diff: 47.16%)
> Merging [#390](https://codecov.io/gh/streamlink/streamlink/pull/390?src=pr) into [master](https://codecov.io/gh/streamlink/streamlink/branch/master?src=pr) will decrease coverage by **0.01%**

```diff
@@             master       #390   diff @@
==========================================
  Files            48         49     +1   
  Lines          4428       4481    +53   
  Methods           0          0          
  Messages          0          0          
  Branches          0          0          
==========================================
+ Hits           2145       2170    +25   
- Misses         2283       2311    +28   
  Partials          0          0          
```

> Powered by [Codecov](https://codecov.io?src=pr). Last update [d45ec8f...535625b](https://codecov.io/gh/streamlink/streamlink/compare/d45ec8ff5e02ccb2c29ed2eb1fe1581cda81c94b...535625b292dc7a149a22adbcfecf72113f17b9da?src=pr)
gravyboat: Thanks @beardypig! 

Patch:
diff --git a/docs/plugin_matrix.rst b/docs/plugin_matrix.rst
index fd3c8d39..846f39e2 100644
--- a/docs/plugin_matrix.rst
+++ b/docs/plugin_matrix.rst
@@ -137,6 +137,7 @@ tv4play             - tv4play.se         Yes   Yes   Streams may be geo-restrict
                                                      Only non-premium streams currently supported.
                     - fotbollskanalen.se
 tv8                 tv8.com.tr           Yes   No
+tv8cat              tv8.cat              Yes   No    Streams may be geo-restricted to Spain/Catalunya.
 tv360               tv360.com.tr         Yes   No
 tvcatchup           - tvcatchup.com      Yes   No    Streams may be geo-restricted to Great Britain.
 tvplayer            tvplayer.com         Yes   No    Streams may be geo-restricted to Great Britain. Premium streams are not supported.
diff --git a/src/streamlink/plugins/tv8cat.py b/src/streamlink/plugins/tv8cat.py
new file mode 100644
index 00000000..7c1acf83
--- /dev/null
+++ b/src/streamlink/plugins/tv8cat.py
@@ -0,0 +1,79 @@
+from __future__ import print_function
+import re
+
+from streamlink.plugin import Plugin
+from streamlink.plugin.api import http
+from streamlink.compat import urlparse, parse_qsl
+from streamlink.plugin.api import useragents
+from streamlink.plugin.api import validate
+from streamlink.stream import HLSStream
+from streamlink.stream import RTMPStream
+from streamlink.utils import parse_json
+
+
+class TV8cat(Plugin):
+    url_re = re.compile(r"https?://(?:www\.)?tv8\.cat/directe/?")
+    live_iframe = "http://www.8tv.cat/wp-content/themes/8tv/_/inc/_live_html.php"
+    iframe_re = re.compile(r'iframe .*?src="((?:https?)?//[^"]*?)"')
+    account_id_re = re.compile(r"accountId:\"(\d+?)\"")
+    policy_key_re = re.compile(r"policyKey:\"(.+?)\"")
+    britecove = "https://edge.api.brightcove.com/playback/v1/accounts/{account_id}/videos/{video_id}"
+    britecove_schema = validate.Schema({
+        "sources": [
+            {"height": int,
+             validate.optional("src"): validate.url(),
+             validate.optional("app_name"): validate.url(scheme="rtmp"),
+             validate.optional("stream_name"): validate.text}
+        ]
+    })
+
+    @classmethod
+    def can_handle_url(cls, url):
+        return cls.url_re.match(url) is not None
+
+    def _find_iframe(self, res):
+        iframe = self.iframe_re.search(res.text)
+        url = iframe and iframe.group(1)
+        if url and url.startswith("//"):
+            p = urlparse(self.url)
+            url = "{0}:{1}".format(p.scheme, url)
+        return url
+
+    def _britecove_params(self, url):
+        res = http.get(url, headers={"User-Agent": useragents.FIREFOX,
+                                     "Referer": self.url})
+        acc = self.account_id_re.search(res.text)
+        pk = self.policy_key_re.search(res.text)
+
+        query = dict(parse_qsl(urlparse(url).query))
+        return {"video_id": query.get("videoId"),
+                "account_id": acc and acc.group(1),
+                "policy_key": pk and pk.group(1),
+                }
+
+    def _get_stream_data(self, **params):
+        api_url = self.britecove.format(**params)
+        res = http.get(api_url, headers={"Accept": "application/json;pk={policy_key}".format(**params)})
+        return parse_json(res.text, schema=self.britecove_schema)
+
+    def _get_streams(self):
+        res = http.get(self.live_iframe)
+        britecove_url = self._find_iframe(res)
+
+        if britecove_url:
+            self.logger.debug("Found britecove embed url: {0}", britecove_url)
+            params = self._britecove_params(britecove_url)
+            self.logger.debug("Got britecode params: {0}", params)
+            stream_info = self._get_stream_data(**params)
+            for source in stream_info.get("sources"):
+                if source.get("src"):
+                    for s in HLSStream.parse_variant_playlist(self.session, source.get("src")).items():
+                        yield s
+                else:
+                    q = "{0}p".format(source.get("height"))
+                    s = RTMPStream(self.session,
+                                   {"rtmp": source.get("app_name"),
+                                    "playpath": source.get("stream_name")})
+                    yield q, s
+
+__plugin__ = TV8cat


Test patch:
diff --git a/tests/test_plugin_tv8cat.py b/tests/test_plugin_tv8cat.py
new file mode 100644
index 00000000..6fc82153
--- /dev/null
+++ b/tests/test_plugin_tv8cat.py
@@ -0,0 +1,17 @@
+import unittest
+
+from streamlink.plugins.tv8cat import TV8cat
+
+
+class TestPluginTV8cat(unittest.TestCase):
+    def test_can_handle_url(self):
+        # should match
+        self.assertTrue(TV8cat.can_handle_url("http://tv8.cat/directe"))
+        self.assertTrue(TV8cat.can_handle_url("http://www.tv8.cat/directe"))
+        self.assertTrue(TV8cat.can_handle_url("http://www.tv8.cat/directe/"))
+        self.assertTrue(TV8cat.can_handle_url("https://tv8.cat/directe/"))
+
+        # shouldn't match
+        self.assertFalse(TV8cat.can_handle_url("http://www.tv8.cat/algo/"))
+        self.assertFalse(TV8cat.can_handle_url("http://www.tvcatchup.com/"))
+        self.assertFalse(TV8cat.can_handle_url("http://www.youtube.com/"))