Repository: streamlink/streamlink
Pull request #3251 (2020-10-15 14:25:06)

plugins.cbsnews: support for live streams from CBS News
Support for live streams from CBS News.

Including:
- https://www.cbsnews.com/live/
- https://www.cbsnews.com/live/cbsn-local-bay-area/
- https://www.cbsnews.com/live/cbsn-local-ny/


Patch:
diff --git a/docs/plugin_matrix.rst b/docs/plugin_matrix.rst
index 77083b18..7af8e466 100644
--- a/docs/plugin_matrix.rst
+++ b/docs/plugin_matrix.rst
@@ -38,6 +38,7 @@ brightcove              players.brig... [6]_ Yes   Yes
 btsports                sport.bt.com         Yes   Yes   Requires subscription account
 btv                     btvplus.bg           Yes   No    Streams are geo-restricted to Bulgaria.
 canalplus               mycanal.fr           No    Yes   Streams may be geo-restricted to France.
+cbsnews                 cbsnews.com          Yes   No
 cdnbg                   - armymedia.bg       Yes   No    Streams may be geo-restricted to Bulgaria
                         - bgonair.bg
                         - bloombergtv.bg
diff --git a/src/streamlink/plugins/cbsnews.py b/src/streamlink/plugins/cbsnews.py
new file mode 100644
index 00000000..4e2bb8c8
--- /dev/null
+++ b/src/streamlink/plugins/cbsnews.py
@@ -0,0 +1,38 @@
+import re
+
+from streamlink.plugin import Plugin
+from streamlink.plugin.api import validate
+from streamlink.stream import HLSStream
+from streamlink.utils import parse_json
+
+
+class CBSNews(Plugin):
+    url_re = re.compile(r"https://www\.cbsnews\.com/live/")
+    _re_default_payload = re.compile(r"CBSNEWS.defaultPayload = (\{.*)")
+
+    _schema_items = validate.Schema(
+        validate.transform(_re_default_payload.search),
+        validate.any(None, validate.all(
+            validate.get(1),
+            validate.transform(parse_json),
+            {"items": [validate.all({
+                "video": validate.url(),
+                "format": "application/x-mpegURL"
+            }, validate.get("video"))]},
+            validate.get("items")
+        ))
+    )
+
+    @classmethod
+    def can_handle_url(cls, url):
+        return cls.url_re.match(url) is not None
+
+    def _get_streams(self):
+        items = self.session.http.get(self.url, schema=self._schema_items)
+        if items:
+            for hls_url in items:
+                for s in HLSStream.parse_variant_playlist(self.session, hls_url).items():
+                    yield s
+
+
+__plugin__ = CBSNews


Test patch:
diff --git a/tests/plugins/test_cbsnews.py b/tests/plugins/test_cbsnews.py
new file mode 100644
index 00000000..addc4ac7
--- /dev/null
+++ b/tests/plugins/test_cbsnews.py
@@ -0,0 +1,24 @@
+import unittest  # noqa: F401
+import pytest
+from streamlink.plugins.cbsnews import CBSNews
+
+
+class TestCBSNews:
+    valid_urls = [
+        ("https://www.cbsnews.com/live/cbs-sports-hq/",),
+        ("https://www.cbsnews.com/live/cbsn-local-bay-area/",),
+        ("https://www.cbsnews.com/live/",),
+    ]
+    invalid_urls = [
+        ("https://www.cbsnews.com/feature/election-2020/",),
+        ("https://www.cbsnews.com/48-hours/",),
+        ("https://twitch.tv/",)
+    ]
+
+    @pytest.mark.parametrize(["url"], valid_urls)
+    def test_can_handle_url(self, url):
+        assert CBSNews.can_handle_url(url), "url should be handled"
+
+    @pytest.mark.parametrize(["url"], invalid_urls)
+    def test_can_handle_url_negative(self, url):
+        assert not CBSNews.can_handle_url(url), "url should not be handled"