Repository: streamlink/streamlink
Pull request #2113 (2018-10-12 13:40:25)

plugins.youtube: Added support for embedded livestream urls
URL regex update.

use canonical url for embedded livestreams,
this was not added before because it is not needed for normal videos.

Ref https://github.com/streamlink/streamlink/pull/1888#discussion_r199504011

Added some more regex group tests.

---

Example where this url can be found

`https://www.aljazeera.com/live/` with `Having trouble watching?`

The youtube url is currently not used for Streamlink,
but might be useful for other future websites.


Patch:
diff --git a/src/streamlink/plugins/youtube.py b/src/streamlink/plugins/youtube.py
index 38e07368..b1081c13 100644
--- a/src/streamlink/plugins/youtube.py
+++ b/src/streamlink/plugins/youtube.py
@@ -7,7 +7,7 @@ import re
 from streamlink.compat import parse_qsl, is_py2
 from streamlink.plugin import Plugin, PluginError, PluginArguments, PluginArgument
 from streamlink.plugin.api import validate, useragents
-from streamlink.plugin.api.utils import parse_query
+from streamlink.plugin.api.utils import itertags, parse_query
 from streamlink.stream import HTTPStream, HLSStream
 from streamlink.stream.ffmpegmux import MuxedStream
 from streamlink.utils import parse_json, search_dict
@@ -89,23 +89,26 @@ _config_schema = validate.Schema(
 )
 
 _ytdata_re = re.compile(r'window\["ytInitialData"\]\s*=\s*({.*?});', re.DOTALL)
-_url_re = re.compile(r"""
-    http(s)?://(\w+\.)?youtube.com
+_url_re = re.compile(r"""(?x)https?://(?:\w+\.)?youtube\.com
     (?:
         (?:
-            /(watch.+v=|embed/|v/)
+            /(?:watch.+v=|embed/(?!live_stream)|v/)
             (?P<video_id>[0-9A-z_-]{11})
         )
         |
         (?:
-            /(user|channel)/(?P<user>[^/?]+)
+            /(?:
+                (?:user|channel)/
+                |
+                embed/live_stream\?channel=
+            )(?P<user>[^/?&]+)
         )
         |
         (?:
-            /(c/)?(?P<liveChannel>[^/?]+)/live
+            /(?:c/)?(?P<liveChannel>[^/?]+)/live/?$
         )
     )
-""", re.VERBOSE)
+""")
 
 
 class YouTube(Plugin):
@@ -270,6 +273,14 @@ class YouTube(Plugin):
                             log.debug("Video ID from videoRenderer (live)")
                             return x["videoId"]
 
+        if "/embed/live_stream" in url:
+            for link in itertags(res.text, "link"):
+                if link.attributes.get("rel") == "canonical":
+                    canon_link = link.attributes.get("href")
+                    if canon_link != url:
+                        log.debug("Re-directing to canonical URL: {0}".format(canon_link))
+                        return self._find_video_id(canon_link)
+
         raise PluginError("Could not find a video on this page")
 
     def _get_stream_info(self, video_id):


Test patch:
diff --git a/tests/plugins/test_youtube.py b/tests/plugins/test_youtube.py
index 4b1934d0..d6e1ab60 100644
--- a/tests/plugins/test_youtube.py
+++ b/tests/plugins/test_youtube.py
@@ -1,17 +1,19 @@
 import unittest
 
-from streamlink.plugins.youtube import YouTube
+from streamlink.plugins.youtube import YouTube, _url_re
 
 
 class TestPluginYouTube(unittest.TestCase):
     def test_can_handle_url(self):
         should_match = [
             "https://www.youtube.com/c/EXAMPLE/live",
+            "https://www.youtube.com/c/EXAMPLE/live/",
             "https://www.youtube.com/channel/EXAMPLE",
             "https://www.youtube.com/v/aqz-KE-bpKQ",
             "https://www.youtube.com/embed/aqz-KE-bpKQ",
             "https://www.youtube.com/user/EXAMPLE/",
             "https://www.youtube.com/watch?v=aqz-KE-bpKQ",
+            "https://www.youtube.com/embed/live_stream?channel=UCNye-wNBqNL5ZzHSJj3l8Bg",
         ]
         for url in should_match:
             self.assertTrue(YouTube.can_handle_url(url))
@@ -21,3 +23,44 @@ class TestPluginYouTube(unittest.TestCase):
         ]
         for url in should_not_match:
             self.assertFalse(YouTube.can_handle_url(url))
+
+    def _test_regex(self, url, expected_string, expected_group):
+        m = _url_re.match(url)
+        self.assertIsNotNone(m)
+        self.assertEqual(expected_string, m.group(expected_group))
+
+    def test_regex_liveChannel_c(self):
+        self._test_regex("https://www.youtube.com/c/EXAMPLE/live",
+                         "EXAMPLE", "liveChannel")
+
+    def test_regex_liveChannel_no_c(self):
+        self._test_regex("https://www.youtube.com/EXAMPLE1/live",
+                         "EXAMPLE1", "liveChannel")
+
+    def test_regex_user_channel(self):
+        self._test_regex("https://www.youtube.com/channel/EXAMPLE2",
+                         "EXAMPLE2", "user")
+
+    def test_regex_user_user(self):
+        self._test_regex("https://www.youtube.com/channel/EXAMPLE3",
+                         "EXAMPLE3", "user")
+
+    def test_regex_user_embed_list_stream(self):
+        self._test_regex("https://www.youtube.com/embed/live_stream?channel=UCNye-wNBqNL5ZzHSJj3l8Bg",
+                         "UCNye-wNBqNL5ZzHSJj3l8Bg", "user")
+
+    def test_regex_user_embed_list_stream_2(self):
+        self._test_regex("https://www.youtube.com/embed/live_stream?channel=UCNye-wNBqNL5ZzHSJj3l8Bg&autoplay=1&modestbranding=1&rel=0&showinfo=0&color=white&fs=1",
+                         "UCNye-wNBqNL5ZzHSJj3l8Bg", "user")
+
+    def test_regex_video_id_v(self):
+        self._test_regex("https://www.youtube.com/v/aqz-KE-bpKQ",
+                         "aqz-KE-bpKQ", "video_id")
+
+    def test_regex_video_id_embed(self):
+        self._test_regex("https://www.youtube.com/embed/aqz-KE-bpKQ",
+                         "aqz-KE-bpKQ", "video_id")
+
+    def test_regex_video_id_watch(self):
+        self._test_regex("https://www.youtube.com/watch?v=aqz-KE-bpKQ",
+                         "aqz-KE-bpKQ", "video_id")