[ie/youtube] Detect experiment binding GVS PO Token to video id (#14471)

Fixes https://github.com/yt-dlp/yt-dlp/issues/14421

Authored by: coletdjnz
This commit is contained in:
coletdjnz 2025-09-29 16:25:09 +13:00 committed by GitHub
parent 88e2a2de8e
commit bd5ed90419
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 28 additions and 2 deletions

View file

@ -45,3 +45,8 @@ class TestGetWebPoContentBinding:
def test_invalid_base64(self, pot_request):
pot_request.visitor_data = 'invalid-base64'
assert get_webpo_content_binding(pot_request, bind_to_visitor_id=True) == (pot_request.visitor_data, ContentBindingType.VISITOR_DATA)
def test_gvs_video_id_binding_experiment(self, pot_request):
pot_request.context = PoTokenContext.GVS
pot_request._gvs_bind_to_video_id = True
assert get_webpo_content_binding(pot_request) == ('example-video-id', ContentBindingType.VIDEO_ID)

View file

@ -2955,9 +2955,20 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
# TODO(future): This validation should be moved into pot framework.
# Some sort of middleware or validation provider perhaps?
gvs_bind_to_video_id = False
experiments = traverse_obj(ytcfg, (
'WEB_PLAYER_CONTEXT_CONFIGS', ..., 'serializedExperimentFlags', {urllib.parse.parse_qs}))
if 'true' in traverse_obj(experiments, (..., 'html5_generate_content_po_token', -1)):
self.write_debug(
f'{video_id}: Detected experiment to bind GVS PO Token to video id.', only_once=True)
gvs_bind_to_video_id = True
# GVS WebPO Token is bound to visitor_data / Visitor ID when logged out.
# Must have visitor_data for it to function.
if player_url and context == _PoTokenContext.GVS and not visitor_data and not self.is_authenticated:
if (
player_url and context == _PoTokenContext.GVS
and not visitor_data and not self.is_authenticated and not gvs_bind_to_video_id
):
self.report_warning(
f'Unable to fetch GVS PO Token for {client} client: Missing required Visitor Data. '
f'You may need to pass Visitor Data with --extractor-args "youtube:visitor_data=XXX"', only_once=True)
@ -2971,7 +2982,10 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
config_po_token = self._get_config_po_token(client, context)
if config_po_token:
# GVS WebPO token is bound to data_sync_id / account Session ID when logged in.
if player_url and context == _PoTokenContext.GVS and not data_sync_id and self.is_authenticated:
if (
player_url and context == _PoTokenContext.GVS
and not data_sync_id and self.is_authenticated and not gvs_bind_to_video_id
):
self.report_warning(
f'Got a GVS PO Token for {client} client, but missing Data Sync ID for account. Formats may not work.'
f'You may need to pass a Data Sync ID with --extractor-args "youtube:data_sync_id=XXX"')
@ -2997,6 +3011,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
video_id=video_id,
video_webpage=webpage,
required=required,
_gvs_bind_to_video_id=gvs_bind_to_video_id,
**kwargs,
)
@ -3040,6 +3055,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
data_sync_id=kwargs.get('data_sync_id'),
video_id=kwargs.get('video_id'),
request_cookiejar=self._downloader.cookiejar,
_gvs_bind_to_video_id=kwargs.get('_gvs_bind_to_video_id', False),
# All requests that would need to be proxied should be in the
# context of www.youtube.com or the innertube host

View file

@ -58,6 +58,8 @@ class PoTokenRequest:
visitor_data: str | None = None
data_sync_id: str | None = None
video_id: str | None = None
# Internal, YouTube experiment on whether to bind GVS PO Token to video_id.
_gvs_bind_to_video_id: bool = False
# Networking parameters
request_cookiejar: YoutubeDLCookieJar = dataclasses.field(default_factory=YoutubeDLCookieJar)

View file

@ -42,6 +42,9 @@ def get_webpo_content_binding(
if not client_name or client_name not in webpo_clients:
return None, None
if request.context == PoTokenContext.GVS and request._gvs_bind_to_video_id:
return request.video_id, ContentBindingType.VIDEO_ID
if request.context == PoTokenContext.GVS or client_name in ('WEB_REMIX', ):
if request.is_authenticated:
return request.data_sync_id, ContentBindingType.DATASYNC_ID