Repository: streamlink/streamlink

Issue:
plugins.youtube: 400 Client Error: Bad request for url: https://consent.youtube.com/s
### Checklist

- [X] This is a plugin issue and not a different kind of issue
- [X] [I have read the contribution guidelines](https://github.com/streamlink/streamlink/blob/master/CONTRIBUTING.md#contributing-to-streamlink)
- [X] [I have checked the list of open and recently closed plugin issues](https://github.com/streamlink/streamlink/issues?q=is%3Aissue+label%3A%22plugin+issue%22)
- [X] [I have checked the commit log of the master branch](https://github.com/streamlink/streamlink/commits/master)

### Streamlink version

Latest stable release

### Description

I randomly get this error on Windows 10 when fetching YouTube URL using channel id :

> Unable to open URL: https://consent.youtube.com/s (400 Client Error: Bad Request for url: https://consent.youtube.com/s)

This is random, sometimes stream fetching works, sometimes no.

And this is related with using the following type of URL, using the channel id instead of a video id : 
https://www.youtube.com/c/TinyKittens/live
https://www.youtube.com/channel/UCeL2LSl91k2VccR7XEh5IKg/live

It always works fine with URLs containing the "watch" parameter : 
https://www.youtube.com/watch?v=Pss7fUAUqrs

I'm doing the exact same test on Debian with Python 3.7 and it always works fine.

### Debug log

```text
===== Output when it fails :

> streamlink https://www.youtube.com/c/TinyKittens/live --loglevel debug
[cli][debug] OS:         Windows 10
[cli][debug] Python:     3.10.1
[cli][debug] Streamlink: 4.0.1
[cli][debug] Requests(2.27.1), Socks(1.7.1), Websocket(1.3.2)
[cli][debug] Arguments:
[cli][debug]  url=https://www.youtube.com/c/TinyKittens/live
[cli][debug]  --loglevel=debug
[cli][debug]  --ffmpeg-ffmpeg=C:\Program Files (x86)\Streamlink\ffmpeg\ffmpeg.exe
[cli][info] Found matching plugin youtube for URL https://www.youtube.com/c/TinyKittens/live
[plugins.youtube][debug] c_data_keys: gl, m, pc, continue, x, v, bl, hl, src, uxe, set_eom, set_ytc, set_apyt
error: Unable to open URL: https://consent.youtube.com/s (400 Client Error: Bad Request for url: https://consent.youtube.com/s)

===== Output when it randomly works :

> streamlink https://www.youtube.com/c/TinyKittens/live  --loglevel debug
[cli][debug] OS:         Windows 10
[cli][debug] Python:     3.9.10
[cli][debug] Streamlink: 3.2.0
[cli][debug] Requests(2.27.1), Socks(1.7.1), Websocket(1.2.3)
[cli][debug] Arguments:
[cli][debug]  url=https://www.youtube.com/c/TinyKittens/live
[cli][debug]  --loglevel=debug
[cli][debug]  --ffmpeg-ffmpeg=C:\Program Files (x86)\Streamlink\ffmpeg\ffmpeg.exe
[cli][info] Found matching plugin youtube for URL https://www.youtube.com/c/TinyKittens/live
[plugins.youtube][debug] c_data_keys: gl, m, pc, continue, ca, x, v, t, hl, src, uxe
[plugins.youtube][debug] Using video ID: Pss7fUAUqrs
[plugins.youtube][debug] This video is live.
[utils.l10n][debug] Language code: fr_FR
Available streams: 144p (worst), 240p, 360p, 480p, 720p (best)
```



Discussion:

Resolving patch:
diff --git a/src/streamlink/plugins/youtube.py b/src/streamlink/plugins/youtube.py
--- a/src/streamlink/plugins/youtube.py
+++ b/src/streamlink/plugins/youtube.py
@@ -1,5 +1,5 @@
 """
-$description Global live streaming and video hosting social platform owned by Google.
+$description Global live-streaming and video hosting social platform owned by Google.
 $url youtube.com
 $url youtu.be
 $type live, vod
@@ -9,7 +9,6 @@
 import json
 import logging
 import re
-from html import unescape
 from urllib.parse import urlparse, urlunparse
 
 from streamlink.plugin import Plugin, PluginError, pluginmatcher
@@ -115,7 +114,18 @@ def stream_weight(cls, stream):
     def _schema_consent(data):
         schema_consent = validate.Schema(
             validate.parse_html(),
-            validate.xml_findall(".//input[@type='hidden']")
+            validate.any(
+                validate.xml_find(".//form[@action='https://consent.youtube.com/s']"),
+                validate.all(
+                    validate.xml_xpath(".//form[@action='https://consent.youtube.com/save']"),
+                    validate.filter(lambda elem: elem.xpath(".//input[@type='hidden'][@name='set_ytc'][@value='true']")),
+                    validate.get(0),
+                )
+            ),
+            validate.union((
+                validate.get("action"),
+                validate.xml_xpath(".//input[@type='hidden']"),
+            )),
         )
         return schema_consent.validate(data)
 
@@ -253,12 +263,14 @@ def _create_adaptive_streams(self, adaptive_formats):
     def _get_res(self, url):
         res = self.session.http.get(url)
         if urlparse(res.url).netloc == "consent.youtube.com":
+            target, elems = self._schema_consent(res.text)
             c_data = {
-                elem.attrib.get("name"): unescape(elem.attrib.get("value"))
-                for elem in self._schema_consent(res.text)
+                elem.attrib.get("name"): elem.attrib.get("value")
+                for elem in elems
             }
-            log.debug(f"c_data_keys: {', '.join(c_data.keys())}")
-            res = self.session.http.post("https://consent.youtube.com/s", data=c_data)
+            log.debug(f"consent target: {target}")
+            log.debug(f"consent data: {', '.join(c_data.keys())}")
+            res = self.session.http.post(target, data=c_data)
         return res
 
     @staticmethod


Test patch: