Repository: streamlink/streamlink
Pull request #139 (2016-11-07 15:05:00)

plugins.adultswim: added adultswim.com plugin
As requested by @stepshal in #126.

Only the live streams (http://www.adultswim.com/videos/streams/) are supported, the VOD streams appear to be encrypted HDS streams. 

codecov-io: ## [Current coverage](https://codecov.io/gh/streamlink/streamlink/pull/139?src=pr) is 33.81% (diff: 54.54%)
> Merging [#139](https://codecov.io/gh/streamlink/streamlink/pull/139?src=pr) into [master](https://codecov.io/gh/streamlink/streamlink/branch/master?src=pr) will increase coverage by **0.12%**

```diff
@@             master       #139   diff @@
==========================================
  Files            40         41     +1   
  Lines          5349       5382    +33   
  Methods           0          0          
  Messages          0          0          
  Branches          0          0          
==========================================
+ Hits           1802       1820    +18   
- Misses         3547       3562    +15   
  Partials          0          0          
```

> Powered by [Codecov](https://codecov.io?src=pr). Last update [ec64ee2...e6ca93e](https://codecov.io/gh/streamlink/streamlink/compare/ec64ee2a142535054d2256e26d35655c911559ed...e6ca93eaf1c92178ce90aec107455e5d2be84d7a?src=pr)
gravyboat: Good stuff, thanks!

Patch:
diff --git a/docs/plugin_matrix.rst b/docs/plugin_matrix.rst
index d90da1ff..4cd211c0 100644
--- a/docs/plugin_matrix.rst
+++ b/docs/plugin_matrix.rst
@@ -12,6 +12,7 @@ is limited.
 =================== ==================== ===== ===== ===========================
 Name                URL(s)               Live  VOD   Notes
 =================== ==================== ===== ===== ===========================
+adultswim           adultswim.com        Yes   No    Streams may be geo-restricted, VOD streams are encrypted
 afreeca             afreecatv.com        Yes   No
 afreecatv           afreeca.tv           Yes   No
 aftonbladet         aftonbladet.se       Yes   Yes
diff --git a/src/streamlink/plugins/adultswim.py b/src/streamlink/plugins/adultswim.py
new file mode 100644
index 00000000..abe15b82
--- /dev/null
+++ b/src/streamlink/plugins/adultswim.py
@@ -0,0 +1,74 @@
+#!/usr/bin/env python
+import re
+from pprint import pprint
+
+from streamlink.plugin import Plugin
+from streamlink.plugin.api import http, validate
+from streamlink.stream import HLSStream
+from streamlink.utils import parse_json
+
+
+class AdultSwim(Plugin):
+    _user_agent = (
+        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) "
+        "Chrome/43.0.2357.65 Safari/537.36")
+    API_URL = "http://www.adultswim.com/videos/api/v2/videos/{id}?fields=stream"
+
+    _url_re = re.compile(r"http://www\.adultswim\.com/videos/streams/(.*)")
+    _stream_data_re = re.compile(r".*AS_INITIAL_DATA = (\{.*?});.*", re.M | re.DOTALL)
+
+    _page_data_schema = validate.Schema({
+        u"streams": {
+            validate.text: {
+                u"stream": validate.text
+            }
+        }
+    })
+
+    _api_schema = validate.Schema({
+        u'status': u'ok',
+        u'data': {
+            u'stream': {
+                u'assets': [
+                    {
+                        u'url': validate.url()
+                    }
+                ]
+            }
+        }
+    })
+
+    @classmethod
+    def can_handle_url(cls, url):
+        match = AdultSwim._url_re.match(url)
+        return match is not None
+
+    def _get_streams(self):
+        # get the page
+        res = http.get(self.url, headers={"User-Agent": self._user_agent})
+        # find the big blob of stream info in the page
+        stream_data = self._stream_data_re.match(res.text)
+        stream_name = AdultSwim._url_re.match(self.url).group(1) or "live-stream"
+
+        if stream_data:
+            # parse the stream info as json
+            stream_info = parse_json(stream_data.group(1), schema=self._page_data_schema)
+            # get the stream ID
+            stream_id = stream_info[u"streams"][stream_name][u"stream"]
+
+            if stream_id:
+                api_url = self.API_URL.format(id=stream_id)
+
+                res = http.get(api_url, headers={"User-Agent": self._user_agent})
+                stream_data = http.json(res, schema=self._api_schema)
+
+                for asset in stream_data[u'data'][u'stream'][u'assets']:
+                    for n, s in HLSStream.parse_variant_playlist(self.session, asset[u"url"]).items():
+                        yield n, s
+
+            else:
+                self.logger.error("Couldn't find the stream ID for this stream: {}".format(stream_name))
+        else:
+            self.logger.error("Couldn't find the stream data for this stream: {}".format(stream_name))
+
+__plugin__ = AdultSwim


Test patch:
diff --git a/tests/test_plugin_adultswim.py b/tests/test_plugin_adultswim.py
new file mode 100644
index 00000000..2fba0fa2
--- /dev/null
+++ b/tests/test_plugin_adultswim.py
@@ -0,0 +1,16 @@
+import unittest
+
+from streamlink.plugins.adultswim import AdultSwim
+
+
+class TestPluginAdultSwim(unittest.TestCase):
+    def test_can_handle_url(self):
+        # should match
+        self.assertTrue(AdultSwim.can_handle_url("http://www.adultswim.com/videos/streams/toonami"))
+        self.assertTrue(AdultSwim.can_handle_url("http://www.adultswim.com/videos/streams/"))
+        self.assertTrue(AdultSwim.can_handle_url("http://www.adultswim.com/videos/streams/last-stream-on-the-left"))
+
+        # shouldn't match
+        self.assertFalse(AdultSwim.can_handle_url("http://www.adultswim.com/videos/specials/the-adult-swim-golf-classic-extended/"))
+        self.assertFalse(AdultSwim.can_handle_url("http://www.tvcatchup.com/"))
+        self.assertFalse(AdultSwim.can_handle_url("http://www.youtube.com/"))