return compat_urlparse.parse_qs(compat_urlparse.urlparse(url).query)
+# any clients starting with _ cannot be explicity requested by the user
+INNERTUBE_CLIENTS = {
+ 'web': {
+ 'INNERTUBE_API_KEY': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8',
+ 'INNERTUBE_CONTEXT': {
+ 'client': {
+ 'clientName': 'WEB',
+ 'clientVersion': '2.20210622.10.00',
+ }
+ },
+ 'INNERTUBE_CONTEXT_CLIENT_NAME': 1
+ },
+ 'web_embedded': {
+ 'INNERTUBE_API_KEY': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8',
+ 'INNERTUBE_CONTEXT': {
+ 'client': {
+ 'clientName': 'WEB_EMBEDDED_PLAYER',
+ 'clientVersion': '1.20210620.0.1',
+ },
+ },
+ 'INNERTUBE_CONTEXT_CLIENT_NAME': 56
+ },
+ 'web_music': {
+ 'INNERTUBE_API_KEY': 'AIzaSyC9XL3ZjWddXya6X74dJoCTL-WEYFDNX30',
+ 'INNERTUBE_HOST': 'music.youtube.com',
+ 'INNERTUBE_CONTEXT': {
+ 'client': {
+ 'clientName': 'WEB_REMIX',
+ 'clientVersion': '1.20210621.00.00',
+ }
+ },
+ 'INNERTUBE_CONTEXT_CLIENT_NAME': 67,
+ },
+ 'android': {
+ 'INNERTUBE_API_KEY': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8',
+ 'INNERTUBE_CONTEXT': {
+ 'client': {
+ 'clientName': 'ANDROID',
+ 'clientVersion': '16.20',
+ }
+ },
+ 'INNERTUBE_CONTEXT_CLIENT_NAME': 3,
+ },
+ 'android_embedded': {
+ 'INNERTUBE_API_KEY': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8',
+ 'INNERTUBE_CONTEXT': {
+ 'client': {
+ 'clientName': 'ANDROID_EMBEDDED_PLAYER',
+ 'clientVersion': '16.20',
+ },
+ },
+ 'INNERTUBE_CONTEXT_CLIENT_NAME': 55
+ },
+ 'android_music': {
+ 'INNERTUBE_API_KEY': 'AIzaSyC9XL3ZjWddXya6X74dJoCTL-WEYFDNX30',
+ 'INNERTUBE_HOST': 'music.youtube.com',
+ 'INNERTUBE_CONTEXT': {
+ 'client': {
+ 'clientName': 'ANDROID_MUSIC',
+ 'clientVersion': '4.32',
+ }
+ },
+ 'INNERTUBE_CONTEXT_CLIENT_NAME': 21,
+ },
+ # ios has HLS live streams
+ # See: https://github.com/TeamNewPipe/NewPipeExtractor/issues/680
+ 'ios': {
+ 'INNERTUBE_API_KEY': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8',
+ 'INNERTUBE_CONTEXT': {
+ 'client': {
+ 'clientName': 'IOS',
+ 'clientVersion': '16.20',
+ }
+ },
+ 'INNERTUBE_CONTEXT_CLIENT_NAME': 5
+ },
+ 'ios_embedded': {
+ 'INNERTUBE_API_KEY': 'AIzaSyDCU8hByM-4DrUqRUYnGn-3llEO78bcxq8',
+ 'INNERTUBE_CONTEXT': {
+ 'client': {
+ 'clientName': 'IOS_MESSAGES_EXTENSION',
+ 'clientVersion': '16.20',
+ },
+ },
+ 'INNERTUBE_CONTEXT_CLIENT_NAME': 66
+ },
+ 'ios_music': {
+ 'INNERTUBE_API_KEY': 'AIzaSyDK3iBpDP9nHVTk2qL73FLJICfOC3c51Og',
+ 'INNERTUBE_HOST': 'music.youtube.com',
+ 'INNERTUBE_CONTEXT': {
+ 'client': {
+ 'clientName': 'IOS_MUSIC',
+ 'clientVersion': '4.32',
+ },
+ },
+ 'INNERTUBE_CONTEXT_CLIENT_NAME': 26
+ },
+ # mweb has 'ultralow' formats
+ # See: https://github.com/yt-dlp/yt-dlp/pull/557
+ 'mweb': {
+ 'INNERTUBE_API_KEY': 'AIzaSyDCU8hByM-4DrUqRUYnGn-3llEO78bcxq8',
+ 'INNERTUBE_CONTEXT': {
+ 'client': {
+ 'clientName': 'MWEB',
+ 'clientVersion': '2.20210721.07.00',
+ }
+ },
+ 'INNERTUBE_CONTEXT_CLIENT_NAME': 2
+ },
+}
+
+
+def build_innertube_clients():
+ third_party = {
+ 'embedUrl': 'https://google.com', # Can be any valid URL
+ }
+ base_clients = ('android', 'web', 'ios', 'mweb')
+ priority = qualities(base_clients[::-1])
+
+ for client, ytcfg in tuple(INNERTUBE_CLIENTS.items()):
+ ytcfg.setdefault('INNERTUBE_API_KEY', 'AIzaSyDCU8hByM-4DrUqRUYnGn-3llEO78bcxq8')
+ ytcfg.setdefault('INNERTUBE_HOST', 'www.youtube.com')
+ ytcfg['INNERTUBE_CONTEXT']['client'].setdefault('hl', 'en')
+ ytcfg['priority'] = 10 * priority(client.split('_', 1)[0])
+
+ if client in base_clients:
+ INNERTUBE_CLIENTS[f'{client}_agegate'] = agegate_ytcfg = copy.deepcopy(ytcfg)
+ agegate_ytcfg['INNERTUBE_CONTEXT']['client']['clientScreen'] = 'EMBED'
+ agegate_ytcfg['INNERTUBE_CONTEXT']['thirdParty'] = third_party
+ agegate_ytcfg['priority'] -= 1
+ elif client.endswith('_embedded'):
+ ytcfg['INNERTUBE_CONTEXT']['thirdParty'] = third_party
+ ytcfg['priority'] -= 2
+ else:
+ ytcfg['priority'] -= 3
+
+
+build_innertube_clients()
+
+
class YoutubeBaseInfoExtractor(InfoExtractor):
"""Provide base functions for Youtube extractors"""
- _LOGIN_URL = 'https://accounts.google.com/ServiceLogin'
- _TWOFACTOR_URL = 'https://accounts.google.com/signin/challenge'
-
- _LOOKUP_URL = 'https://accounts.google.com/_/signin/sl/lookup'
- _CHALLENGE_URL = 'https://accounts.google.com/_/signin/sl/challenge'
- _TFA_URL = 'https://accounts.google.com/_/signin/challenge?hl=en&TL={0}'
_RESERVED_NAMES = (
- r'channel|c|user|browse|playlist|watch|w|v|embed|e|watch_popup|shorts|'
- r'movies|results|shared|hashtag|trending|feed|feeds|oembed|get_video_info|'
+ r'channel|c|user|playlist|watch|w|v|embed|e|watch_popup|'
+ r'shorts|movies|results|shared|hashtag|trending|feed|feeds|'
+ r'browse|oembed|get_video_info|iframe_api|s/player|'
r'storefront|oops|index|account|reporthistory|t/terms|about|upload|signin|logout')
+ _PLAYLIST_ID_RE = r'(?:(?:PL|LL|EC|UU|FL|RD|UL|TL|PU|OLAK5uy_)[0-9A-Za-z-_]{10,}|RDMM|WL|LL|LM)'
+
_NETRC_MACHINE = 'youtube'
+
# If True it will raise an error if no login info is provided
_LOGIN_REQUIRED = False
- _PLAYLIST_ID_RE = r'(?:(?:PL|LL|EC|UU|FL|RD|UL|TL|PU|OLAK5uy_)[0-9A-Za-z-_]{10,}|RDMM|WL|LL|LM)'
+ r''' # Unused since login is broken
+ _LOGIN_URL = 'https://accounts.google.com/ServiceLogin'
+ _TWOFACTOR_URL = 'https://accounts.google.com/signin/challenge'
+
+ _LOOKUP_URL = 'https://accounts.google.com/_/signin/sl/lookup'
+ _CHALLENGE_URL = 'https://accounts.google.com/_/signin/sl/challenge'
+ _TFA_URL = 'https://accounts.google.com/_/signin/challenge?hl=en&TL={0}'
+ '''
def _login(self):
"""
_YT_INITIAL_PLAYER_RESPONSE_RE = r'ytInitialPlayerResponse\s*=\s*({.+?})\s*;'
_YT_INITIAL_BOUNDARY_RE = r'(?:var\s+meta|</script|\n)'
- _YT_DEFAULT_YTCFGS = {
- 'WEB': {
- 'INNERTUBE_API_VERSION': 'v1',
- 'INNERTUBE_CLIENT_NAME': 'WEB',
- 'INNERTUBE_CLIENT_VERSION': '2.20210622.10.00',
- 'INNERTUBE_API_KEY': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8',
- 'INNERTUBE_CONTEXT': {
- 'client': {
- 'clientName': 'WEB',
- 'clientVersion': '2.20210622.10.00',
- 'hl': 'en',
- }
- },
- 'INNERTUBE_CONTEXT_CLIENT_NAME': 1
- },
- 'WEB_AGEGATE': {
- 'INNERTUBE_API_VERSION': 'v1',
- 'INNERTUBE_CLIENT_NAME': 'WEB',
- 'INNERTUBE_CLIENT_VERSION': '2.20210622.10.00',
- 'INNERTUBE_API_KEY': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8',
- 'INNERTUBE_CONTEXT': {
- 'client': {
- 'clientName': 'WEB',
- 'clientVersion': '2.20210622.10.00',
- 'clientScreen': 'EMBED',
- 'hl': 'en',
- }
- },
- 'INNERTUBE_CONTEXT_CLIENT_NAME': 1
- },
- 'WEB_REMIX': {
- 'INNERTUBE_API_VERSION': 'v1',
- 'INNERTUBE_CLIENT_NAME': 'WEB_REMIX',
- 'INNERTUBE_CLIENT_VERSION': '1.20210621.00.00',
- 'INNERTUBE_API_KEY': 'AIzaSyC9XL3ZjWddXya6X74dJoCTL-WEYFDNX30',
- 'INNERTUBE_CONTEXT': {
- 'client': {
- 'clientName': 'WEB_REMIX',
- 'clientVersion': '1.20210621.00.00',
- 'hl': 'en',
- }
- },
- 'INNERTUBE_CONTEXT_CLIENT_NAME': 67
- },
- 'WEB_EMBEDDED_PLAYER': {
- 'INNERTUBE_API_VERSION': 'v1',
- 'INNERTUBE_CLIENT_NAME': 'WEB_EMBEDDED_PLAYER',
- 'INNERTUBE_CLIENT_VERSION': '1.20210620.0.1',
- 'INNERTUBE_API_KEY': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8',
- 'INNERTUBE_CONTEXT': {
- 'client': {
- 'clientName': 'WEB_EMBEDDED_PLAYER',
- 'clientVersion': '1.20210620.0.1',
- 'hl': 'en',
- }
- },
- 'INNERTUBE_CONTEXT_CLIENT_NAME': 56
- },
- 'ANDROID': {
- 'INNERTUBE_API_VERSION': 'v1',
- 'INNERTUBE_CLIENT_NAME': 'ANDROID',
- 'INNERTUBE_CLIENT_VERSION': '16.20',
- 'INNERTUBE_API_KEY': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8',
- 'INNERTUBE_CONTEXT': {
- 'client': {
- 'clientName': 'ANDROID',
- 'clientVersion': '16.20',
- 'hl': 'en',
- }
- },
- 'INNERTUBE_CONTEXT_CLIENT_NAME': 3
- },
- 'ANDROID_AGEGATE': {
- 'INNERTUBE_API_VERSION': 'v1',
- 'INNERTUBE_CLIENT_NAME': 'ANDROID',
- 'INNERTUBE_CLIENT_VERSION': '16.20',
- 'INNERTUBE_API_KEY': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8',
- 'INNERTUBE_CONTEXT': {
- 'client': {
- 'clientName': 'ANDROID',
- 'clientVersion': '16.20',
- 'clientScreen': 'EMBED',
- 'hl': 'en',
- }
- },
- 'INNERTUBE_CONTEXT_CLIENT_NAME': 3
- },
- 'ANDROID_EMBEDDED_PLAYER': {
- 'INNERTUBE_API_VERSION': 'v1',
- 'INNERTUBE_CLIENT_NAME': 'ANDROID_EMBEDDED_PLAYER',
- 'INNERTUBE_CLIENT_VERSION': '16.20',
- 'INNERTUBE_API_KEY': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8',
- 'INNERTUBE_CONTEXT': {
- 'client': {
- 'clientName': 'ANDROID_EMBEDDED_PLAYER',
- 'clientVersion': '16.20',
- 'hl': 'en',
- }
- },
- 'INNERTUBE_CONTEXT_CLIENT_NAME': 55
- },
- 'ANDROID_MUSIC': {
- 'INNERTUBE_API_VERSION': 'v1',
- 'INNERTUBE_CLIENT_NAME': 'ANDROID_MUSIC',
- 'INNERTUBE_CLIENT_VERSION': '4.32',
- 'INNERTUBE_API_KEY': 'AIzaSyC9XL3ZjWddXya6X74dJoCTL-WEYFDNX30',
- 'INNERTUBE_CONTEXT': {
- 'client': {
- 'clientName': 'ANDROID_MUSIC',
- 'clientVersion': '4.32',
- 'hl': 'en',
- }
- },
- 'INNERTUBE_CONTEXT_CLIENT_NAME': 21
- },
- 'IOS': {
- 'INNERTUBE_API_VERSION': 'v1',
- 'INNERTUBE_CLIENT_NAME': 'IOS',
- 'INNERTUBE_CLIENT_VERSION': '16.20',
- 'INNERTUBE_API_KEY': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8',
- 'INNERTUBE_CONTEXT': {
- 'client': {
- 'clientName': 'IOS',
- 'clientVersion': '16.20',
- 'hl': 'en',
- }
- },
- 'INNERTUBE_CONTEXT_CLIENT_NAME': 5
- },
- 'IOS_AGEGATE': {
- 'INNERTUBE_API_VERSION': 'v1',
- 'INNERTUBE_CLIENT_NAME': 'IOS',
- 'INNERTUBE_CLIENT_VERSION': '16.20',
- 'INNERTUBE_API_KEY': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8',
- 'INNERTUBE_CONTEXT': {
- 'client': {
- 'clientName': 'IOS',
- 'clientVersion': '16.20',
- 'clientScreen': 'EMBED',
- 'hl': 'en',
- }
- },
- 'INNERTUBE_CONTEXT_CLIENT_NAME': 5
- },
- 'IOS_MUSIC': {
- 'INNERTUBE_API_VERSION': 'v1',
- 'INNERTUBE_CLIENT_NAME': 'IOS_MUSIC',
- 'INNERTUBE_CLIENT_VERSION': '4.32',
- 'INNERTUBE_API_KEY': 'AIzaSyDK3iBpDP9nHVTk2qL73FLJICfOC3c51Og',
- 'INNERTUBE_CONTEXT': {
- 'client': {
- 'clientName': 'IOS_MUSIC',
- 'clientVersion': '4.32',
- 'hl': 'en',
- }
- },
- 'INNERTUBE_CONTEXT_CLIENT_NAME': 26
- },
- 'IOS_MESSAGES_EXTENSION': {
- 'INNERTUBE_API_VERSION': 'v1',
- 'INNERTUBE_CLIENT_NAME': 'IOS_MESSAGES_EXTENSION',
- 'INNERTUBE_CLIENT_VERSION': '16.20',
- 'INNERTUBE_API_KEY': 'AIzaSyDCU8hByM-4DrUqRUYnGn-3llEO78bcxq8',
- 'INNERTUBE_CONTEXT': {
- 'client': {
- 'clientName': 'IOS_MESSAGES_EXTENSION',
- 'clientVersion': '16.20',
- 'hl': 'en',
- }
- },
- 'INNERTUBE_CONTEXT_CLIENT_NAME': 66
- },
- 'MWEB': {
- 'INNERTUBE_API_VERSION': 'v1',
- 'INNERTUBE_CLIENT_NAME': 'MWEB',
- 'INNERTUBE_CLIENT_VERSION': '2.20210721.07.00',
- 'INNERTUBE_API_KEY': 'AIzaSyDCU8hByM-4DrUqRUYnGn-3llEO78bcxq8',
- 'INNERTUBE_CONTEXT': {
- 'client': {
- 'clientName': 'MWEB',
- 'clientVersion': '2.20210721.07.00',
- 'hl': 'en',
- }
- },
- 'INNERTUBE_CONTEXT_CLIENT_NAME': 2
- },
- 'MWEB_AGEGATE': {
- 'INNERTUBE_API_VERSION': 'v1',
- 'INNERTUBE_CLIENT_NAME': 'MWEB',
- 'INNERTUBE_CLIENT_VERSION': '2.20210721.07.00',
- 'INNERTUBE_API_KEY': 'AIzaSyDCU8hByM-4DrUqRUYnGn-3llEO78bcxq8',
- 'INNERTUBE_CONTEXT': {
- 'client': {
- 'clientName': 'MWEB',
- 'clientVersion': '2.20210721.07.00',
- 'clientScreen': 'EMBED',
- 'hl': 'en',
- }
- },
- 'INNERTUBE_CONTEXT_CLIENT_NAME': 2
- },
- }
+ def _get_default_ytcfg(self, client='web'):
+ return copy.deepcopy(INNERTUBE_CLIENTS[client])
- _YT_DEFAULT_INNERTUBE_HOSTS = {
- 'DIRECT': 'youtubei.googleapis.com',
- 'WEB': 'www.youtube.com',
- 'WEB_REMIX': 'music.youtube.com',
- 'ANDROID_MUSIC': 'music.youtube.com'
- }
+ def _get_innertube_host(self, client='web'):
+ return INNERTUBE_CLIENTS[client]['INNERTUBE_HOST']
- # clients starting with _ cannot be explicity requested by the user
- _YT_CLIENTS = {
- 'android': 'ANDROID',
- 'android_music': 'ANDROID_MUSIC',
- 'android_embedded': 'ANDROID_EMBEDDED_PLAYER',
- 'android_agegate': 'ANDROID_AGEGATE',
- 'ios': 'IOS',
- 'ios_music': 'IOS_MUSIC',
- 'ios_embedded': 'IOS_MESSAGES_EXTENSION',
- 'ios_agegate': 'IOS_AGEGATE',
- 'web': 'WEB',
- 'web_music': 'WEB_REMIX',
- 'web_embedded': 'WEB_EMBEDDED_PLAYER',
- 'web_agegate': 'WEB_AGEGATE',
- 'mweb': 'MWEB',
- 'mweb_agegate': 'MWEB_AGEGATE',
- }
-
- def _get_default_ytcfg(self, client='WEB'):
- if client in self._YT_DEFAULT_YTCFGS:
- return copy.deepcopy(self._YT_DEFAULT_YTCFGS[client])
- self.write_debug(f'INNERTUBE default client {client} does not exist - falling back to WEB client.')
- return copy.deepcopy(self._YT_DEFAULT_YTCFGS['WEB'])
-
- def _get_innertube_host(self, client='WEB'):
- return dict_get(self._YT_DEFAULT_INNERTUBE_HOSTS, (client, 'WEB'))
-
- def _ytcfg_get_safe(self, ytcfg, getter, expected_type=None, default_client='WEB'):
+ def _ytcfg_get_safe(self, ytcfg, getter, expected_type=None, default_client='web'):
# try_get but with fallback to default ytcfg client values when present
_func = lambda y: try_get(y, getter, expected_type)
return _func(ytcfg) or _func(self._get_default_ytcfg(default_client))
- def _extract_client_name(self, ytcfg, default_client='WEB'):
- return self._ytcfg_get_safe(ytcfg, lambda x: x['INNERTUBE_CLIENT_NAME'], compat_str, default_client)
+ def _extract_client_name(self, ytcfg, default_client='web'):
+ return self._ytcfg_get_safe(
+ ytcfg, (lambda x: x['INNERTUBE_CLIENT_NAME'],
+ lambda x: x['INNERTUBE_CONTEXT']['client']['clientName']), compat_str, default_client)
@staticmethod
def _extract_session_index(*data):
if session_index is not None:
return session_index
- def _extract_client_version(self, ytcfg, default_client='WEB'):
- return self._ytcfg_get_safe(ytcfg, lambda x: x['INNERTUBE_CLIENT_VERSION'], compat_str, default_client)
+ def _extract_client_version(self, ytcfg, default_client='web'):
+ return self._ytcfg_get_safe(
+ ytcfg, (lambda x: x['INNERTUBE_CLIENT_VERSION'],
+ lambda x: x['INNERTUBE_CONTEXT']['client']['clientVersion']), compat_str, default_client)
- def _extract_api_key(self, ytcfg=None, default_client='WEB'):
+ def _extract_api_key(self, ytcfg=None, default_client='web'):
return self._ytcfg_get_safe(ytcfg, lambda x: x['INNERTUBE_API_KEY'], compat_str, default_client)
- def _extract_context(self, ytcfg=None, default_client='WEB'):
+ def _extract_context(self, ytcfg=None, default_client='web'):
_get_context = lambda y: try_get(y, lambda x: x['INNERTUBE_CONTEXT'], dict)
context = _get_context(ytcfg)
if context:
def _call_api(self, ep, query, video_id, fatal=True, headers=None,
note='Downloading API JSON', errnote='Unable to download API page',
- context=None, api_key=None, api_hostname=None, default_client='WEB'):
+ context=None, api_key=None, api_hostname=None, default_client='web'):
data = {'context': context} if context else {'context': self._extract_context(default_client=default_client)}
data.update(query)
def generate_api_headers(
self, ytcfg=None, identity_token=None, account_syncid=None,
- visitor_data=None, api_hostname=None, default_client='WEB', session_index=None):
+ visitor_data=None, api_hostname=None, default_client='web', session_index=None):
origin = 'https://' + (api_hostname if api_hostname else self._get_innertube_host(default_client))
headers = {
'X-YouTube-Client-Name': compat_str(
def _extract_response(self, item_id, query, note='Downloading API JSON', headers=None,
ytcfg=None, check_get_keys=None, ep='browse', fatal=True, api_hostname=None,
- default_client='WEB'):
+ default_client='web'):
response = None
last_error = None
count = -1
_AGE_GATE_REASONS = (
'Sign in to confirm your age',
'This video may be inappropriate for some users.',
- 'Sorry, this content is age-restricted.')
+ 'Sorry, this content is age-restricted.',
+ 'Please confirm your age.')
+
+ _AGE_GATE_STATUS_REASONS = (
+ 'AGE_VERIFICATION_REQUIRED',
+ 'AGE_CHECK_REQUIRED'
+ )
_GEO_BYPASS = False
'format': '141/bestaudio[ext=m4a]',
},
},
- # Normal age-gate video (embed allowed)
+ # Age-gate videos. See https://github.com/yt-dlp/yt-dlp/pull/575#issuecomment-888837000
{
+ 'note': 'Embed allowed age-gate video',
'url': 'https://youtube.com/watch?v=HtVdAasjOgU',
'info_dict': {
'id': 'HtVdAasjOgU',
'age_limit': 18,
},
},
+ {
+ 'note': 'Age-gate video with embed allowed in public site',
+ 'url': 'https://youtube.com/watch?v=HsUATh_Nc2U',
+ 'info_dict': {
+ 'id': 'HsUATh_Nc2U',
+ 'ext': 'mp4',
+ 'title': 'Godzilla 2 (Official Video)',
+ 'description': 'md5:bf77e03fcae5529475e500129b05668a',
+ 'upload_date': '20200408',
+ 'uploader_id': 'FlyingKitty900',
+ 'uploader': 'FlyingKitty',
+ 'age_limit': 18,
+ },
+ },
+ {
+ 'note': 'Age-gate video embedable only with clientScreen=EMBED',
+ 'url': 'https://youtube.com/watch?v=Tq92D6wQ1mg',
+ 'info_dict': {
+ 'id': 'Tq92D6wQ1mg',
+ 'title': '[MMD] Adios - EVERGLOW [+Motion DL]',
+ 'ext': 'mp4',
+ 'upload_date': '20191227',
+ 'uploader_id': 'UC1yoRdFoFJaCY-AGfD9W0wQ',
+ 'uploader': 'Projekt Melody',
+ 'description': 'md5:17eccca93a786d51bc67646756894066',
+ 'age_limit': 18,
+ },
+ },
+ {
+ 'note': 'Non-Agegated non-embeddable video',
+ 'url': 'https://youtube.com/watch?v=MeJVWBSsPAY',
+ 'info_dict': {
+ 'id': 'MeJVWBSsPAY',
+ 'ext': 'mp4',
+ 'title': 'OOMPH! - Such Mich Find Mich (Lyrics)',
+ 'uploader': 'Herr Lurik',
+ 'uploader_id': 'st3in234',
+ 'description': 'Fan Video. Music & Lyrics by OOMPH!.',
+ 'upload_date': '20130730',
+ },
+ },
+ {
+ 'note': 'Non-bypassable age-gated video',
+ 'url': 'https://youtube.com/watch?v=Cr381pDsSsA',
+ 'only_matching': True,
+ },
# video_info is None (https://github.com/ytdl-org/youtube-dl/issues/4421)
# YouTube Red ad is not captured for creator
{
'params': {
'skip_download': True,
},
+ 'skip': 'Not multifeed anymore',
},
{
# Multifeed video with comma in title (see https://github.com/ytdl-org/youtube-dl/issues/8536)
'racyCheckOk': True
}
+ def _is_agegated(self, player_response):
+ reasons = traverse_obj(player_response, ('playabilityStatus', ('status', 'reason')), default=[])
+ for reason in reasons:
+ if reason in self._AGE_GATE_REASONS + self._AGE_GATE_STATUS_REASONS:
+ return True
+ if traverse_obj(player_response, ('playabilityStatus', 'desktopLegacyAgeGateReason')) is not None:
+ return True
+ return False
+
def _extract_player_response(self, client, video_id, master_ytcfg, player_ytcfg, identity_token, player_url, initial_pr):
session_index = self._extract_session_index(player_ytcfg, master_ytcfg)
sts = self._extract_signature_timestamp(video_id, player_url, master_ytcfg, fatal=False)
headers = self.generate_api_headers(
player_ytcfg, identity_token, syncid,
- default_client=self._YT_CLIENTS[client], session_index=session_index)
+ default_client=client, session_index=session_index)
yt_query = {'videoId': video_id}
yt_query.update(self._generate_player_context(sts))
return self._extract_response(
item_id=video_id, ep='player', query=yt_query,
ytcfg=player_ytcfg, headers=headers, fatal=False,
- default_client=self._YT_CLIENTS[client],
+ default_client=client,
note='Downloading %s player API JSON' % client.replace('_', ' ').strip()
) or None
def _get_requested_clients(self, url, smuggled_data):
requested_clients = []
- allowed_clients = [client for client in self._YT_CLIENTS.keys() if client[:1] != '_']
+ allowed_clients = sorted(
+ [client for client in INNERTUBE_CLIENTS.keys() if client[:1] != '_'],
+ key=lambda client: INNERTUBE_CLIENTS[client]['priority'], reverse=True)
for client in self._configuration_arg('player_client'):
if client in allowed_clients:
requested_clients.append(client)
player_ytcfg = master_ytcfg if client == 'web' else {}
if 'configs' not in self._configuration_arg('player_skip'):
player_ytcfg = self._extract_player_ytcfg(client, video_id) or player_ytcfg
- if client == 'web_embedded':
- # If we extracted the embed webpage, it'll tell us if we can view the video
- embedded_pr = self._parse_json(
- traverse_obj(player_ytcfg, ('PLAYER_VARS', 'embedded_player_response'), expected_type=str) or '{}',
- video_id=video_id)
- embedded_ps_reason = traverse_obj(embedded_pr, ('playabilityStatus', 'reason'), expected_type=str) or ''
- if embedded_ps_reason in self._AGE_GATE_REASONS:
- self.report_warning(f'Youtube said: {embedded_ps_reason}')
- continue
pr = (
initial_pr if client == 'web' and initial_pr
if pr:
yield pr
- if traverse_obj(pr, ('playabilityStatus', 'reason')) in self._AGE_GATE_REASONS:
+ if self._is_agegated(pr):
client = f'{client}_agegate'
- if client in self._YT_CLIENTS and client not in original_clients:
+ if client in INNERTUBE_CLIENTS and client not in original_clients:
clients.append(client)
# Android player_response does not have microFormats which are needed for
for f in formats:
if '&c=WEB&' in f['url'] and '&ratebypass=yes&' not in f['url']: # throttled
f['source_preference'] = -10
- note = f.get('format_note')
- f['format_note'] = f'{note} (throttled)' if note else '(throttled)'
+ # TODO: this method is not reliable
+ f['format_note'] = format_field(f, 'format_note', '%s ') + '(maybe throttled)'
# Source is given priority since formats that throttle are given lower source_preference
# When throttling issue is fully fixed, remove this