Compare commits

...

2 Commits

Author SHA1 Message Date
bashonly
61c9a938b3
[ie/youtube] Cache signature timestamps (#13047)
Closes #12825
Authored by: bashonly
2025-04-30 01:15:17 +00:00
bashonly
fd8394bc50
[ie/youtube] Improve warning for SABR-only/SSAP player responses (#13049)
Ref: https://github.com/yt-dlp/yt-dlp/issues/12482

Authored by: bashonly
2025-04-30 01:13:35 +00:00

View File

@ -2122,23 +2122,23 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
return ret return ret
return inner return inner
def _load_nsig_code_from_cache(self, player_url): def _load_player_data_from_cache(self, name, player_url):
cache_id = ('youtube-nsig', self._player_js_cache_key(player_url)) cache_id = (f'youtube-{name}', self._player_js_cache_key(player_url))
if func_code := self._player_cache.get(cache_id): if data := self._player_cache.get(cache_id):
return func_code return data
func_code = self.cache.load(*cache_id, min_ver='2025.03.31') data = self.cache.load(*cache_id, min_ver='2025.03.31')
if func_code: if data:
self._player_cache[cache_id] = func_code self._player_cache[cache_id] = data
return func_code return data
def _store_nsig_code_to_cache(self, player_url, func_code): def _store_player_data_to_cache(self, name, player_url, data):
cache_id = ('youtube-nsig', self._player_js_cache_key(player_url)) cache_id = (f'youtube-{name}', self._player_js_cache_key(player_url))
if cache_id not in self._player_cache: if cache_id not in self._player_cache:
self.cache.store(*cache_id, func_code) self.cache.store(*cache_id, data)
self._player_cache[cache_id] = func_code self._player_cache[cache_id] = data
def _decrypt_signature(self, s, video_id, player_url): def _decrypt_signature(self, s, video_id, player_url):
"""Turn the encrypted s field into a working signature""" """Turn the encrypted s field into a working signature"""
@ -2181,7 +2181,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
self.write_debug(f'Decrypted nsig {s} => {ret}') self.write_debug(f'Decrypted nsig {s} => {ret}')
# Only cache nsig func JS code to disk if successful, and only once # Only cache nsig func JS code to disk if successful, and only once
self._store_nsig_code_to_cache(player_url, func_code) self._store_player_data_to_cache('nsig', player_url, func_code)
return ret return ret
def _extract_n_function_name(self, jscode, player_url=None): def _extract_n_function_name(self, jscode, player_url=None):
@ -2300,7 +2300,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
def _extract_n_function_code(self, video_id, player_url): def _extract_n_function_code(self, video_id, player_url):
player_id = self._extract_player_info(player_url) player_id = self._extract_player_info(player_url)
func_code = self._load_nsig_code_from_cache(player_url) func_code = self._load_player_data_from_cache('nsig', player_url)
jscode = func_code or self._load_player(video_id, player_url) jscode = func_code or self._load_player(video_id, player_url)
jsi = JSInterpreter(jscode) jsi = JSInterpreter(jscode)
@ -2336,23 +2336,27 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
Extract signatureTimestamp (sts) Extract signatureTimestamp (sts)
Required to tell API what sig/player version is in use. Required to tell API what sig/player version is in use.
""" """
sts = None if sts := traverse_obj(ytcfg, ('STS', {int_or_none})):
if isinstance(ytcfg, dict): return sts
sts = int_or_none(ytcfg.get('STS'))
if not sts: if not player_url:
# Attempt to extract from player error_msg = 'Cannot extract signature timestamp without player url'
if player_url is None:
error_msg = 'Cannot extract signature timestamp without player_url.'
if fatal: if fatal:
raise ExtractorError(error_msg) raise ExtractorError(error_msg)
self.report_warning(error_msg) self.report_warning(error_msg)
return return None
code = self._load_player(video_id, player_url, fatal=fatal)
if code: sts = self._load_player_data_from_cache('sts', player_url)
if sts:
return sts
if code := self._load_player(video_id, player_url, fatal=fatal):
sts = int_or_none(self._search_regex( sts = int_or_none(self._search_regex(
r'(?:signatureTimestamp|sts)\s*:\s*(?P<sts>[0-9]{5})', code, r'(?:signatureTimestamp|sts)\s*:\s*(?P<sts>[0-9]{5})', code,
'JS player signature timestamp', group='sts', fatal=fatal)) 'JS player signature timestamp', group='sts', fatal=fatal))
if sts:
self._store_player_data_to_cache('sts', player_url, sts)
return sts return sts
def _mark_watched(self, video_id, player_responses): def _mark_watched(self, video_id, player_responses):
@ -3234,12 +3238,16 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
fmt_url = url_or_none(try_get(sc, lambda x: x['url'][0])) fmt_url = url_or_none(try_get(sc, lambda x: x['url'][0]))
encrypted_sig = try_get(sc, lambda x: x['s'][0]) encrypted_sig = try_get(sc, lambda x: x['s'][0])
if not all((sc, fmt_url, player_url, encrypted_sig)): if not all((sc, fmt_url, player_url, encrypted_sig)):
self.report_warning( msg = f'Some {client_name} client https formats have been skipped as they are missing a url. '
f'Some {client_name} client https formats have been skipped as they are missing a url. ' if client_name == 'web':
f'{"Your account" if self.is_authenticated else "The current session"} may have ' msg += 'YouTube is forcing SABR streaming for this client. '
f'the SSAP (server-side ads) experiment which interferes with yt-dlp. ' else:
f'Please see https://github.com/yt-dlp/yt-dlp/issues/12482 for more details.', msg += (
video_id, only_once=True) f'YouTube may have enabled the SABR-only or Server-Side Ad Placement experiment for '
f'{"your account" if self.is_authenticated else "the current session"}. '
)
msg += 'See https://github.com/yt-dlp/yt-dlp/issues/12482 for more details'
self.report_warning(msg, video_id, only_once=True)
continue continue
try: try:
fmt_url += '&{}={}'.format( fmt_url += '&{}={}'.format(