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 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/"))