diff --git a/README.md b/README.md index a46cdfb57..2b85c2be6 100644 --- a/README.md +++ b/README.md @@ -1859,7 +1859,7 @@ The following extractors use this feature: #### youtube * `lang`: Prefer translated metadata (`title`, `description` etc) of this language code (case-sensitive). By default, the video primary language metadata is preferred, with a fallback to `en` translated. See [youtube/_base.py](https://github.com/yt-dlp/yt-dlp/blob/415b4c9f955b1a0391204bd24a7132590e7b3bdb/yt_dlp/extractor/youtube/_base.py#L402-L409) for the list of supported content language codes * `skip`: One or more of `hls`, `dash` or `translated_subs` to skip extraction of the m3u8 manifests, dash manifests and [auto-translated subtitles](https://github.com/yt-dlp/yt-dlp/issues/4090#issuecomment-1158102032) respectively -* `player_client`: Clients to extract video data from. The currently available clients are `web`, `web_safari`, `web_embedded`, `web_music`, `web_creator`, `mweb`, `ios`, `visionos`, `android`, `android_vr`, `tv`, `tv_downgraded`, and `tv_simply`. By default, `android_vr,web_safari` is used. If no JavaScript runtime/engine is available, then only `android_vr` is used. If logged-in cookies are passed to yt-dlp, then `tv_downgraded,web_safari` is used for free accounts and `tv_downgraded,web_creator` is used for premium accounts. The `web_music` client is added for `music.youtube.com` URLs when logged-in cookies are used. The `web_embedded` client is added for age-restricted videos but only successfully works around the age-restriction sometimes (e.g. if the video is embeddable), and may be added as a fallback if `android_vr` or `visionos` is unable to access a video. The `web_creator` client is added for age-restricted videos if account age-verification is required. Some clients, such as `web_creator` and `web_music`, require a `po_token` for their formats to be downloadable. Some clients, such as `web_creator`, will only work with authentication. Not all clients support authentication via cookies. You can use `default` for the default clients, or you can use `all` for all clients (not recommended). You can prefix a client with `-` to exclude it, e.g. `youtube:player_client=default,-web_safari` +* `player_client`: Clients to extract video data from. The currently available clients are `web`, `web_safari`, `web_embedded`, `web_music`, `web_creator`, `mweb`, `ios`, `visionos`, `android`, `android_vr`, `tv`, `tv_downgraded`, and `tv_simply`. By default, `visionos,android_vr,web` is used. If no JavaScript runtime/engine is available, then `web` is omitted. If logged-in cookies are passed to yt-dlp, then `tv_downgraded,web` is used for free accounts and `tv_downgraded,web_creator,web` is used for premium accounts. The `web_music` client is added for `music.youtube.com` URLs when logged-in cookies are used. The `web_embedded` client is added for age-restricted videos but only successfully works around the age-restriction sometimes (e.g. if the video is embeddable). The `tv_downgraded` client may be added as a fallback if `android_vr` or `visionos` is unable to access a video. The `web_creator` client is added for age-restricted videos if account age-verification is required. Some clients, such as `web_creator` and `web_music`, require a `po_token` for their formats to be downloadable. Some clients, such as `web_creator`, will only work with authentication. Not all clients support authentication via cookies. You can use `default` for the default clients, or you can use `all` for all clients (not recommended). You can prefix a client with `-` to exclude it, e.g. `youtube:player_client=default,-web` * `player_skip`: Skip some network requests that are generally needed for robust extraction. One or more of `configs` (skip client configs), `webpage` (skip initial webpage), `js` (skip js player), `initial_data` (skip initial data/next ep request). While these options can help reduce the number of requests needed or avoid some rate-limiting, they could cause issues such as missing formats or metadata. See [#860](https://github.com/yt-dlp/yt-dlp/pull/860) and [#12826](https://github.com/yt-dlp/yt-dlp/issues/12826) for more details * `webpage_skip`: Skip extraction of embedded webpage data. One or both of `player_response`, `initial_data`. These options are for testing purposes and don't skip any network requests. Neither is skipped by default; however, if a `player_js_version` value other than `actual` is used, then `webpage_skip=player_response` is implied * `webpage_client`: Client to use for the video webpage request. One of `web` or `web_safari` (default) diff --git a/yt_dlp/extractor/youtube/_base.py b/yt_dlp/extractor/youtube/_base.py index 0081c5696..fbf6f6d09 100644 --- a/yt_dlp/extractor/youtube/_base.py +++ b/yt_dlp/extractor/youtube/_base.py @@ -107,6 +107,7 @@ INNERTUBE_CLIENTS = { **WEB_PO_TOKEN_POLICIES, }, # Safari UA returns pre-merged video+audio 144p/240p/360p/720p/1080p HLS formats + # Since 2026.07, HLS formats are only returned with some logged-in or "trusted" sessions 'web_safari': { 'INNERTUBE_CONTEXT': { 'client': { @@ -223,6 +224,7 @@ INNERTUBE_CLIENTS = { }, # "Made for kids" videos aren't available with this client # Using a clientVersion>1.65 may return SABR streams only + # Since 2026.07, intermittent/selective POT enforcement has been observed for non-HLS formats 'android_vr': { 'INNERTUBE_CONTEXT': { 'client': { @@ -238,6 +240,24 @@ INNERTUBE_CLIENTS = { }, 'INNERTUBE_CONTEXT_CLIENT_NAME': 28, 'REQUIRE_JS_PLAYER': False, + 'GVS_PO_TOKEN_POLICY': { + StreamingProtocol.HTTPS: GvsPoTokenPolicy( + required=True, + recommended=True, + not_required_with_player_token=True, + ), + StreamingProtocol.DASH: GvsPoTokenPolicy( + required=True, + recommended=True, + not_required_with_player_token=True, + ), + StreamingProtocol.HLS: GvsPoTokenPolicy( + required=False, + recommended=True, + not_required_with_player_token=True, + ), + }, + 'PLAYER_PO_TOKEN_POLICY': PlayerPoTokenPolicy(required=False, recommended=True), }, # iOS clients have HLS live streams. Setting device model to get 60fps formats. # See: https://github.com/TeamNewPipe/NewPipeExtractor/issues/680#issuecomment-1002724558 @@ -340,7 +360,6 @@ INNERTUBE_CLIENTS = { }, }, 'INNERTUBE_CONTEXT_CLIENT_NAME': 7, - 'REQUIRE_AUTH': True, 'SUPPORTS_COOKIES': True, }, 'tv_simply': { diff --git a/yt_dlp/extractor/youtube/_video.py b/yt_dlp/extractor/youtube/_video.py index de9e7fac5..8c5fa5593 100644 --- a/yt_dlp/extractor/youtube/_video.py +++ b/yt_dlp/extractor/youtube/_video.py @@ -140,13 +140,13 @@ class YoutubeIE(YoutubeBaseInfoExtractor): _RETURN_TYPE = 'video' # XXX: How to handle multifeed? _SUBTITLE_FORMATS = ('json3', 'srv1', 'srv2', 'srv3', 'ttml', 'srt', 'vtt') - _DEFAULT_CLIENTS = ('android_vr', 'web_safari') - _DEFAULT_JSLESS_CLIENTS = ('android_vr',) - _DEFAULT_AUTHED_CLIENTS = ('tv_downgraded', 'web_safari') + _DEFAULT_CLIENTS = ('visionos', 'android_vr', 'web') + _DEFAULT_JSLESS_CLIENTS = ('visionos', 'android_vr') + _DEFAULT_AUTHED_CLIENTS = ('tv_downgraded', 'web') # Premium does not require POT (except for subtitles) - _DEFAULT_PREMIUM_CLIENTS = ('tv_downgraded', 'web_creator') + _DEFAULT_PREMIUM_CLIENTS = ('tv_downgraded', 'web_creator', 'web') _WEBPAGE_CLIENTS = ('web', 'web_safari') - _DEFAULT_WEBPAGE_CLIENT = 'web_safari' + _DEFAULT_WEBPAGE_CLIENT = 'web' _GEO_BYPASS = False @@ -2904,6 +2904,10 @@ class YoutubeIE(YoutubeBaseInfoExtractor): def _is_unplayable(player_response): return traverse_obj(player_response, ('playabilityStatus', 'status')) == 'UNPLAYABLE' + @staticmethod + def _is_error_response(player_response): + return traverse_obj(player_response, ('playabilityStatus', 'status')) == 'ERROR' + def _extract_player_response(self, client, video_id, webpage_ytcfg, player_ytcfg, player_url, initial_pr, visitor_data, data_sync_id, po_token): headers = self.generate_api_headers( ytcfg=player_ytcfg, @@ -3137,12 +3141,13 @@ class YoutubeIE(YoutubeBaseInfoExtractor): if ( # Is this a "made for kids" video that can't be downloaded with android_vr/visionos? - client in {'android_vr', 'visionos'} and self._is_unplayable(pr) + client in {'android_vr', 'visionos'} + and (self._is_unplayable(pr) or self._is_error_response(pr)) and webpage and 'made for kids' in webpage # ...and is a JS runtime is available? and any(p.is_available() for p in self._jsc_director.providers.values()) ): - append_client('web_embedded') + append_client('tv_downgraded') # web_embedded can work around age-gate and age-verification for some embeddable videos if self._is_agegated(pr) and variant != 'web_embedded':