mirror of
https://github.com/yt-dlp/yt-dlp
synced 2025-04-22 14:55:07 -05:00
[ie/youtube] Add player_js_variant
extractor-arg (#12767)
- Always distinguish between different JS variants' code/functions - Change naming scheme for nsig and sigfuncs in disk cache Authored by: bashonly
This commit is contained in:
parent
e465b078ea
commit
07f04005e4
@ -1782,6 +1782,7 @@ The following extractors use this feature:
|
|||||||
* `data_sync_id`: Overrides the account Data Sync ID used in Innertube API requests. This may be needed if you are using an account with `youtube:player_skip=webpage,configs` or `youtubetab:skip=webpage`
|
* `data_sync_id`: Overrides the account Data Sync ID used in Innertube API requests. This may be needed if you are using an account with `youtube:player_skip=webpage,configs` or `youtubetab:skip=webpage`
|
||||||
* `visitor_data`: Overrides the Visitor Data used in Innertube API requests. This should be used with `player_skip=webpage,configs` and without cookies. Note: this may have adverse effects if used improperly. If a session from a browser is wanted, you should pass cookies instead (which contain the Visitor ID)
|
* `visitor_data`: Overrides the Visitor Data used in Innertube API requests. This should be used with `player_skip=webpage,configs` and without cookies. Note: this may have adverse effects if used improperly. If a session from a browser is wanted, you should pass cookies instead (which contain the Visitor ID)
|
||||||
* `po_token`: Proof of Origin (PO) Token(s) to use. Comma seperated list of PO Tokens in the format `CLIENT.CONTEXT+PO_TOKEN`, e.g. `youtube:po_token=web.gvs+XXX,web.player=XXX,web_safari.gvs+YYY`. Context can be either `gvs` (Google Video Server URLs) or `player` (Innertube player request)
|
* `po_token`: Proof of Origin (PO) Token(s) to use. Comma seperated list of PO Tokens in the format `CLIENT.CONTEXT+PO_TOKEN`, e.g. `youtube:po_token=web.gvs+XXX,web.player=XXX,web_safari.gvs+YYY`. Context can be either `gvs` (Google Video Server URLs) or `player` (Innertube player request)
|
||||||
|
* `player_js_variant`: The player javascript variant to use for signature and nsig deciphering. The known variants are: `main`, `tce`, `tv`, `tv_es6`, `phone`, `tablet`. Only `main` is recommended as a possible workaround; the others are for debugging purposes. The default is to use what is prescribed by the site, and can be selected with `actual`
|
||||||
|
|
||||||
#### youtubetab (YouTube playlists, channels, feeds, etc.)
|
#### youtubetab (YouTube playlists, channels, feeds, etc.)
|
||||||
* `skip`: One or more of `webpage` (skip initial webpage download), `authcheck` (allow the download of playlists requiring authentication when no initial webpage is downloaded. This may cause unwanted behavior, see [#1122](https://github.com/yt-dlp/yt-dlp/pull/1122) for more details)
|
* `skip`: One or more of `webpage` (skip initial webpage download), `authcheck` (allow the download of playlists requiring authentication when no initial webpage is downloaded. This may cause unwanted behavior, see [#1122](https://github.com/yt-dlp/yt-dlp/pull/1122) for more details)
|
||||||
|
@ -1761,6 +1761,16 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
_PLAYER_JS_VARIANT_MAP = {
|
||||||
|
'main': 'player_ias.vflset/en_US/base.js',
|
||||||
|
'tce': 'player_ias_tce.vflset/en_US/base.js',
|
||||||
|
'tv': 'tv-player-ias.vflset/tv-player-ias.js',
|
||||||
|
'tv_es6': 'tv-player-es6.vflset/tv-player-es6.js',
|
||||||
|
'phone': 'player-plasma-ias-phone-en_US.vflset/base.js',
|
||||||
|
'tablet': 'player-plasma-ias-tablet-en_US.vflset/base.js',
|
||||||
|
}
|
||||||
|
_INVERSE_PLAYER_JS_VARIANT_MAP = {value: key for key, value in _PLAYER_JS_VARIANT_MAP.items()}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def suitable(cls, url):
|
def suitable(cls, url):
|
||||||
from yt_dlp.utils import parse_qs
|
from yt_dlp.utils import parse_qs
|
||||||
@ -1940,6 +1950,21 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
|||||||
get_all=False, expected_type=str)
|
get_all=False, expected_type=str)
|
||||||
if not player_url:
|
if not player_url:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
requested_js_variant = self._configuration_arg('player_js_variant', [''])[0] or 'actual'
|
||||||
|
if requested_js_variant in self._PLAYER_JS_VARIANT_MAP:
|
||||||
|
player_id = self._extract_player_info(player_url)
|
||||||
|
original_url = player_url
|
||||||
|
player_url = f'/s/player/{player_id}/{self._PLAYER_JS_VARIANT_MAP[requested_js_variant]}'
|
||||||
|
if original_url != player_url:
|
||||||
|
self.write_debug(
|
||||||
|
f'Forcing "{requested_js_variant}" player JS variant for player {player_id}\n'
|
||||||
|
f' original url = {original_url}', only_once=True)
|
||||||
|
elif requested_js_variant != 'actual':
|
||||||
|
self.report_warning(
|
||||||
|
f'Invalid player JS variant name "{requested_js_variant}" requested. '
|
||||||
|
f'Valid choices are: {", ".join(self._PLAYER_JS_VARIANT_MAP)}', only_once=True)
|
||||||
|
|
||||||
return urljoin('https://www.youtube.com', player_url)
|
return urljoin('https://www.youtube.com', player_url)
|
||||||
|
|
||||||
def _download_player_url(self, video_id, fatal=False):
|
def _download_player_url(self, video_id, fatal=False):
|
||||||
@ -1954,6 +1979,17 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
|||||||
if player_version:
|
if player_version:
|
||||||
return f'https://www.youtube.com/s/player/{player_version}/player_ias.vflset/en_US/base.js'
|
return f'https://www.youtube.com/s/player/{player_version}/player_ias.vflset/en_US/base.js'
|
||||||
|
|
||||||
|
def _player_js_cache_key(self, player_url):
|
||||||
|
player_id = self._extract_player_info(player_url)
|
||||||
|
player_path = remove_start(urllib.parse.urlparse(player_url).path, f'/s/player/{player_id}/')
|
||||||
|
variant = self._INVERSE_PLAYER_JS_VARIANT_MAP.get(player_path)
|
||||||
|
if not variant:
|
||||||
|
self.write_debug(
|
||||||
|
f'Unable to determine player JS variant\n'
|
||||||
|
f' player = {player_url}', only_once=True)
|
||||||
|
variant = re.sub(r'[^a-zA-Z0-9]', '_', remove_end(player_path, '.js'))
|
||||||
|
return join_nonempty(player_id, variant)
|
||||||
|
|
||||||
def _signature_cache_id(self, example_sig):
|
def _signature_cache_id(self, example_sig):
|
||||||
""" Return a string representation of a signature """
|
""" Return a string representation of a signature """
|
||||||
return '.'.join(str(len(part)) for part in example_sig.split('.'))
|
return '.'.join(str(len(part)) for part in example_sig.split('.'))
|
||||||
@ -1969,25 +2005,24 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
|||||||
return id_m.group('id')
|
return id_m.group('id')
|
||||||
|
|
||||||
def _load_player(self, video_id, player_url, fatal=True):
|
def _load_player(self, video_id, player_url, fatal=True):
|
||||||
player_id = self._extract_player_info(player_url)
|
player_js_key = self._player_js_cache_key(player_url)
|
||||||
if player_id not in self._code_cache:
|
if player_js_key not in self._code_cache:
|
||||||
code = self._download_webpage(
|
code = self._download_webpage(
|
||||||
player_url, video_id, fatal=fatal,
|
player_url, video_id, fatal=fatal,
|
||||||
note='Downloading player ' + player_id,
|
note=f'Downloading player {player_js_key}',
|
||||||
errnote=f'Download of {player_url} failed')
|
errnote=f'Download of {player_js_key} failed')
|
||||||
if code:
|
if code:
|
||||||
self._code_cache[player_id] = code
|
self._code_cache[player_js_key] = code
|
||||||
return self._code_cache.get(player_id)
|
return self._code_cache.get(player_js_key)
|
||||||
|
|
||||||
def _extract_signature_function(self, video_id, player_url, example_sig):
|
def _extract_signature_function(self, video_id, player_url, example_sig):
|
||||||
player_id = self._extract_player_info(player_url)
|
|
||||||
|
|
||||||
# Read from filesystem cache
|
# Read from filesystem cache
|
||||||
func_id = f'js_{player_id}_{self._signature_cache_id(example_sig)}'
|
func_id = join_nonempty(
|
||||||
|
self._player_js_cache_key(player_url), self._signature_cache_id(example_sig))
|
||||||
assert os.path.basename(func_id) == func_id
|
assert os.path.basename(func_id) == func_id
|
||||||
|
|
||||||
self.write_debug(f'Extracting signature function {func_id}')
|
self.write_debug(f'Extracting signature function {func_id}')
|
||||||
cache_spec, code = self.cache.load('youtube-sigfuncs', func_id, min_ver='2025.03.27'), None
|
cache_spec, code = self.cache.load('youtube-sigfuncs', func_id, min_ver='2025.03.31'), None
|
||||||
|
|
||||||
if not cache_spec:
|
if not cache_spec:
|
||||||
code = self._load_player(video_id, player_url)
|
code = self._load_player(video_id, player_url)
|
||||||
@ -2085,22 +2120,22 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
|||||||
return ret
|
return ret
|
||||||
return inner
|
return inner
|
||||||
|
|
||||||
def _load_nsig_code_from_cache(self, player_id):
|
def _load_nsig_code_from_cache(self, player_url):
|
||||||
cache_id = ('nsig code', player_id)
|
cache_id = ('youtube-nsig', self._player_js_cache_key(player_url))
|
||||||
|
|
||||||
if func_code := self._player_cache.get(cache_id):
|
if func_code := self._player_cache.get(cache_id):
|
||||||
return func_code
|
return func_code
|
||||||
|
|
||||||
func_code = self.cache.load('youtube-nsig', player_id, min_ver='2025.03.27')
|
func_code = self.cache.load(*cache_id, min_ver='2025.03.31')
|
||||||
if func_code:
|
if func_code:
|
||||||
self._player_cache[cache_id] = func_code
|
self._player_cache[cache_id] = func_code
|
||||||
|
|
||||||
return func_code
|
return func_code
|
||||||
|
|
||||||
def _store_nsig_code_to_cache(self, player_id, func_code):
|
def _store_nsig_code_to_cache(self, player_url, func_code):
|
||||||
cache_id = ('nsig code', player_id)
|
cache_id = ('youtube-nsig', 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('youtube-nsig', player_id, func_code)
|
self.cache.store(*cache_id, func_code)
|
||||||
self._player_cache[cache_id] = func_code
|
self._player_cache[cache_id] = func_code
|
||||||
|
|
||||||
def _decrypt_signature(self, s, video_id, player_url):
|
def _decrypt_signature(self, s, video_id, player_url):
|
||||||
@ -2144,7 +2179,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_id, func_code)
|
self._store_nsig_code_to_cache(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):
|
||||||
@ -2263,7 +2298,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_id)
|
func_code = self._load_nsig_code_from_cache(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)
|
||||||
|
|
||||||
@ -3226,7 +3261,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
|||||||
if player_url:
|
if player_url:
|
||||||
self.report_warning(
|
self.report_warning(
|
||||||
f'nsig extraction failed: Some formats may be missing\n'
|
f'nsig extraction failed: Some formats may be missing\n'
|
||||||
f' n = {query["n"][0]} ; player = {player_url}',
|
f' n = {query["n"][0]} ; player = {player_url}\n'
|
||||||
|
f' {bug_reports_message(before="")}',
|
||||||
video_id=video_id, only_once=True)
|
video_id=video_id, only_once=True)
|
||||||
self.write_debug(e, only_once=True)
|
self.write_debug(e, only_once=True)
|
||||||
else:
|
else:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user