[ie/youtube] Better error when all player responses are skipped (#9083)

Authored by: Grub4K, pukkandan

Co-authored-by: pukkandan <pukkandan.ytdlp@gmail.com>
This commit is contained in:
Simon Sawicki 2024-02-25 00:20:22 +01:00 committed by GitHub
parent 464c919ea8
commit 5eedc208ec
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -3640,15 +3640,28 @@ def _get_requested_clients(self, url, smuggled_data):
return orderedSet(requested_clients) return orderedSet(requested_clients)
def _invalid_player_response(self, pr, video_id):
# YouTube may return a different video player response than expected.
# See: https://github.com/TeamNewPipe/NewPipe/issues/8713
if (pr_id := traverse_obj(pr, ('videoDetails', 'videoId'))) != video_id:
return pr_id
def _extract_player_responses(self, clients, video_id, webpage, master_ytcfg, smuggled_data): def _extract_player_responses(self, clients, video_id, webpage, master_ytcfg, smuggled_data):
initial_pr = None initial_pr = None
if webpage: if webpage:
initial_pr = self._search_json( initial_pr = self._search_json(
self._YT_INITIAL_PLAYER_RESPONSE_RE, webpage, 'initial player response', video_id, fatal=False) self._YT_INITIAL_PLAYER_RESPONSE_RE, webpage, 'initial player response', video_id, fatal=False)
prs = []
if initial_pr and not self._invalid_player_response(initial_pr, video_id):
# Android player_response does not have microFormats which are needed for
# extraction of some data. So we return the initial_pr with formats
# stripped out even if not requested by the user
# See: https://github.com/yt-dlp/yt-dlp/issues/501
prs.append({**initial_pr, 'streamingData': None})
all_clients = set(clients) all_clients = set(clients)
clients = clients[::-1] clients = clients[::-1]
prs = []
def append_client(*client_names): def append_client(*client_names):
""" Append the first client name that exists but not already used """ """ Append the first client name that exists but not already used """
@ -3660,18 +3673,9 @@ def append_client(*client_names):
all_clients.add(actual_client) all_clients.add(actual_client)
return return
# Android player_response does not have microFormats which are needed for
# extraction of some data. So we return the initial_pr with formats
# stripped out even if not requested by the user
# See: https://github.com/yt-dlp/yt-dlp/issues/501
if initial_pr:
pr = dict(initial_pr)
pr['streamingData'] = None
prs.append(pr)
last_error = None
tried_iframe_fallback = False tried_iframe_fallback = False
player_url = None player_url = None
skipped_clients = {}
while clients: while clients:
client, base_client, variant = _split_innertube_client(clients.pop()) client, base_client, variant = _split_innertube_client(clients.pop())
player_ytcfg = master_ytcfg if client == 'web' else {} player_ytcfg = master_ytcfg if client == 'web' else {}
@ -3692,26 +3696,19 @@ def append_client(*client_names):
pr = initial_pr if client == 'web' and initial_pr else self._extract_player_response( pr = initial_pr if client == 'web' and initial_pr else self._extract_player_response(
client, video_id, player_ytcfg or master_ytcfg, player_ytcfg, player_url if require_js_player else None, initial_pr, smuggled_data) client, video_id, player_ytcfg or master_ytcfg, player_ytcfg, player_url if require_js_player else None, initial_pr, smuggled_data)
except ExtractorError as e: except ExtractorError as e:
if last_error: self.report_warning(e)
self.report_warning(last_error)
last_error = e
continue continue
if pr: if pr_id := self._invalid_player_response(pr, video_id):
# YouTube may return a different video player response than expected. skipped_clients[client] = pr_id
# See: https://github.com/TeamNewPipe/NewPipe/issues/8713 elif pr:
pr_video_id = traverse_obj(pr, ('videoDetails', 'videoId')) # Save client name for introspection later
if pr_video_id and pr_video_id != video_id: name = short_client_name(client)
self.report_warning( sd = traverse_obj(pr, ('streamingData', {dict})) or {}
f'Skipping player response from {client} client (got player response for video "{pr_video_id}" instead of "{video_id}")' + bug_reports_message()) sd[STREAMING_DATA_CLIENT_NAME] = name
else: for f in traverse_obj(sd, (('formats', 'adaptiveFormats'), ..., {dict})):
# Save client name for introspection later f[STREAMING_DATA_CLIENT_NAME] = name
name = short_client_name(client) prs.append(pr)
sd = traverse_obj(pr, ('streamingData', {dict})) or {}
sd[STREAMING_DATA_CLIENT_NAME] = name
for f in traverse_obj(sd, (('formats', 'adaptiveFormats'), ..., {dict})):
f[STREAMING_DATA_CLIENT_NAME] = name
prs.append(pr)
# creator clients can bypass AGE_VERIFICATION_REQUIRED if logged in # creator clients can bypass AGE_VERIFICATION_REQUIRED if logged in
if variant == 'embedded' and self._is_unplayable(pr) and self.is_authenticated: if variant == 'embedded' and self._is_unplayable(pr) and self.is_authenticated:
@ -3722,10 +3719,15 @@ def append_client(*client_names):
elif not variant: elif not variant:
append_client(f'tv_embedded.{base_client}', f'{base_client}_embedded') append_client(f'tv_embedded.{base_client}', f'{base_client}_embedded')
if last_error: if skipped_clients:
if not len(prs): self.report_warning(
raise last_error f'Skipping player responses from {"/".join(skipped_clients)} clients '
self.report_warning(last_error) f'(got player responses for video "{"/".join(set(skipped_clients.values()))}" instead of "{video_id}")')
if not prs:
raise ExtractorError(
'All player responses are invalid. Your IP is likely being blocked by Youtube', expected=True)
elif not prs:
raise ExtractorError('Failed to extract any player response')
return prs, player_url return prs, player_url
def _needs_live_processing(self, live_status, duration): def _needs_live_processing(self, live_status, duration):