Repository: streamlink/streamlink
Pull request #144 (2016-11-09 09:25:07)

plugins.webtv: added a new plugin to support web.tv
A new plugin to support web.tv, as requested by @Rotarum in #142 

beardypig: Working on the tests... 
codecov-io: ## [Current coverage](https://codecov.io/gh/streamlink/streamlink/pull/144?src=pr) is 33.87% (diff: 42.85%)
> Merging [#144](https://codecov.io/gh/streamlink/streamlink/pull/144?src=pr) into [master](https://codecov.io/gh/streamlink/streamlink/branch/master?src=pr) will increase coverage by **0.05%**

```diff
@@             master       #144   diff @@
==========================================
  Files            41         42     +1   
  Lines          5382       5417    +35   
  Methods           0          0          
  Messages          0          0          
  Branches          0          0          
==========================================
+ Hits           1820       1835    +15   
- Misses         3562       3582    +20   
  Partials          0          0          
```

> Powered by [Codecov](https://codecov.io?src=pr). Last update [25706a6...99f9c8f](https://codecov.io/gh/streamlink/streamlink/compare/25706a6cf939807439948ba418f25ac566217c5d...99f9c8f220dc167af43a59d657c85bd5f2a670e6?src=pr)
gravyboat: Thanks!

Patch:
diff --git a/docs/plugin_matrix.rst b/docs/plugin_matrix.rst
index 4cd211c0..0e72d2af 100644
--- a/docs/plugin_matrix.rst
+++ b/docs/plugin_matrix.rst
@@ -123,6 +123,7 @@ viasat              - tv3play.se         Yes   Yes   Streams may be geo-restrict
                     - juicyplay.se
 vidio               vidio.com            Yes   Yes
 wattv               wat.tv               --    Yes
+webtv               web.tv               Yes   --
 weeb                weeb.tv              Yes   --    Requires rtmpdump with K-S-V patches.
 younow              younow.com           Yes   --
 youtube             - youtube.com        Yes   Yes   Protected videos are not supported.
diff --git a/src/streamlink/plugins/webtv.py b/src/streamlink/plugins/webtv.py
new file mode 100644
index 00000000..5cbae101
--- /dev/null
+++ b/src/streamlink/plugins/webtv.py
@@ -0,0 +1,58 @@
+import json
+import re
+
+from streamlink.plugin import Plugin
+from streamlink.plugin.api import http
+from streamlink.plugin.api import validate
+from streamlink.stream import HLSStream
+from streamlink.utils import parse_json
+
+
+class WebTV(Plugin):
+    _url_re = re.compile("http(?:s)?://(\w+)\.web.tv/?")
+    _sources_re = re.compile(r'"sources": (\[.*?\]),', re.DOTALL)
+    _sources_schema = validate.Schema([
+        {
+            u"src": validate.text,
+            u"type": validate.text,
+            u"label": validate.text
+        }
+    ])
+
+    @classmethod
+    def can_handle_url(cls, url):
+        return cls._url_re.match(url) is not None
+
+    def _get_streams(self):
+        """
+        Find the streams for web.tv
+        :return:
+        """
+        headers = {}
+        res = http.get(self.url, headers=headers)
+        headers["Referer"] = self.url
+
+        sources = self._sources_re.findall(res.text)
+        if len(sources):
+            sdata = parse_json(sources[0], schema=self._sources_schema)
+            for source in sdata:
+                self.logger.debug("Found stream of type: {}", source[u'type'])
+                if source[u'type'] == u"application/vnd.apple.mpegurl":
+                    # if the url has no protocol, assume it is http
+                    url = source[u"src"]
+                    if url.startswith("//"):
+                        url = "http:" + url
+
+                    try:
+                        # try to parse the stream as a variant playlist
+                        variant = HLSStream.parse_variant_playlist(self.session, url, headers=headers)
+                        if variant:
+                            for q, s in variant.items():
+                                yield q, s
+                        else:
+                            # and if that fails, try it as a plain HLS stream
+                            yield 'live', HLSStream(self.session, url, headers=headers)
+                    except IOError:
+                        self.logger.warning("Could not open the stream, perhaps the channel is offline")
+
+__plugin__ = WebTV


Test patch:
diff --git a/tests/test_plugin_webtv.py b/tests/test_plugin_webtv.py
new file mode 100644
index 00000000..088658bf
--- /dev/null
+++ b/tests/test_plugin_webtv.py
@@ -0,0 +1,24 @@
+import unittest
+
+from streamlink.plugins.webtv import WebTV
+
+
+class TestPluginWebTV(unittest.TestCase):
+    def test_can_handle_url(self):
+        # should match
+        self.assertTrue(WebTV.can_handle_url("http://planetmutfak.web.tv"))
+        self.assertTrue(WebTV.can_handle_url("http://telex.web.tv"))
+        self.assertTrue(WebTV.can_handle_url("http://nasamedia.web.tv"))
+        self.assertTrue(WebTV.can_handle_url("http://genctv.web.tv"))
+        self.assertTrue(WebTV.can_handle_url("http://etvmanisa.web.tv"))
+        self.assertTrue(WebTV.can_handle_url("http://startv.web.tv"))
+        self.assertTrue(WebTV.can_handle_url("http://akuntv.web.tv"))
+        self.assertTrue(WebTV.can_handle_url("http://telebarnn.web.tv"))
+        self.assertTrue(WebTV.can_handle_url("http://kanal48.web.tv"))
+        self.assertTrue(WebTV.can_handle_url("http://digi24tv.web.tv"))
+        self.assertTrue(WebTV.can_handle_url("http://french24.web.tv"))
+
+        # shouldn't match
+        self.assertFalse(WebTV.can_handle_url("http://www.youtube.com/"))
+        self.assertFalse(WebTV.can_handle_url("https://www.tvplayer.com/watch/itv"))
+        self.assertFalse(WebTV.can_handle_url("https://tvplayer.com/watch/itv"))