From 3c14e9191f3035b9a729d1d87bc0381f42de57cf Mon Sep 17 00:00:00 2001 From: voidptr_t Date: Sat, 11 Jan 2025 17:39:31 +0300 Subject: [PATCH 1/6] [ie/PlVideo] Add extractor (#10657) Closes #10311 Authored by: Sanceilaks, seproDev Co-authored-by: sepro --- yt_dlp/extractor/_extractors.py | 1 + yt_dlp/extractor/plvideo.py | 130 ++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 yt_dlp/extractor/plvideo.py diff --git a/yt_dlp/extractor/_extractors.py b/yt_dlp/extractor/_extractors.py index 967010826e..bbd6d21bd7 100644 --- a/yt_dlp/extractor/_extractors.py +++ b/yt_dlp/extractor/_extractors.py @@ -1551,6 +1551,7 @@ from .pluralsight import ( PluralsightIE, ) from .plutotv import PlutoTVIE +from .plvideo import PlVideoIE from .podbayfm import ( PodbayFMChannelIE, PodbayFMIE, diff --git a/yt_dlp/extractor/plvideo.py b/yt_dlp/extractor/plvideo.py new file mode 100644 index 0000000000..9351af10ae --- /dev/null +++ b/yt_dlp/extractor/plvideo.py @@ -0,0 +1,130 @@ +from .common import InfoExtractor +from ..utils import ( + float_or_none, + int_or_none, + parse_iso8601, + parse_resolution, + url_or_none, +) +from ..utils.traversal import traverse_obj + + +class PlVideoIE(InfoExtractor): + IE_DESC = 'Платформа' + _VALID_URL = r'https?://(?:www\.)?plvideo\.ru/(?:watch\?(?:[^#]+&)?v=|shorts/)(?P[\w-]+)' + _TESTS = [{ + 'url': 'https://plvideo.ru/watch?v=Y5JzUzkcQTMK', + 'md5': 'fe8e18aca892b3b31f3bf492169f8a26', + 'info_dict': { + 'id': 'Y5JzUzkcQTMK', + 'ext': 'mp4', + 'thumbnail': 'https://img.plvideo.ru/images/fp-2024-images/v/cover/37/dd/37dd00a4c96c77436ab737e85947abd7/original663a4a3bb713e5.33151959.jpg', + 'title': 'Presidente de Cuba llega a Moscú en una visita de trabajo', + 'channel': 'RT en Español', + 'channel_id': 'ZH4EKqunVDvo', + 'media_type': 'video', + 'comment_count': int, + 'tags': ['rusia', 'cuba', 'russia', 'miguel díaz-canel'], + 'description': 'md5:a1a395d900d77a86542a91ee0826c115', + 'released_timestamp': 1715096124, + 'channel_is_verified': True, + 'like_count': int, + 'timestamp': 1715095911, + 'duration': 44320, + 'view_count': int, + 'dislike_count': int, + 'upload_date': '20240507', + 'modified_date': '20240701', + 'channel_follower_count': int, + 'modified_timestamp': 1719824073, + }, + }, { + 'url': 'https://plvideo.ru/shorts/S3Uo9c-VLwFX', + 'md5': '7d8fa2279406c69d2fd2a6fc548a9805', + 'info_dict': { + 'id': 'S3Uo9c-VLwFX', + 'ext': 'mp4', + 'channel': 'Romaatom', + 'tags': 'count:22', + 'dislike_count': int, + 'upload_date': '20241130', + 'description': 'md5:452e6de219bf2f32bb95806c51c3b364', + 'duration': 58433, + 'modified_date': '20241130', + 'thumbnail': 'https://img.plvideo.ru/images/fp-2024-11-cover/S3Uo9c-VLwFX/f9318999-a941-482b-b700-2102a7049366.jpg', + 'media_type': 'shorts', + 'like_count': int, + 'modified_timestamp': 1732961458, + 'channel_is_verified': True, + 'channel_id': 'erJyyTIbmUd1', + 'timestamp': 1732961355, + 'comment_count': int, + 'title': 'Белоусов отменил приказы о кадровом резерве на гражданской службе', + 'channel_follower_count': int, + 'view_count': int, + 'released_timestamp': 1732961458, + }, + }] + + def _real_extract(self, url): + video_id = self._match_id(url) + + video_data = self._download_json( + f'https://api.g1.plvideo.ru/v1/videos/{video_id}?Aud=18', video_id) + + is_live = False + formats = [] + subtitles = {} + automatic_captions = {} + for quality, data in traverse_obj(video_data, ('item', 'profiles', {dict.items}, lambda _, v: url_or_none(v[1]['hls']))): + formats.append({ + 'format_id': quality, + 'ext': 'mp4', + 'protocol': 'm3u8_native', + **traverse_obj(data, { + 'url': 'hls', + 'fps': ('fps', {float_or_none}), + 'aspect_ratio': ('aspectRatio', {float_or_none}), + }), + **parse_resolution(quality), + }) + if livestream_url := traverse_obj(video_data, ('item', 'livestream', 'url', {url_or_none})): + is_live = True + formats.extend(self._extract_m3u8_formats(livestream_url, video_id, 'mp4', live=True)) + for lang, url in traverse_obj(video_data, ('item', 'subtitles', {dict.items}, lambda _, v: url_or_none(v[1]))): + if lang.endswith('-auto'): + automatic_captions.setdefault(lang[:-5], []).append({ + 'url': url, + }) + else: + subtitles.setdefault(lang, []).append({ + 'url': url, + }) + + return { + 'id': video_id, + 'formats': formats, + 'subtitles': subtitles, + 'automatic_captions': automatic_captions, + 'is_live': is_live, + **traverse_obj(video_data, ('item', { + 'id': ('id', {str}), + 'title': ('title', {str}), + 'description': ('description', {str}), + 'thumbnail': ('cover', 'paths', 'original', 'src', {url_or_none}), + 'duration': ('uploadFile', 'videoDuration', {int_or_none}), + 'channel': ('channel', 'name', {str}), + 'channel_id': ('channel', 'id', {str}), + 'channel_follower_count': ('channel', 'stats', 'subscribers', {int_or_none}), + 'channel_is_verified': ('channel', 'verified', {bool}), + 'tags': ('tags', ..., {str}), + 'timestamp': ('createdAt', {parse_iso8601}), + 'released_timestamp': ('publishedAt', {parse_iso8601}), + 'modified_timestamp': ('updatedAt', {parse_iso8601}), + 'view_count': ('stats', 'viewTotalCount', {int_or_none}), + 'like_count': ('stats', 'likeCount', {int_or_none}), + 'dislike_count': ('stats', 'dislikeCount', {int_or_none}), + 'comment_count': ('stats', 'commentCount', {int_or_none}), + 'media_type': ('type', {str}), + })), + } From 763ed06ee69f13949397897bd42ff2ec3dc3d384 Mon Sep 17 00:00:00 2001 From: HobbyistDev <105957301+HobbyistDev@users.noreply.github.com> Date: Sun, 12 Jan 2025 01:25:18 +0800 Subject: [PATCH 2/6] [ie/XiaoHongShu] Extend `_VALID_URL` (#11806) Closes #11797 Authored by: HobbyistDev --- yt_dlp/extractor/xiaohongshu.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/yt_dlp/extractor/xiaohongshu.py b/yt_dlp/extractor/xiaohongshu.py index 1280ca6a9c..46543b823e 100644 --- a/yt_dlp/extractor/xiaohongshu.py +++ b/yt_dlp/extractor/xiaohongshu.py @@ -10,7 +10,7 @@ from ..utils.traversal import traverse_obj class XiaoHongShuIE(InfoExtractor): - _VALID_URL = r'https?://www\.xiaohongshu\.com/explore/(?P[\da-f]+)' + _VALID_URL = r'https?://www\.xiaohongshu\.com/(?:explore|discovery/item)/(?P[\da-f]+)' IE_DESC = '小红书' _TESTS = [{ 'url': 'https://www.xiaohongshu.com/explore/6411cf99000000001300b6d9', @@ -25,6 +25,18 @@ class XiaoHongShuIE(InfoExtractor): 'duration': 101.726, 'thumbnail': r're:https?://sns-webpic-qc\.xhscdn\.com/\d+/[a-z0-9]+/[\w]+', }, + }, { + 'url': 'https://www.xiaohongshu.com/discovery/item/674051740000000007027a15?xsec_token=CBgeL8Dxd1ZWBhwqRd568gAZ_iwG-9JIf9tnApNmteU2E=', + 'info_dict': { + 'id': '674051740000000007027a15', + 'ext': 'mp4', + 'title': '相互喜欢就可以了', + 'uploader_id': '63439913000000001901f49a', + 'duration': 28.073, + 'description': '#广州[话题]# #深圳[话题]# #香港[话题]# #街头采访[话题]# #是你喜欢的类型[话题]#', + 'thumbnail': r're:https?://sns-webpic-qc\.xhscdn\.com/\d+/[\da-f]+/[^/]+', + 'tags': ['广州', '深圳', '香港', '街头采访', '是你喜欢的类型'], + }, }] def _real_extract(self, url): From 1f4e1e85a27c5b43e34d7706cfd88ffce1b56a4a Mon Sep 17 00:00:00 2001 From: Paul Storkman <111140294+Strkmn@users.noreply.github.com> Date: Sat, 11 Jan 2025 19:51:16 +0100 Subject: [PATCH 3/6] [core] Validate retries values are non-negative (#11927) Closes #11926 Authored by: Strkmn --- yt_dlp/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/yt_dlp/__init__.py b/yt_dlp/__init__.py index 20111175b1..c76fe27483 100644 --- a/yt_dlp/__init__.py +++ b/yt_dlp/__init__.py @@ -261,9 +261,11 @@ def validate_options(opts): elif value in ('inf', 'infinite'): return float('inf') try: - return int(value) + int_value = int(value) except (TypeError, ValueError): validate(False, f'{name} retry count', value) + validate_positive(f'{name} retry count', int_value) + return int_value opts.retries = parse_retries('download', opts.retries) opts.fragment_retries = parse_retries('fragment', opts.fragment_retries) From 8346b549150003df988538e54c9d8bc4de568979 Mon Sep 17 00:00:00 2001 From: bashonly <88596187+bashonly@users.noreply.github.com> Date: Sat, 11 Jan 2025 13:05:23 -0600 Subject: [PATCH 4/6] Fix filename sanitization with `--no-windows-filenames` (#11988) Fix bug in 6fc85f617a5850307fd5b258477070e6ee177796 Closes #11987 Authored by: bashonly --- yt_dlp/YoutubeDL.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yt_dlp/YoutubeDL.py b/yt_dlp/YoutubeDL.py index 764baf3a00..178c5b9515 100644 --- a/yt_dlp/YoutubeDL.py +++ b/yt_dlp/YoutubeDL.py @@ -1323,7 +1323,7 @@ class YoutubeDL: elif (sys.platform != 'win32' and not self.params.get('restrictfilenames') and self.params.get('windowsfilenames') is False): def sanitize(key, value): - return value.replace('/', '\u29F8').replace('\0', '') + return str(value).replace('/', '\u29F8').replace('\0', '') else: def sanitize(key, value): return filename_sanitizer(key, value, restricted=self.params.get('restrictfilenames')) From 712d2abb32f59b2d246be2901255f84f1a4c30b3 Mon Sep 17 00:00:00 2001 From: coletdjnz Date: Sun, 12 Jan 2025 15:01:13 +1300 Subject: [PATCH 5/6] [ie/youtube] Use `tv` instead of `mweb` client by default (#12059) Authored by: coletdjnz --- yt_dlp/extractor/youtube.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/yt_dlp/extractor/youtube.py b/yt_dlp/extractor/youtube.py index 1e83e41b8f..f414d9d030 100644 --- a/yt_dlp/extractor/youtube.py +++ b/yt_dlp/extractor/youtube.py @@ -256,11 +256,12 @@ INNERTUBE_CLIENTS = { 'client': { 'clientName': 'MWEB', 'clientVersion': '2.20241202.07.00', - # mweb does not require PO Token with this UA + # mweb previously did not require PO Token with this UA 'userAgent': 'Mozilla/5.0 (iPad; CPU OS 16_7_10 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1,gzip(gfe)', }, }, 'INNERTUBE_CONTEXT_CLIENT_NAME': 2, + 'REQUIRE_PO_TOKEN': True, 'SUPPORTS_COOKIES': True, }, 'tv': { @@ -1356,8 +1357,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor): '401': {'ext': 'mp4', 'height': 2160, 'format_note': 'DASH video', 'vcodec': 'av01.0.12M.08'}, } _SUBTITLE_FORMATS = ('json3', 'srv1', 'srv2', 'srv3', 'ttml', 'vtt') - _DEFAULT_CLIENTS = ('ios', 'mweb') - _DEFAULT_AUTHED_CLIENTS = ('web_creator', 'mweb') + _DEFAULT_CLIENTS = ('ios', 'tv') + _DEFAULT_AUTHED_CLIENTS = ('web_creator', 'tv') _GEO_BYPASS = False From 75079f4e3f7dce49b61ef01da7adcd9876a0ca3b Mon Sep 17 00:00:00 2001 From: coletdjnz Date: Sun, 12 Jan 2025 15:02:57 +1300 Subject: [PATCH 6/6] [ie/youtube] Refactor cookie auth (#11989) Authored by: coletdjnz --- yt_dlp/extractor/youtube.py | 178 +++++++++++++++++++++++++----------- 1 file changed, 124 insertions(+), 54 deletions(-) diff --git a/yt_dlp/extractor/youtube.py b/yt_dlp/extractor/youtube.py index f414d9d030..e16ec43edd 100644 --- a/yt_dlp/extractor/youtube.py +++ b/yt_dlp/extractor/youtube.py @@ -32,7 +32,6 @@ from ..utils import ( classproperty, clean_html, datetime_from_str, - dict_get, filesize_from_tbr, filter_dict, float_or_none, @@ -568,9 +567,15 @@ class YoutubeBaseInfoExtractor(InfoExtractor): pref.update({'hl': self._preferred_lang or 'en', 'tz': 'UTC'}) self._set_cookie('.youtube.com', name='PREF', value=urllib.parse.urlencode(pref)) + def _initialize_cookie_auth(self): + yt_sapisid, yt_1psapisid, yt_3psapisid = self._get_sid_cookies() + if yt_sapisid or yt_1psapisid or yt_3psapisid: + self.write_debug('Found YouTube account cookies') + def _real_initialize(self): self._initialize_pref() self._initialize_consent() + self._initialize_cookie_auth() self._check_login_required() def _perform_login(self, username, password): @@ -628,32 +633,63 @@ class YoutubeBaseInfoExtractor(InfoExtractor): client_context.update({'hl': self._preferred_lang or 'en', 'timeZone': 'UTC', 'utcOffsetMinutes': 0}) return context - _SAPISID = None + @staticmethod + def _make_sid_authorization(scheme, sid, origin, additional_parts): + timestamp = str(round(time.time())) - def _generate_sapisidhash_header(self, origin='https://www.youtube.com'): - time_now = round(time.time()) - if self._SAPISID is None: - yt_cookies = self._get_cookies('https://www.youtube.com') - # Sometimes SAPISID cookie isn't present but __Secure-3PAPISID is. - # See: https://github.com/yt-dlp/yt-dlp/issues/393 - sapisid_cookie = dict_get( - yt_cookies, ('__Secure-3PAPISID', 'SAPISID')) - if sapisid_cookie and sapisid_cookie.value: - self._SAPISID = sapisid_cookie.value - self.write_debug('Extracted SAPISID cookie') - # SAPISID cookie is required if not already present - if not yt_cookies.get('SAPISID'): - self.write_debug('Copying __Secure-3PAPISID cookie to SAPISID cookie') - self._set_cookie( - '.youtube.com', 'SAPISID', self._SAPISID, secure=True, expire_time=time_now + 3600) - else: - self._SAPISID = False - if not self._SAPISID: + hash_parts = [] + if additional_parts: + hash_parts.append(':'.join(additional_parts.values())) + hash_parts.extend([timestamp, sid, origin]) + sidhash = hashlib.sha1(' '.join(hash_parts).encode()).hexdigest() + + parts = [timestamp, sidhash] + if additional_parts: + parts.append(''.join(additional_parts)) + + return f'{scheme} {"_".join(parts)}' + + def _get_sid_cookies(self): + """ + Get SAPISID, 1PSAPISID, 3PSAPISID cookie values + @returns sapisid, 1psapisid, 3psapisid + """ + yt_cookies = self._get_cookies('https://www.youtube.com') + yt_sapisid = try_call(lambda: yt_cookies['SAPISID'].value) + yt_3papisid = try_call(lambda: yt_cookies['__Secure-3PAPISID'].value) + yt_1papisid = try_call(lambda: yt_cookies['__Secure-1PAPISID'].value) + + # Sometimes SAPISID cookie isn't present but __Secure-3PAPISID is. + # YouTube also falls back to __Secure-3PAPISID if SAPISID is missing. + # See: https://github.com/yt-dlp/yt-dlp/issues/393 + + return yt_sapisid or yt_3papisid, yt_1papisid, yt_3papisid + + def _get_sid_authorization_header(self, origin='https://www.youtube.com', user_session_id=None): + """ + Generate API Session ID Authorization for Innertube requests. Assumes all requests are secure (https). + @param origin: Origin URL + @param user_session_id: Optional User Session ID + @return: Authorization header value + """ + + authorizations = [] + additional_parts = {} + if user_session_id: + additional_parts['u'] = user_session_id + + yt_sapisid, yt_1psapisid, yt_3psapisid = self._get_sid_cookies() + + for scheme, sid in (('SAPISIDHASH', yt_sapisid), + ('SAPISID1PHASH', yt_1psapisid), + ('SAPISID3PHASH', yt_3psapisid)): + if sid: + authorizations.append(self._make_sid_authorization(scheme, sid, origin, additional_parts)) + + if not authorizations: return None - # SAPISIDHASH algorithm from https://stackoverflow.com/a/32065323 - sapisidhash = hashlib.sha1( - f'{time_now} {self._SAPISID} {origin}'.encode()).hexdigest() - return f'SAPISIDHASH {time_now}_{sapisidhash}' + + return ' '.join(authorizations) def _call_api(self, ep, query, video_id, fatal=True, headers=None, note='Downloading API JSON', errnote='Unable to download API page', @@ -689,26 +725,48 @@ class YoutubeBaseInfoExtractor(InfoExtractor): if session_index is not None: return session_index - def _data_sync_id_to_delegated_session_id(self, data_sync_id): - if not data_sync_id: - return - # datasyncid is of the form "channel_syncid||user_syncid" for secondary channel - # and just "user_syncid||" for primary channel. We only want the channel_syncid - channel_syncid, _, user_syncid = data_sync_id.partition('||') - if user_syncid: - return channel_syncid - - def _extract_account_syncid(self, *args): + @staticmethod + def _parse_data_sync_id(data_sync_id): """ - Extract current session ID required to download private playlists of secondary channels + Parse data_sync_id into delegated_session_id and user_session_id. + + data_sync_id is of the form "delegated_session_id||user_session_id" for secondary channel + and just "user_session_id||" for primary channel. + + @param data_sync_id: data_sync_id string + @return: Tuple of (delegated_session_id, user_session_id) + """ + if not data_sync_id: + return None, None + first, _, second = data_sync_id.partition('||') + if second: + return first, second + return None, first + + def _extract_delegated_session_id(self, *args): + """ + Extract current delegated session ID required to download private playlists of secondary channels @params response and/or ytcfg + @return: delegated session ID """ # ytcfg includes channel_syncid if on secondary channel if delegated_sid := traverse_obj(args, (..., 'DELEGATED_SESSION_ID', {str}, any)): return delegated_sid data_sync_id = self._extract_data_sync_id(*args) - return self._data_sync_id_to_delegated_session_id(data_sync_id) + return self._parse_data_sync_id(data_sync_id)[0] + + def _extract_user_session_id(self, *args): + """ + Extract current user session ID + @params response and/or ytcfg + @return: user session ID + """ + if user_sid := traverse_obj(args, (..., 'USER_SESSION_ID', {str}, any)): + return user_sid + + data_sync_id = self._extract_data_sync_id(*args) + return self._parse_data_sync_id(data_sync_id)[1] def _extract_data_sync_id(self, *args): """ @@ -735,7 +793,7 @@ class YoutubeBaseInfoExtractor(InfoExtractor): @functools.cached_property def is_authenticated(self): - return bool(self._generate_sapisidhash_header()) + return bool(self._get_sid_authorization_header()) def extract_ytcfg(self, video_id, webpage): if not webpage: @@ -745,25 +803,28 @@ class YoutubeBaseInfoExtractor(InfoExtractor): r'ytcfg\.set\s*\(\s*({.+?})\s*\)\s*;', webpage, 'ytcfg', default='{}'), video_id, fatal=False) or {} - def _generate_cookie_auth_headers(self, *, ytcfg=None, account_syncid=None, session_index=None, origin=None, **kwargs): + def _generate_cookie_auth_headers(self, *, ytcfg=None, delegated_session_id=None, user_session_id=None, session_index=None, origin=None, **kwargs): headers = {} - account_syncid = account_syncid or self._extract_account_syncid(ytcfg) - if account_syncid: - headers['X-Goog-PageId'] = account_syncid + delegated_session_id = delegated_session_id or self._extract_delegated_session_id(ytcfg) + if delegated_session_id: + headers['X-Goog-PageId'] = delegated_session_id if session_index is None: session_index = self._extract_session_index(ytcfg) - if account_syncid or session_index is not None: + if delegated_session_id or session_index is not None: headers['X-Goog-AuthUser'] = session_index if session_index is not None else 0 - auth = self._generate_sapisidhash_header(origin) + auth = self._get_sid_authorization_header(origin, user_session_id=user_session_id or self._extract_user_session_id(ytcfg)) if auth is not None: headers['Authorization'] = auth headers['X-Origin'] = origin + if traverse_obj(ytcfg, 'LOGGED_IN', expected_type=bool): + headers['X-Youtube-Bootstrap-Logged-In'] = 'true' + return headers def generate_api_headers( - self, *, ytcfg=None, account_syncid=None, session_index=None, + self, *, ytcfg=None, delegated_session_id=None, user_session_id=None, session_index=None, visitor_data=None, api_hostname=None, default_client='web', **kwargs): origin = 'https://' + (self._select_api_hostname(api_hostname, default_client)) @@ -774,7 +835,12 @@ class YoutubeBaseInfoExtractor(InfoExtractor): 'Origin': origin, 'X-Goog-Visitor-Id': visitor_data or self._extract_visitor_data(ytcfg), 'User-Agent': self._ytcfg_get_safe(ytcfg, lambda x: x['INNERTUBE_CONTEXT']['client']['userAgent'], default_client=default_client), - **self._generate_cookie_auth_headers(ytcfg=ytcfg, account_syncid=account_syncid, session_index=session_index, origin=origin), + **self._generate_cookie_auth_headers( + ytcfg=ytcfg, + delegated_session_id=delegated_session_id, + user_session_id=user_session_id, + session_index=session_index, + origin=origin), } return filter_dict(headers) @@ -3837,9 +3903,13 @@ class YoutubeIE(YoutubeBaseInfoExtractor): default_client=client, visitor_data=visitor_data, session_index=self._extract_session_index(master_ytcfg, player_ytcfg), - account_syncid=( - self._data_sync_id_to_delegated_session_id(data_sync_id) - or self._extract_account_syncid(master_ytcfg, initial_pr, player_ytcfg) + delegated_session_id=( + self._parse_data_sync_id(data_sync_id)[0] + or self._extract_delegated_session_id(master_ytcfg, initial_pr, player_ytcfg) + ), + user_session_id=( + self._parse_data_sync_id(data_sync_id)[1] + or self._extract_user_session_id(master_ytcfg, initial_pr, player_ytcfg) ), ) @@ -5351,7 +5421,7 @@ class YoutubeTabBaseInfoExtractor(YoutubeBaseInfoExtractor): if not continuation_list[0]: continuation_list[0] = self._extract_continuation(parent_renderer) - def _entries(self, tab, item_id, ytcfg, account_syncid, visitor_data): + def _entries(self, tab, item_id, ytcfg, delegated_session_id, visitor_data): continuation_list = [None] extract_entries = lambda x: self._extract_entries(x, continuation_list) tab_content = try_get(tab, lambda x: x['content'], dict) @@ -5372,7 +5442,7 @@ class YoutubeTabBaseInfoExtractor(YoutubeBaseInfoExtractor): break seen_continuations.add(continuation_token) headers = self.generate_api_headers( - ytcfg=ytcfg, account_syncid=account_syncid, visitor_data=visitor_data) + ytcfg=ytcfg, delegated_session_id=delegated_session_id, visitor_data=visitor_data) response = self._extract_response( item_id=f'{item_id} page {page_num}', query=continuation, headers=headers, ytcfg=ytcfg, @@ -5442,7 +5512,7 @@ class YoutubeTabBaseInfoExtractor(YoutubeBaseInfoExtractor): return self.playlist_result( self._entries( selected_tab, metadata['id'], ytcfg, - self._extract_account_syncid(ytcfg, data), + self._extract_delegated_session_id(ytcfg, data), self._extract_visitor_data(data, ytcfg)), **metadata) @@ -5594,7 +5664,7 @@ class YoutubeTabBaseInfoExtractor(YoutubeBaseInfoExtractor): watch_endpoint = try_get( playlist, lambda x: x['contents'][-1]['playlistPanelVideoRenderer']['navigationEndpoint']['watchEndpoint']) headers = self.generate_api_headers( - ytcfg=ytcfg, account_syncid=self._extract_account_syncid(ytcfg, data), + ytcfg=ytcfg, delegated_session_id=self._extract_delegated_session_id(ytcfg, data), visitor_data=self._extract_visitor_data(response, data, ytcfg)) query = { 'playlistId': playlist_id, @@ -5692,7 +5762,7 @@ class YoutubeTabBaseInfoExtractor(YoutubeBaseInfoExtractor): if not is_playlist: return headers = self.generate_api_headers( - ytcfg=ytcfg, account_syncid=self._extract_account_syncid(ytcfg, data), + ytcfg=ytcfg, delegated_session_id=self._extract_delegated_session_id(ytcfg, data), visitor_data=self._extract_visitor_data(data, ytcfg)) query = { 'params': 'wgYCCAA=',