Repository: yt-dlp/yt-dlp

Issue:
LinkedIn download returns "Unsupported URL" error
### DO NOT REMOVE OR SKIP THE ISSUE TEMPLATE

- [X] I understand that I will be **blocked** if I remove or skip any mandatory\* field

### Checklist

- [X] I'm reporting a broken site
- [X] I've verified that I'm running yt-dlp version **2023.01.06** ([update instructions](https://github.com/yt-dlp/yt-dlp#update)) or later (specify commit)
- [X] I've checked that all provided URLs are playable in a browser with the same IP and same login details
- [X] I've checked that all URLs and arguments with special characters are [properly quoted or escaped](https://github.com/yt-dlp/yt-dlp/wiki/FAQ#video-url-contains-an-ampersand--and-im-getting-some-strange-output-1-2839-or-v-is-not-recognized-as-an-internal-or-external-command)
- [X] I've searched the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues **including closed ones**. DO NOT post duplicates
- [X] I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
- [X] I've read about [sharing account credentials](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#are-you-willing-to-share-account-details-if-needed) and I'm willing to share it if required

### Region

_No response_

### Provide a description that is worded well enough to be understood

Hi, I'm trying the dump the JSON data for the followin LinkedIn URL and it returns an error saying "Unsupported URL".

https://www.linkedin.com/feed/update/urn:li:activity:7016901149999955968?utm_source=share&utm_medium=member_desktop

### Provide verbose output that clearly demonstrates the problem

- [X] Run **your** yt-dlp command with **-vU** flag added (`yt-dlp -vU <your command line>`)
- [X] Copy the WHOLE output (starting with `[debug] Command-line config`) and insert it below

### Complete Verbose Output

```shell
[debug] Command-line config: ['-vU', '-J', 'https://www.linkedin.com/feed/update/urn:li:activity:7016901149999955968?utm_source=share']
[debug] Encodings: locale UTF-8, fs utf-8, pref UTF-8, out UTF-8, error UTF-8, screen UTF-8
[debug] yt-dlp version 2023.01.06 [6becd25] (zip)
[debug] Python 3.7.5 (CPython x86_64 64bit) - Linux-4.15.0-135-generic-x86_64-with-Ubuntu-18.04-bionic (OpenSSL 1.1.1  11 Sep 2018, glibc 2.26)
[debug] exe versions: none
[debug] Optional libraries: Cryptodome-3.14.1, brotli-1.0.9, certifi-2021.05.30, mutagen-1.45.1, sqlite3-2.6.0, websockets-10.2
[debug] Proxy map: {}
[debug] Loaded 1760 extractors
[debug] Fetching release info: https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest
Latest version: 2023.01.06, Current version: 2023.01.06
yt-dlp is up to date (2023.01.06)
[generic] Extracting URL: https://www.linkedin.com/feed/update/urn:li:activity:7016901149999955968?utm_source=share
[generic] urn:li:activity:7016901149999955968?utm_source=share: Downloading webpage
WARNING: [generic] Falling back on generic information extractor
[generic] urn:li:activity:7016901149999955968?utm_source=share: Extracting information
[debug] Looking for embeds
[debug] Identified a twitter:player iframe
[generic] Extracting URL: https://www.linkedin.com/embed/feed/update/urn:li:activity:7016901149999955968?compact=true
[generic] urn:li:activity:7016901149999955968?compact=true: Downloading webpage
WARNING: [generic] Falling back on generic information extractor
[generic] urn:li:activity:7016901149999955968?compact=true: Extracting information
[debug] Looking for embeds
ERROR: Unsupported URL: https://www.linkedin.com/embed/feed/update/urn:li:activity:7016901149999955968?compact=true
Traceback (most recent call last):
  File "/usr/local/bin/yt-dlp/yt_dlp/YoutubeDL.py", line 1502, in wrapper
    return func(self, *args, **kwargs)
  File "/usr/local/bin/yt-dlp/yt_dlp/YoutubeDL.py", line 1578, in __extract_info
    ie_result = ie.extract(url)
  File "/usr/local/bin/yt-dlp/yt_dlp/extractor/common.py", line 680, in extract
    ie_result = self._real_extract(url)
  File "/usr/local/bin/yt-dlp/yt_dlp/extractor/generic.py", line 2523, in _real_extract
    raise UnsupportedError(url)
yt_dlp.utils.UnsupportedError: Unsupported URL: https://www.linkedin.com/embed/feed/update/urn:li:activity:7016901149999955968?compact=true

null
```

Discussion:
@dirkf: The URL isn't matched, but the page structure is handled by the `LinkedInIE` extractor, if its `_VALID_URL` is modified by changing `/posts/` to `/(?:embed/)?(?:posts|feed/update)/`.

See https://github.com/ytdl-org/youtube-dl/issues/31501.@ahm750: I modified the URL by adding "/embed/" and the resulting page contains the <video> tag. Perhaps the extractor must be updated to handle this URL format?

Solution patch:
diff --git a/README.md b/README.md
index c0329f539..0cc2cd7b2 100644
--- a/README.md
+++ b/README.md
@@ -1770,7 +1770,7 @@ #### youtube
 * `lang`: Prefer translated metadata (`title`, `description` etc) of this language code (case-sensitive). By default, the video primary language metadata is preferred, with a fallback to `en` translated. See [youtube.py](https://github.com/yt-dlp/yt-dlp/blob/c26f9b991a0681fd3ea548d535919cec1fbbd430/yt_dlp/extractor/youtube.py#L381-L390) for list of supported content language codes
 * `skip`: One or more of `hls`, `dash` or `translated_subs` to skip extraction of the m3u8 manifests, dash manifests and [auto-translated subtitles](https://github.com/yt-dlp/yt-dlp/issues/4090#issuecomment-1158102032) respectively
 * `player_client`: Clients to extract video data from. The currently available clients are `web`, `web_safari`, `web_embedded`, `web_music`, `web_creator`, `mweb`, `ios`, `android`, `android_vr`, `tv` and `tv_embedded`. By default, `tv,ios,web` is used, or `tv,web` is used when authenticating with cookies. The `web_music` client is added for `music.youtube.com` URLs when logged-in cookies are used. The `tv_embedded` and `web_creator` clients are added for age-restricted videos if account age-verification is required. Some clients, such as `web` and `web_music`, require a `po_token` for their formats to be downloadable. Some clients, such as `web_creator`, will only work with authentication. Not all clients support authentication via cookies. You can use `default` for the default clients, or you can use `all` for all clients (not recommended). You can prefix a client with `-` to exclude it, e.g. `youtube:player_client=default,-ios`
-* `player_skip`: Skip some network requests that are generally needed for robust extraction. One or more of `configs` (skip client configs), `webpage` (skip initial webpage), `js` (skip js player). While these options can help reduce the number of requests needed or avoid some rate-limiting, they could cause some issues. See [#860](https://github.com/yt-dlp/yt-dlp/pull/860) for more details
+* `player_skip`: Skip some network requests that are generally needed for robust extraction. One or more of `configs` (skip client configs), `webpage` (skip initial webpage), `js` (skip js player), `initial_data` (skip initial data/next ep request). While these options can help reduce the number of requests needed or avoid some rate-limiting, they could cause issues such as missing formats or metadata.  See [#860](https://github.com/yt-dlp/yt-dlp/pull/860) and [#12826](https://github.com/yt-dlp/yt-dlp/issues/12826) for more details
 * `player_params`: YouTube player parameters to use for player requests. Will overwrite any default ones set by yt-dlp.
 * `comment_sort`: `top` or `new` (default) - choose comment sorting mode (on YouTube's side)
 * `max_comments`: Limit the amount of comments to gather. Comma-separated list of integers representing `max-comments,max-parents,max-replies,max-replies-per-thread`. Default is `all,all,all,all`
diff --git a/yt_dlp/extractor/cda.py b/yt_dlp/extractor/cda.py
index 96f25c22a..aa39bf382 100644
--- a/yt_dlp/extractor/cda.py
+++ b/yt_dlp/extractor/cda.py
@@ -353,7 +353,7 @@ def extract_format(page, version):
 
 class CDAFolderIE(InfoExtractor):
     _MAX_PAGE_SIZE = 36
-    _VALID_URL = r'https?://(?:www\.)?cda\.pl/(?P<channel>\w+)/folder/(?P<id>\d+)'
+    _VALID_URL = r'https?://(?:www\.)?cda\.pl/(?P<channel>[\w-]+)/folder/(?P<id>\d+)'
     _TESTS = [
         {
             'url': 'https://www.cda.pl/domino264/folder/31188385',
@@ -378,6 +378,9 @@ class CDAFolderIE(InfoExtractor):
                 'title': 'TESTY KOSMETYKÓW',
             },
             'playlist_mincount': 139,
+        }, {
+            'url': 'https://www.cda.pl/FILMY-SERIALE-ANIME-KRESKOWKI-BAJKI/folder/18493422',
+            'only_matching': True,
         }]
 
     def _real_extract(self, url):
diff --git a/yt_dlp/extractor/linkedin.py b/yt_dlp/extractor/linkedin.py
index c8c8ae52a..d25f0fe6a 100644
--- a/yt_dlp/extractor/linkedin.py
+++ b/yt_dlp/extractor/linkedin.py
@@ -82,7 +82,10 @@ def _get_video_id(self, video_data, course_slug, video_slug):
 
 
 class LinkedInIE(LinkedInBaseIE):
-    _VALID_URL = r'https?://(?:www\.)?linkedin\.com/posts/[^/?#]+-(?P<id>\d+)-\w{4}/?(?:[?#]|$)'
+    _VALID_URL = [
+        r'https?://(?:www\.)?linkedin\.com/posts/[^/?#]+-(?P<id>\d+)-\w{4}/?(?:[?#]|$)',
+        r'https?://(?:www\.)?linkedin\.com/feed/update/urn:li:activity:(?P<id>\d+)',
+    ]
     _TESTS = [{
         'url': 'https://www.linkedin.com/posts/mishalkhawaja_sendinblueviews-toronto-digitalmarketing-ugcPost-6850898786781339649-mM20',
         'info_dict': {
@@ -106,6 +109,9 @@ class LinkedInIE(LinkedInBaseIE):
             'like_count': int,
             'subtitles': 'mincount:1',
         },
+    }, {
+        'url': 'https://www.linkedin.com/feed/update/urn:li:activity:7016901149999955968/?utm_source=share&utm_medium=member_desktop',
+        'only_matching': True,
     }]
 
     def _real_extract(self, url):
diff --git a/yt_dlp/extractor/manyvids.py b/yt_dlp/extractor/manyvids.py
index 8caa8f87f..1356169bf 100644
--- a/yt_dlp/extractor/manyvids.py
+++ b/yt_dlp/extractor/manyvids.py
@@ -1,31 +1,38 @@
-import re
-
 from .common import InfoExtractor
 from ..utils import (
+    clean_html,
     determine_ext,
-    extract_attributes,
     int_or_none,
-    str_to_int,
+    join_nonempty,
+    parse_count,
+    parse_duration,
+    parse_iso8601,
     url_or_none,
-    urlencode_postdata,
 )
+from ..utils.traversal import traverse_obj
 
 
 class ManyVidsIE(InfoExtractor):
-    _WORKING = False
     _VALID_URL = r'(?i)https?://(?:www\.)?manyvids\.com/video/(?P<id>\d+)'
     _TESTS = [{
         # preview video
-        'url': 'https://www.manyvids.com/Video/133957/everthing-about-me/',
-        'md5': '03f11bb21c52dd12a05be21a5c7dcc97',
+        'url': 'https://www.manyvids.com/Video/530341/mv-tips-tricks',
+        'md5': '738dc723f7735ee9602f7ea352a6d058',
         'info_dict': {
-            'id': '133957',
+            'id': '530341-preview',
             'ext': 'mp4',
-            'title': 'everthing about me (Preview)',
-            'uploader': 'ellyxxix',
+            'title': 'MV Tips & Tricks (Preview)',
+            'description': r're:I will take you on a tour around .{1313}$',
+            'thumbnail': r're:https://cdn5\.manyvids\.com/php_uploads/video_images/DestinyDiaz/.+\.jpg',
+            'uploader': 'DestinyDiaz',
             'view_count': int,
             'like_count': int,
+            'release_timestamp': 1508419904,
+            'tags': ['AdultSchool', 'BBW', 'SFW', 'TeacherFetish'],
+            'release_date': '20171019',
+            'duration': 3167.0,
         },
+        'expected_warnings': ['Only extracting preview'],
     }, {
         # full video
         'url': 'https://www.manyvids.com/Video/935718/MY-FACE-REVEAL/',
@@ -34,129 +41,68 @@ class ManyVidsIE(InfoExtractor):
             'id': '935718',
             'ext': 'mp4',
             'title': 'MY FACE REVEAL',
-            'description': 'md5:ec5901d41808b3746fed90face161612',
+            'description': r're:Today is the day!! I am finally taking off my mask .{445}$',
+            'thumbnail': r're:https://ods\.manyvids\.com/1001061960/3aa5397f2a723ec4597e344df66ab845/screenshots/.+\.jpg',
             'uploader': 'Sarah Calanthe',
             'view_count': int,
             'like_count': int,
+            'release_date': '20181110',
+            'tags': ['EyeContact', 'Interviews', 'MaskFetish', 'MouthFetish', 'Redhead'],
+            'release_timestamp': 1541851200,
+            'duration': 224.0,
         },
     }]
+    _API_BASE = 'https://www.manyvids.com/bff/store/video'
 
     def _real_extract(self, url):
         video_id = self._match_id(url)
-
-        real_url = f'https://www.manyvids.com/video/{video_id}/gtm.js'
-        try:
-            webpage = self._download_webpage(real_url, video_id)
-        except Exception:
-            # probably useless fallback
-            webpage = self._download_webpage(url, video_id)
-
-        info = self._search_regex(
-            r'''(<div\b[^>]*\bid\s*=\s*(['"])pageMetaDetails\2[^>]*>)''',
-            webpage, 'meta details', default='')
-        info = extract_attributes(info)
-
-        player = self._search_regex(
-            r'''(<div\b[^>]*\bid\s*=\s*(['"])rmpPlayerStream\2[^>]*>)''',
-            webpage, 'player details', default='')
-        player = extract_attributes(player)
-
-        video_urls_and_ids = (
-            (info.get('data-meta-video'), 'video'),
-            (player.get('data-video-transcoded'), 'transcoded'),
-            (player.get('data-video-filepath'), 'filepath'),
-            (self._og_search_video_url(webpage, secure=False, default=None), 'og_video'),
-        )
-
-        def txt_or_none(s, default=None):
-            return (s.strip() or default) if isinstance(s, str) else default
-
-        uploader = txt_or_none(info.get('data-meta-author'))
-
-        def mung_title(s):
-            if uploader:
-                s = re.sub(rf'^\s*{re.escape(uploader)}\s+[|-]', '', s)
-            return txt_or_none(s)
-
-        title = (
-            mung_title(info.get('data-meta-title'))
-            or self._html_search_regex(
-                (r'<span[^>]+class=["\']item-title[^>]+>([^<]+)',
-                 r'<h2[^>]+class=["\']h2 m-0["\'][^>]*>([^<]+)'),
-                webpage, 'title', default=None)
-            or self._html_search_meta(
-                'twitter:title', webpage, 'title', fatal=True))
-
-        title = re.sub(r'\s*[|-]\s+ManyVids\s*$', '', title) or title
-
-        if any(p in webpage for p in ('preview_videos', '_preview.mp4')):
-            title += ' (Preview)'
-
-        mv_token = self._search_regex(
-            r'data-mvtoken=(["\'])(?P<value>(?:(?!\1).)+)\1', webpage,
-            'mv token', default=None, group='value')
-
-        if mv_token:
-            # Sets some cookies
-            self._download_webpage(
-                'https://www.manyvids.com/includes/ajax_repository/you_had_me_at_hello.php',
-                video_id, note='Setting format cookies', fatal=False,
-                data=urlencode_postdata({
-                    'mvtoken': mv_token,
-                    'vid': video_id,
-                }), headers={
-                    'Referer': url,
-                    'X-Requested-With': 'XMLHttpRequest',
-                })
-
-        formats = []
-        for v_url, fmt in video_urls_and_ids:
-            v_url = url_or_none(v_url)
-            if not v_url:
+        video_data = self._download_json(f'{self._API_BASE}/{video_id}/private', video_id)['data']
+        formats, preview_only = [], True
+
+        for format_id, path in [
+            ('preview', ['teaser', 'filepath']),
+            ('transcoded', ['transcodedFilepath']),
+            ('filepath', ['filepath']),
+        ]:
+            format_url = traverse_obj(video_data, (*path, {url_or_none}))
+            if not format_url:
                 continue
-            if determine_ext(v_url) == 'm3u8':
-                formats.extend(self._extract_m3u8_formats(
-                    v_url, video_id, 'mp4', entry_protocol='m3u8_native',
-                    m3u8_id='hls'))
+            if determine_ext(format_url) == 'm3u8':
+                formats.extend(self._extract_m3u8_formats(format_url, video_id, 'mp4', m3u8_id=format_id))
             else:
                 formats.append({
-                    'url': v_url,
-                    'format_id': fmt,
+                    'url': format_url,
+                    'format_id': format_id,
+                    'preference': -10 if format_id == 'preview' else None,
+                    'quality': 10 if format_id == 'filepath' else None,
+                    'height': int_or_none(
+                        self._search_regex(r'_(\d{2,3}[02468])_', format_url, 'height', default=None)),
                 })
+            if format_id != 'preview':
+                preview_only = False
 
-        self._remove_duplicate_formats(formats)
-
-        for f in formats:
-            if f.get('height') is None:
-                f['height'] = int_or_none(
-                    self._search_regex(r'_(\d{2,3}[02468])_', f['url'], 'video height', default=None))
-            if '/preview/' in f['url']:
-                f['format_id'] = '_'.join(filter(None, (f.get('format_id'), 'preview')))
-                f['preference'] = -10
-            if 'transcoded' in f['format_id']:
-                f['preference'] = f.get('preference', -1) - 1
-
-        def get_likes():
-            likes = self._search_regex(
-                rf'''(<a\b[^>]*\bdata-id\s*=\s*(['"]){video_id}\2[^>]*>)''',
-                webpage, 'likes', default='')
-            likes = extract_attributes(likes)
-            return int_or_none(likes.get('data-likes'))
+        metadata = traverse_obj(
+            self._download_json(f'{self._API_BASE}/{video_id}', video_id, fatal=False), 'data')
+        title = traverse_obj(metadata, ('title', {clean_html}))
 
-        def get_views():
-            return str_to_int(self._html_search_regex(
-                r'''(?s)<span\b[^>]*\bclass\s*=["']views-wrapper\b[^>]+>.+?<span\b[^>]+>\s*(\d[\d,.]*)\s*</span>''',
-                webpage, 'view count', default=None))
+        if preview_only:
+            title = join_nonempty(title, '(Preview)', delim=' ')
+            video_id += '-preview'
+            self.report_warning(
+                f'Only extracting preview. Video may be paid or subscription only. {self._login_hint()}')
 
         return {
             'id': video_id,
             'title': title,
             'formats': formats,
-            'description': txt_or_none(info.get('data-meta-description')),
-            'uploader': txt_or_none(info.get('data-meta-author')),
-            'thumbnail': (
-                url_or_none(info.get('data-meta-image'))
-                or url_or_none(player.get('data-video-screenshot'))),
-            'view_count': get_views(),
-            'like_count': get_likes(),
+            **traverse_obj(metadata, {
+                'description': ('description', {clean_html}),
+                'uploader': ('model', 'displayName', {clean_html}),
+                'thumbnail': (('screenshot', 'thumbnail'), {url_or_none}, any),
+                'view_count': ('views', {parse_count}),
+                'like_count': ('likes', {parse_count}),
+                'release_timestamp': ('launchDate', {parse_iso8601}),
+                'duration': ('videoDuration', {parse_duration}),
+                'tags': ('tagList', ..., 'label', {str}, filter, all, filter),
+            }),
         }
diff --git a/yt_dlp/extractor/tvp.py b/yt_dlp/extractor/tvp.py
index da3082907..416cbab3c 100644
--- a/yt_dlp/extractor/tvp.py
+++ b/yt_dlp/extractor/tvp.py
@@ -513,7 +513,7 @@ def _parse_video(self, video, with_url=True):
 
 class TVPVODVideoIE(TVPVODBaseIE):
     IE_NAME = 'tvp:vod'
-    _VALID_URL = r'https?://vod\.tvp\.pl/(?P<category>[a-z\d-]+,\d+)/[a-z\d-]+(?<!-odcinki)(?:-odcinki,\d+/odcinek-\d+,S\d+E\d+)?,(?P<id>\d+)/?(?:[?#]|$)'
+    _VALID_URL = r'https?://vod\.tvp\.pl/(?P<category>[a-z\d-]+,\d+)/[a-z\d-]+(?<!-odcinki)(?:-odcinki,\d+/odcinek--?\d+,S-?\d+E-?\d+)?,(?P<id>\d+)/?(?:[?#]|$)'
 
     _TESTS = [{
         'url': 'https://vod.tvp.pl/dla-dzieci,24/laboratorium-alchemika-odcinki,309338/odcinek-24,S01E24,311357',
@@ -568,6 +568,9 @@ class TVPVODVideoIE(TVPVODBaseIE):
             'live_status': 'is_live',
             'thumbnail': 're:https?://.+',
         },
+    }, {
+        'url': 'https://vod.tvp.pl/informacje-i-publicystyka,205/konskie-2025-debata-przedwyborcza-odcinki,2028435/odcinek--1,S01E-1,2028419',
+        'only_matching': True,
     }]
 
     def _real_extract(self, url):
diff --git a/yt_dlp/extractor/youtube/_tab.py b/yt_dlp/extractor/youtube/_tab.py
index 122300e60..c018ee8cf 100644
--- a/yt_dlp/extractor/youtube/_tab.py
+++ b/yt_dlp/extractor/youtube/_tab.py
@@ -524,10 +524,16 @@ def _entries(self, tab, item_id, ytcfg, delegated_session_id, visitor_data):
             response = self._extract_response(
                 item_id=f'{item_id} page {page_num}',
                 query=continuation, headers=headers, ytcfg=ytcfg,
-                check_get_keys=('continuationContents', 'onResponseReceivedActions', 'onResponseReceivedEndpoints'))
+                check_get_keys=(
+                    'continuationContents', 'onResponseReceivedActions', 'onResponseReceivedEndpoints',
+                    # Playlist recommendations may return with no data - ignore
+                    ('responseContext', 'serviceTrackingParams', ..., 'params', ..., lambda k, v: k == 'key' and v == 'GetRecommendedMusicPlaylists_rid'),
+                ))
 
             if not response:
                 break
+
+            continuation = None
             # Extracting updated visitor data is required to prevent an infinite extraction loop in some cases
             # See: https://github.com/ytdl-org/youtube-dl/issues/28702
             visitor_data = self._extract_visitor_data(response) or visitor_data
@@ -564,7 +570,13 @@ def _entries(self, tab, item_id, ytcfg, delegated_session_id, visitor_data):
                 yield from func(video_items_renderer)
                 continuation = continuation_list[0] or self._extract_continuation(video_items_renderer)
 
-            if not video_items_renderer:
+            # In the case only a continuation is returned, try to follow it.
+            # We extract this after trying to extract non-continuation items as otherwise this
+            # may be prioritized over other continuations.
+            # see: https://github.com/yt-dlp/yt-dlp/issues/12933
+            continuation = continuation or self._extract_continuation({'contents': [continuation_item]})
+
+            if not continuation and not video_items_renderer:
                 break
 
     @staticmethod
@@ -999,14 +1011,14 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
         'playlist_mincount': 94,
         'info_dict': {
             'id': 'UCqj7Cz7revf5maW9g5pgNcg',
-            'title': 'Igor Kleiner Ph.D. - Playlists',
+            'title': 'Igor Kleiner  - Playlists',
             'description': 'md5:15d7dd9e333cb987907fcb0d604b233a',
-            'uploader': 'Igor Kleiner Ph.D.',
+            'uploader': 'Igor Kleiner ',
             'uploader_id': '@IgorDataScience',
             'uploader_url': 'https://www.youtube.com/@IgorDataScience',
-            'channel': 'Igor Kleiner Ph.D.',
+            'channel': 'Igor Kleiner ',
             'channel_id': 'UCqj7Cz7revf5maW9g5pgNcg',
-            'tags': ['критическое мышление', 'наука просто', 'математика', 'анализ данных'],
+            'tags': 'count:23',
             'channel_url': 'https://www.youtube.com/channel/UCqj7Cz7revf5maW9g5pgNcg',
             'channel_follower_count': int,
         },
@@ -1016,18 +1028,19 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
         'playlist_mincount': 94,
         'info_dict': {
             'id': 'UCqj7Cz7revf5maW9g5pgNcg',
-            'title': 'Igor Kleiner Ph.D. - Playlists',
+            'title': 'Igor Kleiner  - Playlists',
             'description': 'md5:15d7dd9e333cb987907fcb0d604b233a',
-            'uploader': 'Igor Kleiner Ph.D.',
+            'uploader': 'Igor Kleiner ',
             'uploader_id': '@IgorDataScience',
             'uploader_url': 'https://www.youtube.com/@IgorDataScience',
-            'tags': ['критическое мышление', 'наука просто', 'математика', 'анализ данных'],
+            'tags': 'count:23',
             'channel_id': 'UCqj7Cz7revf5maW9g5pgNcg',
-            'channel': 'Igor Kleiner Ph.D.',
+            'channel': 'Igor Kleiner ',
             'channel_url': 'https://www.youtube.com/channel/UCqj7Cz7revf5maW9g5pgNcg',
             'channel_follower_count': int,
         },
     }, {
+        # TODO: fix channel_is_verified extraction
         'note': 'playlists, series',
         'url': 'https://www.youtube.com/c/3blue1brown/playlists?view=50&sort=dd&shelf_id=3',
         'playlist_mincount': 5,
@@ -1066,22 +1079,23 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
         'url': 'https://www.youtube.com/c/ChristophLaimer/playlists',
         'only_matching': True,
     }, {
+        # TODO: fix availability extraction
         'note': 'basic, single video playlist',
-        'url': 'https://www.youtube.com/playlist?list=PL4lCao7KL_QFVb7Iudeipvc2BCavECqzc',
+        'url': 'https://www.youtube.com/playlist?list=PLt5yu3-wZAlSLRHmI1qNm0wjyVNWw1pCU',
         'info_dict': {
-            'id': 'PL4lCao7KL_QFVb7Iudeipvc2BCavECqzc',
-            'title': 'youtube-dl public playlist',
+            'id': 'PLt5yu3-wZAlSLRHmI1qNm0wjyVNWw1pCU',
+            'title': 'single video playlist',
             'description': '',
             'tags': [],
             'view_count': int,
-            'modified_date': '20201130',
-            'channel': 'Sergey M.',
-            'channel_id': 'UCmlqkdCBesrv2Lak1mF_MxA',
-            'channel_url': 'https://www.youtube.com/channel/UCmlqkdCBesrv2Lak1mF_MxA',
+            'modified_date': '20250417',
+            'channel': 'cole-dlp-test-acc',
+            'channel_id': 'UCiu-3thuViMebBjw_5nWYrA',
+            'channel_url': 'https://www.youtube.com/channel/UCiu-3thuViMebBjw_5nWYrA',
             'availability': 'public',
-            'uploader': 'Sergey M.',
-            'uploader_url': 'https://www.youtube.com/@sergeym.6173',
-            'uploader_id': '@sergeym.6173',
+            'uploader': 'cole-dlp-test-acc',
+            'uploader_url': 'https://www.youtube.com/@coletdjnz',
+            'uploader_id': '@coletdjnz',
         },
         'playlist_count': 1,
     }, {
@@ -1171,11 +1185,11 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
         },
         'playlist_mincount': 17,
     }, {
-        'note': 'Community tab',
+        'note': 'Posts tab',
         'url': 'https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w/community',
         'info_dict': {
             'id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
-            'title': 'lex will - Community',
+            'title': 'lex will - Posts',
             'description': 'md5:2163c5d0ff54ed5f598d6a7e6211e488',
             'channel': 'lex will',
             'channel_url': 'https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w',
@@ -1188,30 +1202,14 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
         },
         'playlist_mincount': 18,
     }, {
-        'note': 'Channels tab',
-        'url': 'https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w/channels',
-        'info_dict': {
-            'id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
-            'title': 'lex will - Channels',
-            'description': 'md5:2163c5d0ff54ed5f598d6a7e6211e488',
-            'channel': 'lex will',
-            'channel_url': 'https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w',
-            'channel_id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
-            'tags': ['bible', 'history', 'prophesy'],
-            'channel_follower_count': int,
-            'uploader_url': 'https://www.youtube.com/@lexwill718',
-            'uploader_id': '@lexwill718',
-            'uploader': 'lex will',
-        },
-        'playlist_mincount': 12,
-    }, {
+        # TODO: fix channel_is_verified extraction
         'note': 'Search tab',
         'url': 'https://www.youtube.com/c/3blue1brown/search?query=linear%20algebra',
         'playlist_mincount': 40,
         'info_dict': {
             'id': 'UCYO_jab_esuFRV4b17AJtAw',
             'title': '3Blue1Brown - Search - linear algebra',
-            'description': 'md5:4d1da95432004b7ba840ebc895b6b4c9',
+            'description': 'md5:602e3789e6a0cb7d9d352186b720e395',
             'channel_url': 'https://www.youtube.com/channel/UCYO_jab_esuFRV4b17AJtAw',
             'tags': ['Mathematics'],
             'channel': '3Blue1Brown',
@@ -1232,6 +1230,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
         'url': 'https://music.youtube.com/channel/UCmlqkdCBesrv2Lak1mF_MxA',
         'only_matching': True,
     }, {
+        # TODO: fix availability extraction
         'note': 'Playlist with deleted videos (#651). As a bonus, the video #51 is also twice in this list.',
         'url': 'https://www.youtube.com/playlist?list=PLwP_SiAcdui0KVebT0mU9Apz359a4ubsC',
         'info_dict': {
@@ -1294,24 +1293,25 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
         },
         'playlist_mincount': 21,
     }, {
+        # TODO: fix availability extraction
         'note': 'Playlist with "show unavailable videos" button',
-        'url': 'https://www.youtube.com/playlist?list=UUTYLiWFZy8xtPwxFwX9rV7Q',
+        'url': 'https://www.youtube.com/playlist?list=PLYwq8WOe86_xGmR7FrcJq8Sb7VW8K3Tt2',
         'info_dict': {
-            'title': 'Uploads from Phim Siêu Nhân Nhật Bản',
-            'id': 'UUTYLiWFZy8xtPwxFwX9rV7Q',
+            'title': 'The Memes Of 2010s.....',
+            'id': 'PLYwq8WOe86_xGmR7FrcJq8Sb7VW8K3Tt2',
             'view_count': int,
-            'channel': 'Phim Siêu Nhân Nhật Bản',
+            'channel': "I'm Not JiNxEd",
             'tags': [],
-            'description': '',
-            'channel_url': 'https://www.youtube.com/channel/UCTYLiWFZy8xtPwxFwX9rV7Q',
-            'channel_id': 'UCTYLiWFZy8xtPwxFwX9rV7Q',
+            'description': 'md5:44dc3b315ba69394feaafa2f40e7b2a1',
+            'channel_url': 'https://www.youtube.com/channel/UC5H5H85D1QE5-fuWWQ1hdNg',
+            'channel_id': 'UC5H5H85D1QE5-fuWWQ1hdNg',
             'modified_date': r're:\d{8}',
             'availability': 'public',
-            'uploader_url': 'https://www.youtube.com/@phimsieunhannhatban',
-            'uploader_id': '@phimsieunhannhatban',
-            'uploader': 'Phim Siêu Nhân Nhật Bản',
+            'uploader_url': 'https://www.youtube.com/@imnotjinxed1998',
+            'uploader_id': '@imnotjinxed1998',
+            'uploader': "I'm Not JiNxEd",
         },
-        'playlist_mincount': 200,
+        'playlist_mincount': 150,
         'expected_warnings': [r'[Uu]navailable videos (are|will be) hidden'],
     }, {
         'note': 'Playlist with unavailable videos in page 7',
@@ -1334,6 +1334,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
         'playlist_mincount': 1000,
         'expected_warnings': [r'[Uu]navailable videos (are|will be) hidden'],
     }, {
+        # TODO: fix availability extraction
         'note': 'https://github.com/ytdl-org/youtube-dl/issues/21844',
         'url': 'https://www.youtube.com/playlist?list=PLzH6n4zXuckpfMu_4Ff8E7Z1behQks5ba',
         'info_dict': {
@@ -1384,7 +1385,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
     }, {
         'url': 'https://www.youtube.com/channel/UCoMdktPbSTixAyNGwb-UYkQ/live',
         'info_dict': {
-            'id': 'hGkQjiJLjWQ',  # This will keep changing
+            'id': 'YDvsBbKfLPA',  # This will keep changing
             'ext': 'mp4',
             'title': str,
             'upload_date': r're:\d{8}',
@@ -1409,6 +1410,8 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
             'uploader_id': '@SkyNews',
             'uploader': 'Sky News',
             'channel_is_verified': True,
+            'media_type': 'livestream',
+            'timestamp': int,
         },
         'params': {
             'skip_download': True,
@@ -1496,6 +1499,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
         'url': 'https://music.youtube.com/browse/UC1a8OFewdjuLq6KlF8M_8Ng',
         'only_matching': True,
     }, {
+        # TODO: fix availability extraction
         'note': 'VLPL, should redirect to playlist?list=PL...',
         'url': 'https://music.youtube.com/browse/VLPLRBp0Fe2GpgmgoscNFLxNyBVSFVdYmFkq',
         'info_dict': {
@@ -1537,6 +1541,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
     }, {
         # Destination channel with only a hidden self tab (tab id is UCtFRv9O2AHqOZjjynzrv-xg)
         # Treat as a general feed
+        # TODO: fix extraction
         'url': 'https://www.youtube.com/channel/UCtFRv9O2AHqOZjjynzrv-xg',
         'info_dict': {
             'id': 'UCtFRv9O2AHqOZjjynzrv-xg',
@@ -1560,21 +1565,21 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
         'expected_warnings': ['YouTube Music is not directly supported'],
     }, {
         'note': 'unlisted single video playlist',
-        'url': 'https://www.youtube.com/playlist?list=PLwL24UFy54GrB3s2KMMfjZscDi1x5Dajf',
+        'url': 'https://www.youtube.com/playlist?list=PLt5yu3-wZAlQLfIN0MMgp0wVV6MP3bM4_',
         'info_dict': {
-            'id': 'PLwL24UFy54GrB3s2KMMfjZscDi1x5Dajf',
-            'title': 'yt-dlp unlisted playlist test',
+            'id': 'PLt5yu3-wZAlQLfIN0MMgp0wVV6MP3bM4_',
+            'title': 'unlisted playlist',
             'availability': 'unlisted',
             'tags': [],
-            'modified_date': '20220418',
-            'channel': 'colethedj',
+            'modified_date': '20250417',
+            'channel': 'cole-dlp-test-acc',
             'view_count': int,
             'description': '',
-            'channel_id': 'UC9zHu_mHU96r19o-wV5Qs1Q',
-            'channel_url': 'https://www.youtube.com/channel/UC9zHu_mHU96r19o-wV5Qs1Q',
-            'uploader_url': 'https://www.youtube.com/@colethedj1894',
-            'uploader_id': '@colethedj1894',
-            'uploader': 'colethedj',
+            'channel_id': 'UCiu-3thuViMebBjw_5nWYrA',
+            'channel_url': 'https://www.youtube.com/channel/UCiu-3thuViMebBjw_5nWYrA',
+            'uploader_url': 'https://www.youtube.com/@coletdjnz',
+            'uploader_id': '@coletdjnz',
+            'uploader': 'cole-dlp-test-acc',
         },
         'playlist': [{
             'info_dict': {
@@ -1596,6 +1601,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
         'playlist_count': 1,
         'params': {'extract_flat': True},
     }, {
+        # By default, recommended is always empty.
         'note': 'API Fallback: Recommended - redirects to home page. Requires visitorData',
         'url': 'https://www.youtube.com/feed/recommended',
         'info_dict': {
@@ -1603,7 +1609,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
             'title': 'recommended',
             'tags': [],
         },
-        'playlist_mincount': 50,
+        'playlist_count': 0,
         'params': {
             'skip_download': True,
             'extractor_args': {'youtubetab': {'skip': ['webpage']}},
@@ -1628,6 +1634,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
         },
         'skip': 'Query for sorting no longer works',
     }, {
+        # TODO: fix 'unviewable' issue with this playlist when reloading with unavailable videos
         'note': 'API Fallback: Topic, should redirect to playlist?list=UU...',
         'url': 'https://music.youtube.com/browse/UC9ALqqC4aIeG5iDs7i90Bfw',
         'info_dict': {
@@ -1654,11 +1661,12 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
         'url': 'https://www.youtube.com/channel/UCwVVpHQ2Cs9iGJfpdFngePQ',
         'only_matching': True,
     }, {
+        # TODO: fix metadata extraction
         'note': 'collaborative playlist (uploader name in the form "by <uploader> and x other(s)")',
         'url': 'https://www.youtube.com/playlist?list=PLx-_-Kk4c89oOHEDQAojOXzEzemXxoqx6',
         'info_dict': {
             'id': 'PLx-_-Kk4c89oOHEDQAojOXzEzemXxoqx6',
-            'modified_date': '20220407',
+            'modified_date': '20250115',
             'channel_url': 'https://www.youtube.com/channel/UCKcqXmCcyqnhgpA5P0oHH_Q',
             'tags': [],
             'availability': 'unlisted',
@@ -1692,6 +1700,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
         'expected_warnings': ['Preferring "ja"'],
     }, {
         # XXX: this should really check flat playlist entries, but the test suite doesn't support that
+        # TODO: fix availability extraction
         'note': 'preferred lang set with playlist with translated video titles',
         'url': 'https://www.youtube.com/playlist?list=PLt5yu3-wZAlQAaPZ5Z-rJoTdbT-45Q7c0',
         'info_dict': {
@@ -1714,6 +1723,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
     }, {
         # shorts audio pivot for 2GtVksBMYFM.
         'url': 'https://www.youtube.com/feed/sfv_audio_pivot?bp=8gUrCikSJwoLMkd0VmtzQk1ZRk0SCzJHdFZrc0JNWUZNGgsyR3RWa3NCTVlGTQ==',
+        # TODO: fix extraction
         'info_dict': {
             'id': 'sfv_audio_pivot',
             'title': 'sfv_audio_pivot',
@@ -1751,6 +1761,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
         'playlist_mincount': 8,
     }, {
         # Should get three playlists for videos, shorts and streams tabs
+        # TODO: fix channel_is_verified extraction
         'url': 'https://www.youtube.com/channel/UCK9V2B22uJYu3N7eR_BT9QA',
         'info_dict': {
             'id': 'UCK9V2B22uJYu3N7eR_BT9QA',
@@ -1758,7 +1769,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
             'channel_follower_count': int,
             'channel_id': 'UCK9V2B22uJYu3N7eR_BT9QA',
             'channel_url': 'https://www.youtube.com/channel/UCK9V2B22uJYu3N7eR_BT9QA',
-            'description': 'md5:49809d8bf9da539bc48ed5d1f83c33f2',
+            'description': 'md5:01e53f350ab8ad6fcf7c4fedb3c1b99f',
             'channel': 'Polka Ch. 尾丸ポルカ',
             'tags': 'count:35',
             'uploader_url': 'https://www.youtube.com/@OmaruPolka',
@@ -1769,14 +1780,14 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
         'playlist_count': 3,
     }, {
         # Shorts tab with channel with handle
-        # TODO: fix channel description
+        # TODO: fix channel_is_verified extraction
         'url': 'https://www.youtube.com/@NotJustBikes/shorts',
         'info_dict': {
             'id': 'UC0intLFzLaudFG-xAvUEO-A',
             'title': 'Not Just Bikes - Shorts',
             'tags': 'count:10',
             'channel_url': 'https://www.youtube.com/channel/UC0intLFzLaudFG-xAvUEO-A',
-            'description': 'md5:5e82545b3a041345927a92d0585df247',
+            'description': 'md5:1d9fc1bad7f13a487299d1fe1712e031',
             'channel_follower_count': int,
             'channel_id': 'UC0intLFzLaudFG-xAvUEO-A',
             'channel': 'Not Just Bikes',
@@ -1797,7 +1808,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
             'channel_url': 'https://www.youtube.com/channel/UC3eYAvjCVwNHgkaGbXX3sig',
             'channel': '中村悠一',
             'channel_follower_count': int,
-            'description': 'md5:e744f6c93dafa7a03c0c6deecb157300',
+            'description': 'md5:e8fd705073a594f27d6d6d020da560dc',
             'uploader_url': 'https://www.youtube.com/@Yuichi-Nakamura',
             'uploader_id': '@Yuichi-Nakamura',
             'uploader': '中村悠一',
@@ -1815,6 +1826,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
         'only_matching': True,
     }, {
         # No videos tab but has a shorts tab
+        # TODO: fix metadata extraction
         'url': 'https://www.youtube.com/c/TKFShorts',
         'info_dict': {
             'id': 'UCgJ5_1F6yJhYLnyMszUdmUg',
@@ -1851,6 +1863,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
     }, {
         # Shorts url result in shorts tab
         # TODO: Fix channel id extraction
+        # TODO: fix test suite, 208163447408c78673b08c172beafe5c310fb167 broke this test
         'url': 'https://www.youtube.com/channel/UCiu-3thuViMebBjw_5nWYrA/shorts',
         'info_dict': {
             'id': 'UCiu-3thuViMebBjw_5nWYrA',
@@ -1879,6 +1892,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
         'params': {'extract_flat': True},
     }, {
         # Live video status should be extracted
+        # TODO: fix test suite, 208163447408c78673b08c172beafe5c310fb167 broke this test
         'url': 'https://www.youtube.com/channel/UCQvWX73GQygcwXOTSf_VDVg/live',
         'info_dict': {
             'id': 'UCQvWX73GQygcwXOTSf_VDVg',
@@ -1907,6 +1921,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
         'playlist_mincount': 1,
     }, {
         # Channel renderer metadata. Contains number of videos on the channel
+        # TODO: channels tab removed, change this test to use another page with channel renderer
         'url': 'https://www.youtube.com/channel/UCiu-3thuViMebBjw_5nWYrA/channels',
         'info_dict': {
             'id': 'UCiu-3thuViMebBjw_5nWYrA',
@@ -1940,7 +1955,9 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
             },
         }],
         'params': {'extract_flat': True},
+        'skip': 'channels tab removed',
     }, {
+        # TODO: fix channel_is_verified extraction
         'url': 'https://www.youtube.com/@3blue1brown/about',
         'info_dict': {
             'id': '@3blue1brown',
@@ -1950,7 +1967,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
             'channel_id': 'UCYO_jab_esuFRV4b17AJtAw',
             'channel': '3Blue1Brown',
             'channel_url': 'https://www.youtube.com/channel/UCYO_jab_esuFRV4b17AJtAw',
-            'description': 'md5:4d1da95432004b7ba840ebc895b6b4c9',
+            'description': 'md5:602e3789e6a0cb7d9d352186b720e395',
             'uploader_url': 'https://www.youtube.com/@3blue1brown',
             'uploader_id': '@3blue1brown',
             'uploader': '3Blue1Brown',
@@ -1976,6 +1993,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
         'playlist_count': 5,
     }, {
         # Releases tab, with rich entry playlistRenderers (same as Podcasts tab)
+        # TODO: fix channel_is_verified extraction
         'url': 'https://www.youtube.com/@AHimitsu/releases',
         'info_dict': {
             'id': 'UCgFwu-j5-xNJml2FtTrrB3A',
@@ -2015,6 +2033,7 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
         'playlist_mincount': 100,
         'expected_warnings': [r'[Uu]navailable videos (are|will be) hidden'],
     }, {
+        # TODO: fix channel_is_verified extraction
         'note': 'Tags containing spaces',
         'url': 'https://www.youtube.com/channel/UC7_YxT-KID8kRbqZo7MyscQ',
         'playlist_count': 3,
@@ -2035,6 +2054,24 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
                      'challenges', 'sketches', 'scary games', 'funny games', 'rage games',
                      'mark fischbach'],
         },
+    }, {
+        # https://github.com/yt-dlp/yt-dlp/issues/12933
+        'note': 'streams tab, some scheduled streams. Empty intermediate response with only continuation - must follow',
+        'url': 'https://www.youtube.com/@sbcitygov/streams',
+        'playlist_mincount': 150,
+        'info_dict': {
+            'id': 'UCH6-qfQwlUgz9SAf05jvc_w',
+            'channel': 'sbcitygov',
+            'channel_id': 'UCH6-qfQwlUgz9SAf05jvc_w',
+            'title': 'sbcitygov - Live',
+            'channel_follower_count': int,
+            'description': 'md5:ca1a92059835c071e33b3db52f4a6d67',
+            'uploader_id': '@sbcitygov',
+            'uploader_url': 'https://www.youtube.com/@sbcitygov',
+            'uploader': 'sbcitygov',
+            'channel_url': 'https://www.youtube.com/channel/UCH6-qfQwlUgz9SAf05jvc_w',
+            'tags': [],
+        },
     }]
 
     @classmethod
diff --git a/yt_dlp/extractor/youtube/_video.py b/yt_dlp/extractor/youtube/_video.py
index 074a2a0d8..bcfe8b152 100644
--- a/yt_dlp/extractor/youtube/_video.py
+++ b/yt_dlp/extractor/youtube/_video.py
@@ -3646,6 +3646,8 @@ def feed_entry(name):
                 if 'sign in' in reason.lower():
                     reason = remove_end(reason, 'This helps protect our community. Learn more')
                     reason = f'{remove_end(reason.strip(), ".")}. {self._youtube_login_hint}'
+                elif get_first(playability_statuses, ('errorScreen', 'playerCaptchaViewModel', {dict})):
+                    reason += '. YouTube is requiring a captcha challenge before playback'
                 self.raise_no_formats(reason, expected=True)
 
         keywords = get_first(video_details, 'keywords', expected_type=list) or []
@@ -3874,7 +3876,7 @@ def process_language(container, base_url, lang_code, sub_name, query):
             if not traverse_obj(initial_data, 'contents'):
                 self.report_warning('Incomplete data received in embedded initial data; re-fetching using API.')
                 initial_data = None
-        if not initial_data:
+        if not initial_data and 'initial_data' not in self._configuration_arg('player_skip'):
             query = {'videoId': video_id}
             query.update(self._get_checkok_params())
             initial_data = self._extract_response(
diff --git a/yt_dlp/networking/__init__.py b/yt_dlp/networking/__init__.py
index 1eaa0ee5f..39158a8cc 100644
--- a/yt_dlp/networking/__init__.py
+++ b/yt_dlp/networking/__init__.py
@@ -3,6 +3,7 @@
 
 from .common import (
     HEADRequest,
+    PATCHRequest,
     PUTRequest,
     Request,
     RequestDirector,
diff --git a/yt_dlp/networking/common.py b/yt_dlp/networking/common.py
index ddceaa9a9..e33769422 100644
--- a/yt_dlp/networking/common.py
+++ b/yt_dlp/networking/common.py
@@ -505,6 +505,7 @@ def copy(self):
 
 
 HEADRequest = functools.partial(Request, method='HEAD')
+PATCHRequest = functools.partial(Request, method='PATCH')
 PUTRequest = functools.partial(Request, method='PUT')
 
 


Test patch:
diff --git a/test/helper.py b/test/helper.py
index 4169af799..e4cb478e2 100644
--- a/test/helper.py
+++ b/test/helper.py
@@ -136,7 +136,7 @@ def _iter_differences(got, expected, field):
             return
 
         if op == 'startswith':
-            if not val.startswith(got):
+            if not got.startswith(val):
                 yield field, f'should start with {val!r}, got {got!r}'
             return
 
diff --git a/test/test_networking.py b/test/test_networking.py
index 3ab60fe83..2f441fced 100644
--- a/test/test_networking.py
+++ b/test/test_networking.py
@@ -39,6 +39,7 @@
 from yt_dlp.dependencies import brotli, curl_cffi, requests, urllib3
 from yt_dlp.networking import (
     HEADRequest,
+    PATCHRequest,
     PUTRequest,
     Request,
     RequestDirector,
@@ -1856,6 +1857,7 @@ def test_method(self):
 
     def test_request_helpers(self):
         assert HEADRequest('http://example.com').method == 'HEAD'
+        assert PATCHRequest('http://example.com').method == 'PATCH'
         assert PUTRequest('http://example.com').method == 'PUT'
 
     def test_headers(self):