Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package yt-dlp for openSUSE:Factory checked in at 2026-03-13 21:17:44 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/yt-dlp (Old) and /work/SRC/openSUSE:Factory/.yt-dlp.new.8177 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "yt-dlp" Fri Mar 13 21:17:44 2026 rev:89 rq:1338667 version:2026.03.13 Changes: -------- --- /work/SRC/openSUSE:Factory/yt-dlp/yt-dlp.changes 2026-03-07 20:15:21.981359849 +0100 +++ /work/SRC/openSUSE:Factory/.yt-dlp.new.8177/yt-dlp.changes 2026-03-13 21:21:49.681775823 +0100 @@ -1,0 +2,16 @@ +Fri Mar 13 09:19:22 UTC 2026 - Luigi Baldoni <[email protected]> + +- Update to version 2026.03.13 + * Extractor changes: + * tiktok: Fix challenge solving + * yt: Fix android_vr player client + * yt: Fix use_ad_playback_context extractor-arg + * yt: Fix web_embedded player client + * yt: Request web_safari & web_creator client configs + * tab: Fix album extraction + * tab: Improve description extraction + * Update ejs to 0.7.0 + * ejs: Fix sig value encoding by + * ejs: Solve new player variants + +------------------------------------------------------------------- Old: ---- yt_dlp_ejs-0.5.0-py3-none-any.whl New: ---- yt_dlp_ejs-0.7.0-py3-none-any.whl ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ yt-dlp.spec ++++++ --- /var/tmp/diff_new_pack.mL0v5c/_old 2026-03-13 21:21:50.505809815 +0100 +++ /var/tmp/diff_new_pack.mL0v5c/_new 2026-03-13 21:21:50.509809980 +0100 @@ -27,8 +27,8 @@ %endif Name: yt-dlp -Version: 2026.03.03 -%define ejsver 0.5.0 +Version: 2026.03.13 +%define ejsver 0.7.0 Release: 0 Summary: Enhanced fork of youtube-dl, a video site downloader for offline watching License: CC-BY-SA-3.0 AND SUSE-Public-Domain ++++++ _scmsync.obsinfo ++++++ --- /var/tmp/diff_new_pack.mL0v5c/_old 2026-03-13 21:21:50.557811960 +0100 +++ /var/tmp/diff_new_pack.mL0v5c/_new 2026-03-13 21:21:50.561812125 +0100 @@ -1,5 +1,5 @@ -mtime: 1772648117 -commit: ffb0e8fa4812abe13422a4a47afd3325fdddc1ac324dbbaa88d69cc060b69dc9 +mtime: 1773395486 +commit: 4dcaef1d42b3f31d08e089a22c26f3e8d491326150f53e226d7251a38e21492b url: https://src.opensuse.org/jengelh/yt-dlp revision: master ++++++ build.specials.obscpio ++++++ ++++++ build.specials.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/.gitignore new/.gitignore --- old/.gitignore 1970-01-01 01:00:00.000000000 +0100 +++ new/.gitignore 2026-03-13 10:51:35.000000000 +0100 @@ -0,0 +1 @@ +.osc ++++++ yt-dlp.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yt-dlp/AUTHORS new/yt-dlp/AUTHORS --- old/yt-dlp/AUTHORS 2026-03-03 17:37:40.000000000 +0100 +++ new/yt-dlp/AUTHORS 2026-03-13 09:45:34.000000000 +0100 @@ -396,6 +396,7 @@ François Revol Frederic Bournival Frederik Nordahl Jul Sabroe +Frieder Hannenheim Friedrich Rehren GD-Slime GDR! @@ -842,6 +843,7 @@ Peisen Wang Pete Hemery Peter +Peter Devine Peter Hosey Peter Oettig Peter Pitzulo @@ -996,6 +998,7 @@ Soebb Sojiroh Sonic +SparseOrnament15 SsSsS Stanislav Kupryakhin Stanny Nuytkens diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yt-dlp/CONTRIBUTORS new/yt-dlp/CONTRIBUTORS --- old/yt-dlp/CONTRIBUTORS 2026-03-03 17:37:22.000000000 +0100 +++ new/yt-dlp/CONTRIBUTORS 2026-03-13 09:45:34.000000000 +0100 @@ -874,3 +874,6 @@ regulad stastix syphyr +FriederHannenheim +Peter-Devine +SparseOrnament15 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yt-dlp/Changelog.md new/yt-dlp/Changelog.md --- old/yt-dlp/Changelog.md 2026-03-03 17:37:33.000000000 +0100 +++ new/yt-dlp/Changelog.md 2026-03-13 09:45:29.000000000 +0100 @@ -4,6 +4,20 @@ # To create a release, dispatch the https://github.com/yt-dlp/yt-dlp/actions/workflows/release.yml workflow on master --> +### 2026.03.13 + +#### Extractor changes +- **tiktok**: [Fix challenge solving](https://github.com/yt-dlp/yt-dlp/commit/db62e438a15743b156ca5ebfc6dbe160e9bc1662) ([#16223](https://github.com/yt-dlp/yt-dlp/issues/16223)) by [bashonly](https://github.com/bashonly) +- **youtube** + - [Fix `android_vr` player client](https://github.com/yt-dlp/yt-dlp/commit/ff459e5fc04b1a061212672626b7bfa23ff3cdcd) ([#16168](https://github.com/yt-dlp/yt-dlp/issues/16168)) by [gamer191](https://github.com/gamer191) + - [Fix `use_ad_playback_context` extractor-arg](https://github.com/yt-dlp/yt-dlp/commit/7e145ac1cae8f891e18c9375fa23097f1dfa0b19) ([#16196](https://github.com/yt-dlp/yt-dlp/issues/16196)) by [bashonly](https://github.com/bashonly) + - [Fix `web_embedded` player client](https://github.com/yt-dlp/yt-dlp/commit/f2bd3202c0ffa3f0c0069c44ca53b625dca568bc) ([#16177](https://github.com/yt-dlp/yt-dlp/issues/16177)) by [bashonly](https://github.com/bashonly), [SparseOrnament15](https://github.com/SparseOrnament15) + - [Request `web_safari` & `web_creator` client configs](https://github.com/yt-dlp/yt-dlp/commit/48a61d0f38b156785d24df628d42892441e008c4) ([#16198](https://github.com/yt-dlp/yt-dlp/issues/16198)) by [bashonly](https://github.com/bashonly) + - [Update ejs to 0.7.0](https://github.com/yt-dlp/yt-dlp/commit/92f1d99dbe1e10d942ef0963f625dbc5bc0768aa) ([#16231](https://github.com/yt-dlp/yt-dlp/issues/16231)) by [bashonly](https://github.com/bashonly), [Grub4K](https://github.com/Grub4K) + - tab + - [Fix album extraction](https://github.com/yt-dlp/yt-dlp/commit/ae025da02364f4d085953f41fd0d32ade3c4afb9) ([#16041](https://github.com/yt-dlp/yt-dlp/issues/16041)) by [FriederHannenheim](https://github.com/FriederHannenheim) + - [Improve description extraction](https://github.com/yt-dlp/yt-dlp/commit/3e36cf9cdb12ef566416c5620a1a95b5a0221017) ([#16057](https://github.com/yt-dlp/yt-dlp/issues/16057)) by [Peter-Devine](https://github.com/Peter-Devine) + ### 2026.03.03 #### Extractor changes diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yt-dlp/Makefile new/yt-dlp/Makefile --- old/yt-dlp/Makefile 2026-03-03 17:37:22.000000000 +0100 +++ new/yt-dlp/Makefile 2026-03-13 09:45:22.000000000 +0100 @@ -202,9 +202,9 @@ # The following EJS_-prefixed variables are auto-generated by devscripts/update_ejs.py # DO NOT EDIT! -EJS_VERSION = 0.5.0 -EJS_WHEEL_NAME = yt_dlp_ejs-0.5.0-py3-none-any.whl -EJS_WHEEL_HASH = sha256:674fc0efea741d3100cdf3f0f9e123150715ee41edf47ea7a62fbdeda204bdec +EJS_VERSION = 0.7.0 +EJS_WHEEL_NAME = yt_dlp_ejs-0.7.0-py3-none-any.whl +EJS_WHEEL_HASH = sha256:967e9cbe114ddfd046ff4668af18b1827b4597e2e47a83deea668a355828c798 EJS_PY_FOLDERS = yt_dlp_ejs yt_dlp_ejs/yt yt_dlp_ejs/yt/solver EJS_PY_FILES = yt_dlp_ejs/__init__.py yt_dlp_ejs/_version.py yt_dlp_ejs/yt/__init__.py yt_dlp_ejs/yt/solver/__init__.py EJS_JS_FOLDERS = yt_dlp_ejs/yt/solver diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yt-dlp/README.md new/yt-dlp/README.md --- old/yt-dlp/README.md 2026-03-03 17:37:39.000000000 +0100 +++ new/yt-dlp/README.md 2026-03-13 09:45:33.000000000 +0100 @@ -1862,10 +1862,11 @@ * `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`, `android`, `android_vr`, `tv`, `tv_downgraded`, and `tv_simply`. By default, `android_vr,web,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,web_safari` 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), and may be added as a fallback if `android_vr` 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_crea tor` 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`. Using these will not skip any network requests, and in some cases will result in additional network requests. Currently, the default is `player_response`; however, typically these are for testing purposes only +* `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) * `player_params`: YouTube player parameters to use for player requests. Will overwrite any default ones set by yt-dlp. * `player_js_variant`: The player javascript variant to use for n/sig deciphering. The known variants are: `main`, `tcc`, `tce`, `es5`, `es6`, `es6_tcc`, `es6_tce`, `tv`, `tv_es6`, `phone`, `house`. The default is `tv`, and the others are for debugging purposes. You can use `actual` to go with what is prescribed by the site -* `player_js_version`: The player javascript version to use for n/sig deciphering, in the format of `signature_timestamp@hash` (e.g. `20348@0004de42`). Currently, the default is to force `20514@9f4cc5e4`. You can use `actual` to go with what is prescribed by the site +* `player_js_version`: The player javascript version to use for n/sig deciphering, in the format of `signature_timestamp@hash` (e.g. `20348@0004de42`). The default is to use what is prescribed by the site, and can be selected with `actual`. Using any other value will imply `webpage_skip=player_response` * `comment_sort`: `top` or `new` (default) - choose comment sorting mode (on YouTube's side) * `max_comments`: Limit the amount of comments to gather. Comma-separated list of integers representing `max-comments,max-parents,max-replies,max-replies-per-thread,max-depth`. Default is `all,all,all,all,all` * A `max-depth` value of `1` will discard all replies, regardless of the `max-replies` or `max-replies-per-thread` values given @@ -1880,7 +1881,7 @@ * `pot_trace`: Enable debug logging for PO Token fetching. Either `true` or `false` (default) * `fetch_pot`: Policy to use for fetching a PO Token from providers. One of `always` (always try fetch a PO Token regardless if the client requires one for the given context), `never` (never fetch a PO Token), or `auto` (default; only fetch a PO Token if the client requires one for the given context) * `jsc_trace`: Enable debug logging for JS Challenge fetching. Either `true` or `false` (default) -* `use_ad_playback_context`: Skip preroll ads to eliminate the mandatory wait period before download. Do NOT use this when passing premium account cookies to yt-dlp, as it will result in a loss of premium formats. Only effective with the `web`, `web_safari`, `web_music` and `mweb` player clients. Either `true` or `false` (default) +* `use_ad_playback_context`: Skip preroll ads to eliminate the mandatory wait period before download. Do NOT use this when passing premium account cookies to yt-dlp, as it will result in a loss of premium formats. Only effective with the `mweb` and `web_music` player clients. Either `true` or `false` (default) #### youtube-ejs * `jitless`: Run supported Javascript engines in JIT-less mode. Supported runtimes are `deno`, `node` and `bun`. Provides better security at the cost of performance/speed. Do note that `node` and `bun` are still considered insecure. Either `true` or `false` (default) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yt-dlp/README.txt new/yt-dlp/README.txt --- old/yt-dlp/README.txt 2026-03-03 17:37:40.000000000 +0100 +++ new/yt-dlp/README.txt 2026-03-13 09:45:34.000000000 +0100 @@ -2339,10 +2339,12 @@ they could cause issues such as missing formats or metadata. See #860 and #12826 for more details - webpage_skip: Skip extraction of embedded webpage data. One or both - of player_response, initial_data. Using these will not skip any - network requests, and in some cases will result in additional - network requests. Currently, the default is player_response; - however, typically these are for testing purposes only + 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) - player_params: YouTube player parameters to use for player requests. Will overwrite any default ones set by yt-dlp. - player_js_variant: The player javascript variant to use for n/sig @@ -2352,8 +2354,9 @@ what is prescribed by the site - player_js_version: The player javascript version to use for n/sig deciphering, in the format of signature_timestamp@hash (e.g. - 20348@0004de42). Currently, the default is to force 20514@9f4cc5e4. - You can use actual to go with what is prescribed by the site + 20348@0004de42). The default is to use what is prescribed by the + site, and can be selected with actual. Using any other value will + imply webpage_skip=player_response - comment_sort: top or new (default) - choose comment sorting mode (on YouTube's side) - max_comments: Limit the amount of comments to gather. @@ -2405,8 +2408,8 @@ - use_ad_playback_context: Skip preroll ads to eliminate the mandatory wait period before download. Do NOT use this when passing premium account cookies to yt-dlp, as it will result in a loss of premium - formats. Only effective with the web, web_safari, web_music and mweb - player clients. Either true or false (default) + formats. Only effective with the mweb and web_music player clients. + Either true or false (default) youtube-ejs diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yt-dlp/pyproject.toml new/yt-dlp/pyproject.toml --- old/yt-dlp/pyproject.toml 2026-03-03 17:37:22.000000000 +0100 +++ new/yt-dlp/pyproject.toml 2026-03-13 09:45:22.000000000 +0100 @@ -55,7 +55,7 @@ "requests>=2.32.2,<3", "urllib3>=2.0.2,<3", "websockets>=13.0", - "yt-dlp-ejs==0.5.0", + "yt-dlp-ejs==0.7.0", ] curl-cffi = [ "curl-cffi>=0.5.10,!=0.6.*,!=0.7.*,!=0.8.*,!=0.9.*,<0.15; implementation_name=='cpython'", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yt-dlp/test/test_jsc/test_ejs_integration.py new/yt-dlp/test/test_jsc/test_ejs_integration.py --- old/yt-dlp/test/test_jsc/test_ejs_integration.py 2026-03-03 17:37:22.000000000 +0100 +++ new/yt-dlp/test/test_jsc/test_ejs_integration.py 2026-03-13 09:45:22.000000000 +0100 @@ -53,117 +53,49 @@ CHALLENGES: list[Challenge] = [ - Challenge('3d3ba064', Variant.tce, JsChallengeType.N, { - 'ZdZIqFPQK-Ty8wId': 'qmtUsIz04xxiNW', - '4GMrWHyKI5cEvhDO': 'N9gmEX7YhKTSmw', + # 20518 + Challenge('edc3ba07', Variant.tv, JsChallengeType.N, { + 'BQoJvGBkC2nj1ZZLK-': '-m-se9fQVnvEofLx', }), - Challenge('3d3ba064', Variant.tce, JsChallengeType.SIG, { - 'gN7a-hudCuAuPH6fByOk1_GNXN0yNMHShjZXS2VOgsEItAJz0tipeavEOmNdYN-wUtcEqD3bCXjc0iyKfAyZxCBGgIARwsSdQfJ2CJtt': - 'ttJC2JfQdSswRAIgGBCxZyAfKyi0cjXCb3gqEctUw-NYdNmOEvaepit0zJAtIEsgOV2SXZjhSHMNy0NXNG_1kNyBf6HPuAuCduh-a7O', - }), - Challenge('5ec65609', Variant.tce, JsChallengeType.N, { - '0eRGgQWJGfT5rFHFj': '4SvMpDQH-vBJCw', - }), - Challenge('5ec65609', Variant.tce, JsChallengeType.SIG, { - 'AAJAJfQdSswRQIhAMG5SN7-cAFChdrE7tLA6grH0rTMICA1mmDc0HoXgW3CAiAQQ4=CspfaF_vt82XH5yewvqcuEkvzeTsbRuHssRMyJQ=I': - 'AJfQdSswRQIhAMG5SN7-cAFChdrE7tLA6grI0rTMICA1mmDc0HoXgW3CAiAQQ4HCspfaF_vt82XH5yewvqcuEkvzeTsbRuHssRMyJQ==', - }), - Challenge('6742b2b9', Variant.tce, JsChallengeType.N, { - '_HPB-7GFg1VTkn9u': 'qUAsPryAO_ByYg', - 'K1t_fcB6phzuq2SF': 'Y7PcOt3VE62mog', - }), - Challenge('6742b2b9', Variant.tce, JsChallengeType.SIG, { - 'MMGZJMUucirzS_SnrSPYsc85CJNnTUi6GgR5NKn-znQEICACojE8MHS6S7uYq4TGjQX_D4aPk99hNU6wbTvorvVVMgIARwsSdQfJAA': - 'AJfQdSswRAIgMVVvrovTbw6UNh99kPa4D_XQjGT4qYu7S6SHM8EjoCACIEQnz-nKN5RgG6iUTnNJC58csYPSrnS_SzricuUMJZGM', - }), - Challenge('2b83d2e0', Variant.main, JsChallengeType.N, { - '0eRGgQWJGfT5rFHFj': 'euHbygrCMLksxd', - }), - Challenge('2b83d2e0', Variant.main, JsChallengeType.SIG, { - 'MMGZJMUucirzS_SnrSPYsc85CJNnTUi6GgR5NKn-znQEICACojE8MHS6S7uYq4TGjQX_D4aPk99hNU6wbTvorvVVMgIARwsSdQfJA': - '-MGZJMUucirzS_SnrSPYsc85CJNnTUi6GgR5NKnMznQEICACojE8MHS6S7uYq4TGjQX_D4aPk99hNU6wbTvorvVVMgIARwsSdQfJ', - }), - Challenge('638ec5c6', Variant.main, JsChallengeType.N, { - 'ZdZIqFPQK-Ty8wId': '1qov8-KM-yH', - }), - Challenge('638ec5c6', Variant.main, JsChallengeType.SIG, { - 'gN7a-hudCuAuPH6fByOk1_GNXN0yNMHShjZXS2VOgsEItAJz0tipeavEOmNdYN-wUtcEqD3bCXjc0iyKfAyZxCBGgIARwsSdQfJ2CJtt': - 'MhudCuAuP-6fByOk1_GNXN7gNHHShjyXS2VOgsEItAJz0tipeav0OmNdYN-wUtcEqD3bCXjc0iyKfAyZxCBGgIARwsSdQfJ2CJtt', - }), - # c1c87fb0: tce variant broke sig solving; n and main variant are added only for regression testing - Challenge('c1c87fb0', Variant.main, JsChallengeType.N, { - 'ZdZIqFPQK-Ty8wId': 'jCHBK5GuAFNa2', - }), - Challenge('c1c87fb0', Variant.main, JsChallengeType.SIG, { - 'gN7a-hudCuAuPH6fByOk1_GNXN0yNMHShjZXS2VOgsEItAJz0tipeavEOmNdYN-wUtcEqD3bCXjc0iyKfAyZxCBGgIARwsSdQfJ2CJtt': - 'ttJC2JfQdSswRAIgGBCxZyAfKyi0cjXCb3DqEctUw-NYdNmOEvaepit0zJAtIEsgOV2SXZjhSHMNy0NXNGa1kOyBf6HPuAuCduh-_', - }), - Challenge('c1c87fb0', Variant.tce, JsChallengeType.N, { - 'ZdZIqFPQK-Ty8wId': 'jCHBK5GuAFNa2', - }), - Challenge('c1c87fb0', Variant.tce, JsChallengeType.SIG, { - 'gN7a-hudCuAuPH6fByOk1_GNXN0yNMHShjZXS2VOgsEItAJz0tipeavEOmNdYN-wUtcEqD3bCXjc0iyKfAyZxCBGgIARwsSdQfJ2CJtt': - 'ttJC2JfQdSswRAIgGBCxZyAfKyi0cjXCb3DqEctUw-NYdNmOEvaepit0zJAtIEsgOV2SXZjhSHMNy0NXNGa1kOyBf6HPuAuCduh-_', - }), - # 4e51e895: main variant broke sig solving; n challenge is added only for regression testing - Challenge('4e51e895', Variant.main, JsChallengeType.N, { - '0eRGgQWJGfT5rFHFj': 't5kO23_msekBur', - }), - Challenge('4e51e895', Variant.main, JsChallengeType.SIG, { - 'AL6p_8AwdY9yAhRzK8rYA_9n97Kizf7_9n97Kizf7_9n97Kizf7_9n97Kizf7_9n97Kizf7_9n97Kizf7': - 'AwdY9yAhRzK8rYA_9n97Kizf7_9n97Kizf7_9n9pKizf7_9n97Kizf7_9n97Kizf7_9n97Kizf7', - }), - # 42c5570b: tce variant broke sig solving; n challenge is added only for regression testing - Challenge('42c5570b', Variant.tce, JsChallengeType.N, { - 'ZdZIqFPQK-Ty8wId': 'CRoXjB-R-R', - }), - Challenge('42c5570b', Variant.tce, JsChallengeType.SIG, { - 'gN7a-hudCuAuPH6fByOk1_GNXN0yNMHShjZXS2VOgsEItAJz0tipeavEOmNdYN-wUtcEqD3bCXjc0iyKfAyZxCBGgIARwsSdQfJ2CJtt': - 'EN7a-hudCuAuPH6fByOk1_GNXN0yNMHShjZXS2VOgsEItAJz0tipeavcOmNdYN-wUtgEqD3bCXjc0iyKfAyZxCBGgIARwsSdQfJ2CJtt', - }), - # 54bd1de4: tce variant broke sig solving; n challenge is added only for regression testing - Challenge('54bd1de4', Variant.tce, JsChallengeType.N, { - 'ZdZIqFPQK-Ty8wId': 'ka-slAQ31sijFN', - }), - Challenge('54bd1de4', Variant.tce, JsChallengeType.SIG, { - 'gN7a-hudCuAuPH6fByOk1_GNXN0yNMHShjZXS2VOgsEItAJz0tipeavEOmNdYN-wUtcEqD3bCXjc0iyKfAyZxCBGgIARwsSdQfJ2CJtt': - 'gN7a-hudCuAuPH6fByOk1_GNXN0yNMHShjZXS2VOgsEItAJz0titeavEOmNdYN-wUtcEqD3bCXjc0iyKfAyZxCBGgIARwsSdQfJ2CJtp', - }), - # 94667337: tce and es6 variants broke sig solving; n and main/tv variants are added only for regression testing - Challenge('94667337', Variant.main, JsChallengeType.N, { - 'BQoJvGBkC2nj1ZZLK-': 'ib1ShEOGoFXIIw', - }), - Challenge('94667337', Variant.main, JsChallengeType.SIG, { + Challenge('edc3ba07', Variant.tv, JsChallengeType.SIG, { 'NJAJEij0EwRgIhAI0KExTgjfPk-MPM9MAdzyyPRt=BM8-XO5tm5hlMCSVpAiEAv7eP3CURqZNSPow8BXXAoazVoXgeMP7gH9BdylHCwgw=gwzz': - 'AJEij0EwRgIhAI0KExTgjfPk-MPM9MNdzyyPRtzBM8-XO5tm5hlMCSVpAiEAv7eP3CURqZNSPow8BXXAoazVoXgeMP7gH9BdylHCwgw=', + 'zwg=wgwCHlydB9zg7PMegXoVzaoAXXB8woPSNZqRUC3Pe7vAEiApVSCMlh5mt5OX-8MB=tRPyyEdAM9MPM-kPfjgTxEK0IAhIgRwE0jiz', }), - Challenge('94667337', Variant.tv, JsChallengeType.N, { - 'BQoJvGBkC2nj1ZZLK-': 'ib1ShEOGoFXIIw', + # 20521 + Challenge('316b61b4', Variant.tv, JsChallengeType.N, { + 'IlLiA21ny7gqA2m4p37': 'GchRcsUC_WmnhOUVGV', }), - Challenge('94667337', Variant.tv, JsChallengeType.SIG, { + Challenge('316b61b4', Variant.tv, JsChallengeType.SIG, { 'NJAJEij0EwRgIhAI0KExTgjfPk-MPM9MAdzyyPRt=BM8-XO5tm5hlMCSVpAiEAv7eP3CURqZNSPow8BXXAoazVoXgeMP7gH9BdylHCwgw=gwzz': - 'AJEij0EwRgIhAI0KExTgjfPk-MPM9MNdzyyPRtzBM8-XO5tm5hlMCSVpAiEAv7eP3CURqZNSPow8BXXAoazVoXgeMP7gH9BdylHCwgw=', + 'tJAJEij0EwRgIhAI0KExTgjfPk-MPM9MAdzyyPRN=BM8-XO5tm5hlMCSVpAiEAv7eP3CURqZNSPow8BXXAoazVoXgeMP7gH9BdylHCwgw=gwz', }), - Challenge('94667337', Variant.es6, JsChallengeType.N, { - 'BQoJvGBkC2nj1ZZLK-': 'ib1ShEOGoFXIIw', + # 20522 + Challenge('74edf1a3', Variant.tv, JsChallengeType.N, { + 'IlLiA21ny7gqA2m4p37': '9nRTxrbM1f0yHg', + 'eabGFpsUKuWHXGh6FR4': 'izmYqDEY6kl7Sg', + 'eabGF/ps%UK=uWHXGh6FR4': 'LACmqlhaBpiPlgE-a', }), - Challenge('94667337', Variant.es6, JsChallengeType.SIG, { + Challenge('74edf1a3', Variant.tv, JsChallengeType.SIG, { 'NJAJEij0EwRgIhAI0KExTgjfPk-MPM9MAdzyyPRt=BM8-XO5tm5hlMCSVpAiEAv7eP3CURqZNSPow8BXXAoazVoXgeMP7gH9BdylHCwgw=gwzz': - 'AJEij0EwRgIhAI0KExTgjfPk-MPM9MNdzyyPRtzBM8-XO5tm5hlMCSVpAiEAv7eP3CURqZNSPow8BXXAoazVoXgeMP7gH9BdylHCwgw=', - }), - Challenge('94667337', Variant.tce, JsChallengeType.N, { - 'BQoJvGBkC2nj1ZZLK-': 'ib1ShEOGoFXIIw', + 'NJAJEij0EwRgIhAI0KExTgjfPk-MPM9MAdzyyPRt=BM8-XO5tm5hzMCSVpAiEAv7eP3CURqZNSPow8BXXAoazVoXgeMP7gH9BdylHCwgw=gwzl', + '\x00\x01\x02%\x03\x04\x05\x06\x07\x08\x09\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49': + '\x00\x01\x02%\x03\x04\x05\x06\x07\x08\x09\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x40\x41\x42\x49\x44\x45\x46\x47\x48\x43', + }), + # 20523 + Challenge('901741ab', Variant.tv, JsChallengeType.N, { + 'BQoJvGBkC2nj1ZZLK-': 'UMPovvBZRh-sjb', }), - Challenge('94667337', Variant.tce, JsChallengeType.SIG, { + Challenge('901741ab', Variant.tv, JsChallengeType.SIG, { 'NJAJEij0EwRgIhAI0KExTgjfPk-MPM9MAdzyyPRt=BM8-XO5tm5hlMCSVpAiEAv7eP3CURqZNSPow8BXXAoazVoXgeMP7gH9BdylHCwgw=gwzz': - 'AJEij0EwRgIhAI0KExTgjfPk-MPM9MNdzyyPRtzBM8-XO5tm5hlMCSVpAiEAv7eP3CURqZNSPow8BXXAoazVoXgeMP7gH9BdylHCwgw=', + 'wgwCHlydB9Hg7PMegXoVzaoAXXB8woPSNZqRUC3Pe7vAEiApVSCMlhwmt5ON-8MB=5RPyyzdAM9MPM-kPfjgTxEK0IAhIgRwE0jiEJA', }), - Challenge('94667337', Variant.es6_tce, JsChallengeType.N, { - 'BQoJvGBkC2nj1ZZLK-': 'ib1ShEOGoFXIIw', + # 20524 + Challenge('e7573094', Variant.tv, JsChallengeType.N, { + 'IlLiA21ny7gqA2m4p37': '3KuQ3235dojTSjo4', }), - Challenge('94667337', Variant.es6_tce, JsChallengeType.SIG, { + Challenge('e7573094', Variant.tv, JsChallengeType.SIG, { 'NJAJEij0EwRgIhAI0KExTgjfPk-MPM9MAdzyyPRt=BM8-XO5tm5hlMCSVpAiEAv7eP3CURqZNSPow8BXXAoazVoXgeMP7gH9BdylHCwgw=gwzz': - 'AJEij0EwRgIhAI0KExTgjfPk-MPM9MNdzyyPRtzBM8-XO5tm5hlMCSVpAiEAv7eP3CURqZNSPow8BXXAoazVoXgeMP7gH9BdylHCwgw=', + 'yEij0EwRgIhAI0KExTgjfPk-MPM9MAdzyNPRt=BM8-XO5tm5hlMCSVNAiEAvpeP3CURqZJSPow8BXXAoazVoXgeMP7gH9BdylHCwgw=g', }), ] Binary files old/yt-dlp/yt-dlp and new/yt-dlp/yt-dlp differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yt-dlp/yt-dlp.1 new/yt-dlp/yt-dlp.1 --- old/yt-dlp/yt-dlp.1 2026-03-03 17:37:41.000000000 +0100 +++ new/yt-dlp/yt-dlp.1 2026-03-13 09:45:35.000000000 +0100 @@ -2752,10 +2752,14 @@ .IP \[bu] 2 \f[V]webpage_skip\f[R]: Skip extraction of embedded webpage data. One or both of \f[V]player_response\f[R], \f[V]initial_data\f[R]. -Using these will not skip any network requests, and in some cases will -result in additional network requests. -Currently, the default is \f[V]player_response\f[R]; however, typically -these are for testing purposes only +These options are for testing purposes and don\[aq]t skip any network +requests. +Neither is skipped by default; however, if a \f[V]player_js_version\f[R] +value other than \f[V]actual\f[R] is used, then +\f[V]webpage_skip=player_response\f[R] is implied +.IP \[bu] 2 +\f[V]webpage_client\f[R]: Client to use for the video webpage request. +One of \f[V]web\f[R] or \f[V]web_safari\f[R] (default) .IP \[bu] 2 \f[V]player_params\f[R]: YouTube player parameters to use for player requests. @@ -2773,8 +2777,9 @@ n/sig deciphering, in the format of \f[V]signature_timestamp\[at]hash\f[R] (e.g. \f[V]20348\[at]0004de42\f[R]). -Currently, the default is to force \f[V]20514\[at]9f4cc5e4\f[R]. -You can use \f[V]actual\f[R] to go with what is prescribed by the site +The default is to use what is prescribed by the site, and can be +selected with \f[V]actual\f[R]. +Using any other value will imply \f[V]webpage_skip=player_response\f[R] .IP \[bu] 2 \f[V]comment_sort\f[R]: \f[V]top\f[R] or \f[V]new\f[R] (default) - choose comment sorting mode (on YouTube\[aq]s side) @@ -2854,8 +2859,8 @@ mandatory wait period before download. Do NOT use this when passing premium account cookies to yt-dlp, as it will result in a loss of premium formats. -Only effective with the \f[V]web\f[R], \f[V]web_safari\f[R], -\f[V]web_music\f[R] and \f[V]mweb\f[R] player clients. +Only effective with the \f[V]mweb\f[R] and \f[V]web_music\f[R] player +clients. Either \f[V]true\f[R] or \f[V]false\f[R] (default) .SS youtube-ejs .IP \[bu] 2 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yt-dlp/yt_dlp/extractor/tiktok.py new/yt-dlp/yt_dlp/extractor/tiktok.py --- old/yt-dlp/yt_dlp/extractor/tiktok.py 2026-03-03 17:37:22.000000000 +0100 +++ new/yt-dlp/yt_dlp/extractor/tiktok.py 2026-03-13 09:45:22.000000000 +0100 @@ -220,7 +220,7 @@ raise ExtractorError('Unable to extract aweme detail info', video_id=aweme_id) return self._parse_aweme_video_app(aweme_detail) - def _solve_challenge_and_set_cookie(self, webpage): + def _solve_challenge_and_set_cookies(self, webpage): challenge_data = traverse_obj(webpage, ( {find_element(id='cs', html=True)}, {extract_attributes}, 'class', filter, {lambda x: f'{x}==='}, {base64.b64decode}, {json.loads})) @@ -250,17 +250,27 @@ else: raise ExtractorError('Unable to solve JS challenge') - cookie_value = base64.b64encode( + wci_cookie_value = base64.b64encode( json.dumps(challenge_data, separators=(',', ':')).encode()).decode() - # At time of writing, the cookie name was _wafchallengeid - cookie_name = traverse_obj(webpage, ( + # At time of writing, the wci cookie name was `_wafchallengeid` + wci_cookie_name = traverse_obj(webpage, ( {find_element(id='wci', html=True)}, {extract_attributes}, 'class', {require('challenge cookie name')})) - # Actual JS sets Max-Age=1, but we need to adjust for --sleep-requests and Python slowness - expire_time = int(time.time()) + (self.get_param('sleep_interval_requests') or 0) + 2 - self._set_cookie('.tiktok.com', cookie_name, cookie_value, expire_time=expire_time) + # At time of writing, the **optional** rci cookie name was `waforiginalreid` + rci_cookie_name = traverse_obj(webpage, ( + {find_element(id='rci', html=True)}, {extract_attributes}, 'class')) + rci_cookie_value = traverse_obj(webpage, ( + {find_element(id='rs', html=True)}, {extract_attributes}, 'class')) + + # Actual JS sets Max-Age=1 for the cookies, but we'll manually clear them later instead + expire_time = int(time.time()) + (self.get_param('sleep_interval_requests') or 0) + 120 + self._set_cookie('.tiktok.com', wci_cookie_name, wci_cookie_value, expire_time=expire_time) + if rci_cookie_name and rci_cookie_value: + self._set_cookie('.tiktok.com', rci_cookie_name, rci_cookie_value, expire_time=expire_time) + + return wci_cookie_name, rci_cookie_name def _extract_web_data_and_status(self, url, video_id, fatal=True): video_data, status = {}, -1 @@ -287,7 +297,7 @@ universal_data = self._get_universal_data(webpage, video_id) if not universal_data: try: - self._solve_challenge_and_set_cookie(webpage) + cookie_names = self._solve_challenge_and_set_cookies(webpage) except ExtractorError as e: if fatal: raise @@ -295,6 +305,9 @@ return video_data, status webpage = get_webpage(note='Downloading webpage with challenge cookie') + # Manually clear challenge cookies that should expire immediately after webpage request + for cookie_name in filter(None, cookie_names): + self.cookiejar.clear(domain='.tiktok.com', path='/', name=cookie_name) if webpage is False: return video_data, status universal_data = self._get_universal_data(webpage, video_id) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yt-dlp/yt_dlp/extractor/youtube/_base.py new/yt-dlp/yt_dlp/extractor/youtube/_base.py --- old/yt-dlp/yt_dlp/extractor/youtube/_base.py 2026-03-03 17:37:22.000000000 +0100 +++ new/yt-dlp/yt_dlp/extractor/youtube/_base.py 2026-03-13 09:45:22.000000000 +0100 @@ -104,7 +104,6 @@ }, 'INNERTUBE_CONTEXT_CLIENT_NAME': 1, 'SUPPORTS_COOKIES': True, - 'SUPPORTS_AD_PLAYBACK_CONTEXT': True, **WEB_PO_TOKEN_POLICIES, }, # Safari UA returns pre-merged video+audio 144p/240p/360p/720p/1080p HLS formats @@ -118,7 +117,6 @@ }, 'INNERTUBE_CONTEXT_CLIENT_NAME': 1, 'SUPPORTS_COOKIES': True, - 'SUPPORTS_AD_PLAYBACK_CONTEXT': True, **WEB_PO_TOKEN_POLICIES, }, 'web_embedded': { @@ -223,16 +221,17 @@ }, 'PLAYER_PO_TOKEN_POLICY': PlayerPoTokenPolicy(required=False, recommended=True), }, - # YouTube Kids videos aren't returned on this client for some reason + # "Made for kids" videos aren't available with this client + # Using a clientVersion>1.65 may return SABR streams only 'android_vr': { 'INNERTUBE_CONTEXT': { 'client': { 'clientName': 'ANDROID_VR', - 'clientVersion': '1.71.26', + 'clientVersion': '1.65.10', 'deviceMake': 'Oculus', 'deviceModel': 'Quest 3', 'androidSdkVersion': 32, - 'userAgent': 'com.google.android.apps.youtube.vr.oculus/1.71.26 (Linux; U; Android 12L; eureka-user Build/SQ3A.220605.009.A1) gzip', + 'userAgent': 'com.google.android.apps.youtube.vr.oculus/1.65.10 (Linux; U; Android 12L; eureka-user Build/SQ3A.220605.009.A1) gzip', 'osName': 'Android', 'osVersion': '12L', }, @@ -369,7 +368,7 @@ def _fix_embedded_ytcfg(ytcfg): ytcfg['INNERTUBE_CONTEXT'].setdefault('thirdParty', {}).update({ - 'embedUrl': 'https://www.youtube.com/', # Can be any valid URL + 'embedUrl': 'https://www.reddit.com/', # Can be any valid non-YouTube URL }) @@ -958,16 +957,25 @@ url = { 'mweb': 'https://m.youtube.com', 'web': 'https://www.youtube.com', + 'web_safari': 'https://www.youtube.com', 'web_music': 'https://music.youtube.com', + 'web_creator': 'https://studio.youtube.com', 'web_embedded': f'https://www.youtube.com/embed/{video_id}?html5=1', 'tv': 'https://www.youtube.com/tv', }.get(client) if not url: return {} + + default_ytcfg = self._get_default_ytcfg(client) + + if default_ytcfg['REQUIRE_AUTH'] and not self.is_authenticated: + return {} + webpage = self._download_webpage_with_retries( url, video_id, note=f'Downloading {client.replace("_", " ").strip()} client config', - headers=traverse_obj(self._get_default_ytcfg(client), { + headers=traverse_obj(default_ytcfg, { 'User-Agent': ('INNERTUBE_CONTEXT', 'client', 'userAgent', {str}), + 'Referer': ('INNERTUBE_CONTEXT', 'thirdParty', 'embedUrl', {str}), })) ytcfg = self.extract_ytcfg(video_id, webpage) or {} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yt-dlp/yt_dlp/extractor/youtube/_tab.py new/yt-dlp/yt_dlp/extractor/youtube/_tab.py --- old/yt-dlp/yt_dlp/extractor/youtube/_tab.py 2026-03-03 17:37:22.000000000 +0100 +++ new/yt-dlp/yt_dlp/extractor/youtube/_tab.py 2026-03-13 09:45:22.000000000 +0100 @@ -81,7 +81,7 @@ 'reelPlayerHeaderSupportedRenderers', 'reelPlayerHeaderRenderer')) title = self._get_text(renderer, 'title', 'headline') or self._get_text(reel_header_renderer, 'reelTitleText') - description = self._get_text(renderer, 'descriptionSnippet') + description = self._get_text(renderer, 'descriptionSnippet', ('detailedMetadataSnippets', ..., 'snippetText')) duration = int_or_none(renderer.get('lengthSeconds')) if duration is None: @@ -2148,7 +2148,7 @@ f'https://music.youtube.com/playlist?list={item_id[2:]}', YoutubeTabIE, item_id[2:]) elif item_id[:2] == 'MP': # Resolve albums (/[channel/browse]/MP...) to their equivalent playlist mdata = self._extract_tab_endpoint( - f'https://music.youtube.com/channel/{item_id}', item_id, default_client='web_music') + f'https://music.youtube.com/browse/{item_id}', item_id, default_client='web_music') murl = traverse_obj(mdata, ('microformat', 'microformatDataRenderer', 'urlCanonical'), get_all=False, expected_type=str) if not murl: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yt-dlp/yt_dlp/extractor/youtube/_video.py new/yt-dlp/yt_dlp/extractor/youtube/_video.py --- old/yt-dlp/yt_dlp/extractor/youtube/_video.py 2026-03-03 17:37:22.000000000 +0100 +++ new/yt-dlp/yt_dlp/extractor/youtube/_video.py 2026-03-13 09:45:22.000000000 +0100 @@ -140,11 +140,13 @@ _RETURN_TYPE = 'video' # XXX: How to handle multifeed? _SUBTITLE_FORMATS = ('json3', 'srv1', 'srv2', 'srv3', 'ttml', 'srt', 'vtt') - _DEFAULT_CLIENTS = ('android_vr', 'web', 'web_safari') + _DEFAULT_CLIENTS = ('android_vr', 'web_safari') _DEFAULT_JSLESS_CLIENTS = ('android_vr',) - _DEFAULT_AUTHED_CLIENTS = ('tv_downgraded', 'web', 'web_safari') + _DEFAULT_AUTHED_CLIENTS = ('tv_downgraded', 'web_safari') # Premium does not require POT (except for subtitles) - _DEFAULT_PREMIUM_CLIENTS = ('tv_downgraded', 'web_creator', 'web') + _DEFAULT_PREMIUM_CLIENTS = ('tv_downgraded', 'web_creator') + _WEBPAGE_CLIENTS = ('web', 'web_safari') + _DEFAULT_WEBPAGE_CLIENT = 'web_safari' _GEO_BYPASS = False @@ -1873,12 +1875,7 @@ 'params': {'skip_download': True}, }] - @property - def _skipped_webpage_data(self): - # XXX: player_response as a default is a TEMPORARY workaround for pinning _DEFAULT_PLAYER_JS_VERSION - return self._configuration_arg('webpage_skip', default=['player_response']) - - _DEFAULT_PLAYER_JS_VERSION = '20514@9f4cc5e4' + _DEFAULT_PLAYER_JS_VERSION = 'actual' _DEFAULT_PLAYER_JS_VARIANT = 'tv' _PLAYER_JS_VARIANT_MAP = { 'main': 'player_ias.vflset/en_US/base.js', @@ -1895,6 +1892,18 @@ } _INVERSE_PLAYER_JS_VARIANT_MAP = {v: k for k, v in _PLAYER_JS_VARIANT_MAP.items()} + @functools.cached_property + def _player_js_version(self): + return self._configuration_arg('player_js_version', [None])[0] or self._DEFAULT_PLAYER_JS_VERSION + + @functools.cached_property + def _skipped_webpage_data(self): + skipped = set(self._configuration_arg('webpage_skip')) + # If forcing a player version, the webpage player response must be skipped + if self._player_js_version != 'actual': + skipped.add('player_response') + return skipped + @classmethod def suitable(cls, url): from yt_dlp.utils import parse_qs @@ -2082,15 +2091,14 @@ time.sleep(max(0, FETCH_SPAN + fetch_time - time.time())) def _get_player_js_version(self): - player_js_version = self._configuration_arg('player_js_version', [''])[0] or self._DEFAULT_PLAYER_JS_VERSION - if player_js_version == 'actual': + if self._player_js_version == 'actual': return None, None - if not re.fullmatch(r'[0-9]{5,}@[0-9a-f]{8,}', player_js_version): + if not re.fullmatch(r'[0-9]{5,}@[0-9a-f]{8,}', self._player_js_version): self.report_warning( - f'Invalid player JS version "{player_js_version}" specified. ' + f'Invalid player JS version "{self._player_js_version}" specified. ' f'It should be "actual" or in the format of STS@HASH', only_once=True) return None, None - return player_js_version.split('@') + return self._player_js_version.split('@') def _construct_player_url(self, *, player_id=None, player_url=None): assert player_id or player_url, '_construct_player_url must take one of player_id or player_url' @@ -2680,12 +2688,14 @@ return {'contentCheckOk': True, 'racyCheckOk': True} @classmethod - def _generate_player_context(cls, sts=None, use_ad_playback_context=False): + def _generate_player_context(cls, sts=None, use_ad_playback_context=False, encrypted_context=None): context = { 'html5Preference': 'HTML5_PREF_WANTS', } if sts is not None: context['signatureTimestamp'] = sts + if encrypted_context: + context['encryptedHostFlags'] = encrypted_context playback_context = { 'contentPlaybackContext': context, @@ -2930,7 +2940,19 @@ self._configuration_arg('use_ad_playback_context', ['false'])[0] != 'false' and traverse_obj(INNERTUBE_CLIENTS, (client, 'SUPPORTS_AD_PLAYBACK_CONTEXT', {bool}))) - yt_query.update(self._generate_player_context(sts, use_ad_playback_context)) + # web_embedded player requests may need to include encryptedHostFlags in its contentPlaybackContext. + # This can be detected with the embeds_enable_encrypted_host_flags_enforcement experiemnt flag, + # but there is no harm in including encryptedHostFlags with all web_embedded player requests. + encrypted_context = None + if _split_innertube_client(client)[2] == 'embedded': + encrypted_context = traverse_obj(player_ytcfg, ( + 'WEB_PLAYER_CONTEXT_CONFIGS', 'WEB_PLAYER_CONTEXT_CONFIG_ID_EMBEDDED_PLAYER', 'encryptedHostFlags')) + + yt_query.update( + self._generate_player_context( + sts=sts, + use_ad_playback_context=use_ad_playback_context, + encrypted_context=encrypted_context)) return self._extract_response( item_id=video_id, ep='player', query=yt_query, @@ -3880,7 +3902,12 @@ base_url = self.http_scheme() + '//www.youtube.com/' webpage_url = base_url + 'watch?v=' + video_id - webpage_client = 'web' + webpage_client = self._configuration_arg('webpage_client', [self._DEFAULT_WEBPAGE_CLIENT])[0] + if webpage_client not in self._WEBPAGE_CLIENTS: + self.report_warning( + f'Invalid webpage_client "{webpage_client}" requested; ' + f'falling back to {self._DEFAULT_WEBPAGE_CLIENT}', only_once=True) + webpage_client = self._DEFAULT_WEBPAGE_CLIENT webpage, webpage_ytcfg, initial_data, is_premium_subscriber, player_responses, player_url = self._initial_extract( url, smuggled_data, webpage_url, webpage_client, video_id) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yt-dlp/yt_dlp/extractor/youtube/jsc/_builtin/vendor/_info.py new/yt-dlp/yt_dlp/extractor/youtube/jsc/_builtin/vendor/_info.py --- old/yt-dlp/yt_dlp/extractor/youtube/jsc/_builtin/vendor/_info.py 2026-03-03 17:37:22.000000000 +0100 +++ new/yt-dlp/yt_dlp/extractor/youtube/jsc/_builtin/vendor/_info.py 2026-03-13 09:45:22.000000000 +0100 @@ -1,10 +1,10 @@ # This file is generated by devscripts/update_ejs.py. DO NOT MODIFY! -VERSION = '0.5.0' +VERSION = '0.7.0' HASHES = { 'yt.solver.bun.lib.js': '6ff45e94de9f0ea936a183c48173cfa9ce526ee4b7544cd556428427c1dd53c8073ef0174e79b320252bf0e7c64b0032cc1cf9c4358f3fda59033b7caa01c241', - 'yt.solver.core.js': '9742868113d7b0c29e24a95c8eb2c2bec7cdf95513dc7f55f523ba053c0ecf2af7dcb0138b1d933578304f0dda633a6b3bfff64e912b4c547b99dad083428c4b', - 'yt.solver.core.min.js': 'aee8c3354cfd535809c871c2a517d03231f89cd184e903af82ee274bcc2e90991ef19cb3f65f2ccc858c4963856ea87f8692fe16d71209f4fc7f41c44b828e36', + 'yt.solver.core.js': '84e91a8ae91684272d11f1ef0970c757e9fec9ab277fb415b976c156163dde6ae2a9857c19c1ee21c9dcd01e2f89071098a1de2dc3072cf3ceeded84537db5e4', + 'yt.solver.core.min.js': 'd965ec01dcf44a0a9dea43f5935141c788471de9e8def5bf70d0b88ca656b79ca983d3e595f84b788d921dc98b900b7bf7380e9775ccb3b70a87c865482c71e3', 'yt.solver.deno.lib.js': '9c8ee3ab6c23e443a5a951e3ac73c6b8c1c8fb34335e7058a07bf99d349be5573611de00536dcd03ecd3cf34014c4e9b536081de37af3637c5390c6a6fd6a0f0', 'yt.solver.lib.js': '1ee3753a8222fc855f5c39db30a9ccbb7967dbe1fb810e86dc9a89aa073a0907f294c720e9b65427d560a35aa1ce6af19ef854d9126a05ca00afe03f72047733', 'yt.solver.lib.min.js': '8420c259ad16e99ce004e4651ac1bcabb53b4457bf5668a97a9359be9a998a789fee8ab124ee17f91a2ea8fd84e0f2b2fc8eabcaf0b16a186ba734cf422ad053', diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yt-dlp/yt_dlp/extractor/youtube/jsc/_builtin/vendor/yt.solver.core.js new/yt-dlp/yt_dlp/extractor/youtube/jsc/_builtin/vendor/yt.solver.core.js --- old/yt-dlp/yt_dlp/extractor/youtube/jsc/_builtin/vendor/yt.solver.core.js 2026-03-03 17:37:22.000000000 +0100 +++ new/yt-dlp/yt_dlp/extractor/youtube/jsc/_builtin/vendor/yt.solver.core.js 2026-03-13 09:45:22.000000000 +0100 @@ -39,7 +39,10 @@ function isOneOf(value, ...of) { return of.includes(value); } - function _optionalChain$2(ops) { + function generateArrowFunction(data) { + return meriyah.parse(data).body[0].expression; + } + function _optionalChain$1(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; @@ -60,94 +63,7 @@ } return value; } - const nsig = { - type: 'CallExpression', - callee: { or: [{ type: 'Identifier' }, { type: 'SequenceExpression' }] }, - arguments: [ - {}, - { - type: 'CallExpression', - callee: { type: 'Identifier', name: 'decodeURIComponent' }, - arguments: [{}], - }, - ], - }; - const nsigAssignment = { - type: 'AssignmentExpression', - left: { type: 'Identifier' }, - operator: '=', - right: nsig, - }; - const nsigDeclarator = { - type: 'VariableDeclarator', - id: { type: 'Identifier' }, - init: nsig, - }; - const logicalExpression = { - type: 'ExpressionStatement', - expression: { - type: 'LogicalExpression', - left: { type: 'Identifier' }, - right: { - type: 'SequenceExpression', - expressions: [ - { - type: 'AssignmentExpression', - left: { type: 'Identifier' }, - operator: '=', - right: { - type: 'CallExpression', - callee: { type: 'Identifier' }, - arguments: { - or: [ - [ - { - type: 'CallExpression', - callee: { - type: 'Identifier', - name: 'decodeURIComponent', - }, - arguments: [{ type: 'Identifier' }], - optional: false, - }, - ], - [ - { type: 'Literal' }, - { - type: 'CallExpression', - callee: { - type: 'Identifier', - name: 'decodeURIComponent', - }, - arguments: [{ type: 'Identifier' }], - optional: false, - }, - ], - [ - { type: 'Literal' }, - { type: 'Literal' }, - { - type: 'CallExpression', - callee: { - type: 'Identifier', - name: 'decodeURIComponent', - }, - arguments: [{ type: 'Identifier' }], - optional: false, - }, - ], - ], - }, - optional: false, - }, - }, - { type: 'CallExpression' }, - ], - }, - operator: '&&', - }, - }; - const identifier$1 = { + const identifier = { or: [ { type: 'ExpressionStatement', @@ -155,339 +71,108 @@ type: 'AssignmentExpression', operator: '=', left: { or: [{ type: 'Identifier' }, { type: 'MemberExpression' }] }, - right: { type: 'FunctionExpression' }, + right: { type: 'FunctionExpression', async: false }, }, }, - { type: 'FunctionDeclaration' }, + { type: 'FunctionDeclaration', async: false, id: { type: 'Identifier' } }, { type: 'VariableDeclaration', declarations: { anykey: [ { type: 'VariableDeclarator', - init: { type: 'FunctionExpression' }, + init: { type: 'FunctionExpression', async: false }, }, ], }, }, ], }; - function extract$1(node) { - const blocks = []; - if (matchesStructure(node, identifier$1)) { - if ( - node.type === 'ExpressionStatement' && - node.expression.type === 'AssignmentExpression' && - node.expression.right.type === 'FunctionExpression' && - node.expression.right.params.length >= 3 - ) { - blocks.push(node.expression.right.body); - } else if (node.type === 'VariableDeclaration') { - for (const decl of node.declarations) { - if ( - _optionalChain$2([ - decl, - 'access', - (_) => _.init, - 'optionalAccess', - (_2) => _2.type, - ]) === 'FunctionExpression' && - decl.init.params.length >= 3 - ) { - blocks.push(decl.init.body); - } - } - } else if ( - node.type === 'FunctionDeclaration' && - node.params.length >= 3 - ) { - blocks.push(node.body); - } else { - return null; - } - } else if ( - node.type === 'ExpressionStatement' && - node.expression.type === 'SequenceExpression' - ) { - for (const expr of node.expression.expressions) { - if ( - expr.type === 'AssignmentExpression' && - expr.right.type === 'FunctionExpression' && - expr.right.params.length === 3 - ) { - blocks.push(expr.right.body); - } - } - } else { - return null; - } - for (const block of blocks) { - let call = null; - for (const stmt of block.body) { - if (matchesStructure(stmt, logicalExpression)) { - if ( - stmt.type === 'ExpressionStatement' && - stmt.expression.type === 'LogicalExpression' && - stmt.expression.right.type === 'SequenceExpression' && - stmt.expression.right.expressions[0].type === - 'AssignmentExpression' && - stmt.expression.right.expressions[0].right.type === 'CallExpression' - ) { - call = stmt.expression.right.expressions[0].right; - } - } else if (stmt.type === 'IfStatement') { - let consequent = stmt.consequent; - while (consequent.type === 'LabeledStatement') { - consequent = consequent.body; - } - if (consequent.type !== 'BlockStatement') { - continue; - } - for (const n of consequent.body) { - if (n.type !== 'VariableDeclaration') { - continue; - } - for (const decl of n.declarations) { - if ( - matchesStructure(decl, nsigDeclarator) && - _optionalChain$2([ - decl, - 'access', - (_3) => _3.init, - 'optionalAccess', - (_4) => _4.type, - ]) === 'CallExpression' - ) { - call = decl.init; - break; - } - } - if (call) { - break; - } - } - } else if (stmt.type === 'ExpressionStatement') { - if ( - stmt.expression.type !== 'LogicalExpression' || - stmt.expression.operator !== '&&' || - stmt.expression.right.type !== 'SequenceExpression' - ) { - continue; - } - for (const expr of stmt.expression.right.expressions) { - if (matchesStructure(expr, nsigAssignment) && expr.type) { - if ( - expr.type === 'AssignmentExpression' && - expr.right.type === 'CallExpression' - ) { - call = expr.right; - break; - } - } - } - } - if (call) { - break; - } - } - if (!call) { - continue; - } - return { - type: 'ArrowFunctionExpression', - params: [{ type: 'Identifier', name: 'sig' }], - body: { - type: 'CallExpression', - callee: call.callee, - arguments: call.arguments.map((arg) => { - if ( - arg.type === 'CallExpression' && - arg.callee.type === 'Identifier' && - arg.callee.name === 'decodeURIComponent' - ) { - return { type: 'Identifier', name: 'sig' }; - } - return arg; - }), - optional: false, - }, - async: false, - expression: false, - generator: false, - }; - } - return null; - } - function _optionalChain$1(ops) { - let lastAccessLHS = undefined; - let value = ops[0]; - let i = 1; - while (i < ops.length) { - const op = ops[i]; - const fn = ops[i + 1]; - i += 2; - if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { - return undefined; - } - if (op === 'access' || op === 'optionalAccess') { - lastAccessLHS = value; - value = fn(value); - } else if (op === 'call' || op === 'optionalCall') { - value = fn((...args) => value.call(lastAccessLHS, ...args)); - lastAccessLHS = undefined; - } - } - return value; - } - const identifier = { - or: [ - { - type: 'VariableDeclaration', - kind: 'var', - declarations: { - anykey: [ - { - type: 'VariableDeclarator', - id: { type: 'Identifier' }, - init: { - type: 'ArrayExpression', - elements: [{ type: 'Identifier' }], - }, - }, - ], - }, - }, - { - type: 'ExpressionStatement', - expression: { - type: 'AssignmentExpression', - left: { type: 'Identifier' }, - operator: '=', - right: { - type: 'ArrayExpression', - elements: [{ type: 'Identifier' }], - }, - }, - }, - ], - }; - const catchBlockBody = [ - { - type: 'ReturnStatement', - argument: { - type: 'BinaryExpression', - left: { - type: 'MemberExpression', - object: { type: 'Identifier' }, - computed: true, - property: { type: 'Literal' }, - optional: false, - }, - right: { type: 'Identifier' }, - operator: '+', + const asdasd = { + type: 'ExpressionStatement', + expression: { + type: 'CallExpression', + callee: { + type: 'MemberExpression', + object: { type: 'Identifier' }, + property: {}, + optional: false, }, + arguments: [ + { type: 'Literal', value: 'alr' }, + { type: 'Literal', value: 'yes' }, + ], + optional: false, }, - ]; + }; function extract(node) { if (!matchesStructure(node, identifier)) { - let name = null; - let block = null; - switch (node.type) { - case 'ExpressionStatement': { - if ( - node.expression.type === 'AssignmentExpression' && - node.expression.left.type === 'Identifier' && - node.expression.right.type === 'FunctionExpression' && - node.expression.right.params.length === 1 - ) { - name = node.expression.left.name; - block = node.expression.right.body; - } - break; - } - case 'FunctionDeclaration': { - if (node.params.length === 1) { - name = _optionalChain$1([ - node, - 'access', - (_) => _.id, - 'optionalAccess', - (_2) => _2.name, - ]); - block = node.body; - } - break; - } - } - if (!block || !name) { - return null; - } - const tryNode = block.body.at(-2); + return null; + } + const options = []; + if (node.type === 'FunctionDeclaration') { if ( - _optionalChain$1([tryNode, 'optionalAccess', (_3) => _3.type]) !== - 'TryStatement' || + node.id && _optionalChain$1([ - tryNode, + node, 'access', - (_4) => _4.handler, + (_) => _.body, 'optionalAccess', - (_5) => _5.type, - ]) !== 'CatchClause' + (_2) => _2.body, + ]) ) { + options.push({ + name: node.id, + statements: _optionalChain$1([ + node, + 'access', + (_3) => _3.body, + 'optionalAccess', + (_4) => _4.body, + ]), + }); + } + } else if (node.type === 'ExpressionStatement') { + if (node.expression.type !== 'AssignmentExpression') { return null; } - const catchBody = tryNode.handler.body.body; - if (matchesStructure(catchBody, catchBlockBody)) { - return makeSolverFuncFromName(name); + const name = node.expression.left; + const body = _optionalChain$1([ + node.expression.right, + 'optionalAccess', + (_5) => _5.body, + 'optionalAccess', + (_6) => _6.body, + ]); + if (name && body) { + options.push({ name: name, statements: body }); } - return null; - } - if (node.type === 'VariableDeclaration') { + } else if (node.type === 'VariableDeclaration') { for (const declaration of node.declarations) { - if ( - declaration.type !== 'VariableDeclarator' || - !declaration.init || - declaration.init.type !== 'ArrayExpression' || - declaration.init.elements.length !== 1 - ) { - continue; - } - const [firstElement] = declaration.init.elements; - if (firstElement && firstElement.type === 'Identifier') { - return makeSolverFuncFromName(firstElement.name); + const name = declaration.id; + const body = _optionalChain$1([ + declaration.init, + 'optionalAccess', + (_7) => _7.body, + 'optionalAccess', + (_8) => _8.body, + ]); + if (name && body) { + options.push({ name: name, statements: body }); } } - } else if (node.type === 'ExpressionStatement') { - const expr = node.expression; - if ( - expr.type === 'AssignmentExpression' && - expr.left.type === 'Identifier' && - expr.operator === '=' && - expr.right.type === 'ArrayExpression' && - expr.right.elements.length === 1 - ) { - const [firstElement] = expr.right.elements; - if (firstElement && firstElement.type === 'Identifier') { - return makeSolverFuncFromName(firstElement.name); - } + } + for (const { name: name, statements: statements } of options) { + if (matchesStructure(statements, { anykey: [asdasd] })) { + return createSolver(name); } } return null; } - function makeSolverFuncFromName(name) { - return { - type: 'ArrowFunctionExpression', - params: [{ type: 'Identifier', name: 'n' }], - body: { - type: 'CallExpression', - callee: { type: 'Identifier', name: name }, - arguments: [{ type: 'Identifier', name: 'n' }], - optional: false, - }, - async: false, - expression: false, - generator: false, - }; + function createSolver(expression) { + return generateArrowFunction( + `\n({sig, n}) => {\n const url = (${astring.generate(expression)})("https://youtube.com/watch?v=yt-dlp-wins", "s", sig ? encodeURIComponent(sig) : undefined);\n url.set("n", n);\n const proto = Object.getPrototypeOf(url);\n const keys = Object.keys(proto).concat(Object.getOwnPropertyNames(proto));\n for (const key of keys) {\n if (!["constructor", "set", "get", "clone"].includes(key)) {\n url[key]();\n break;\n }\n }\n const s = url.get("s");\n return {\n sig: s ? decodeURIComponent(s) : null,\n n: url.get("n") ?? null,\n };\n}\n`, + ); } const setupNodes = meriyah.parse( `\nif (typeof globalThis.XMLHttpRequest === "undefined") {\n globalThis.XMLHttpRequest = { prototype: {} };\n}\nconst window = Object.create(null);\nif (typeof URL === "undefined") {\n window.location = {\n hash: "",\n host: "www.youtube.com",\n hostname: "www.youtube.com",\n href: "https://www.youtube.com/watch?v=yt-dlp-wins",\n origin: "https://www.youtube.com",\n password: "",\n pathname: "/watch",\n port: "",\n protocol: "https:",\n search: "?v=yt-dlp-wins",\n username: "",\n };\n} else {\n window.location = new URL("https://www.youtube.com/watch?v=yt-dlp-wins");\n}\nif (typeof globalThis.document === "undefined") {\n globalThis.document = Object.create(null);\n}\nif (typeof globalThis.navigator === "undefined") {\n globalThis.navigator = Object.create(null);\n}\nif (typeof globalThis.self === "undefined") {\n globalThis.self = globalThis;\n}\n`, @@ -585,236 +270,60 @@ function getSolutions(statements) { const found = { n: [], sig: [] }; for (const statement of statements) { - const n = extract(statement); - if (n) { - found.n.push(n); - } - const sig = extract$1(statement); - if (sig) { - found.sig.push(sig); + const result = extract(statement); + if (result) { + found.n.push(makeSolver(result, { type: 'Identifier', name: 'n' })); + found.sig.push(makeSolver(result, { type: 'Identifier', name: 'sig' })); } } return found; } - function getFromPrepared(code) { - const resultObj = { n: null, sig: null }; - Function('_result', code)(resultObj); - return resultObj; - } - function multiTry(generators) { + function makeSolver(result, ident) { return { type: 'ArrowFunctionExpression', - params: [{ type: 'Identifier', name: '_input' }], + params: [ident], body: { - type: 'BlockStatement', - body: [ - { - type: 'VariableDeclaration', - kind: 'const', - declarations: [ - { - type: 'VariableDeclarator', - id: { type: 'Identifier', name: '_results' }, - init: { - type: 'NewExpression', - callee: { type: 'Identifier', name: 'Set' }, - arguments: [], - }, - }, - ], - }, - { - type: 'ForOfStatement', - left: { - type: 'VariableDeclaration', - kind: 'const', - declarations: [ - { - type: 'VariableDeclarator', - id: { type: 'Identifier', name: '_generator' }, - init: null, - }, - ], - }, - right: { type: 'ArrayExpression', elements: generators }, - body: { - type: 'BlockStatement', - body: [ - { - type: 'TryStatement', - block: { - type: 'BlockStatement', - body: [ - { - type: 'ExpressionStatement', - expression: { - type: 'CallExpression', - callee: { - type: 'MemberExpression', - object: { type: 'Identifier', name: '_results' }, - computed: false, - property: { type: 'Identifier', name: 'add' }, - optional: false, - }, - arguments: [ - { - type: 'CallExpression', - callee: { - type: 'Identifier', - name: '_generator', - }, - arguments: [ - { type: 'Identifier', name: '_input' }, - ], - optional: false, - }, - ], - optional: false, - }, - }, - ], - }, - handler: { - type: 'CatchClause', - param: null, - body: { type: 'BlockStatement', body: [] }, - }, - finalizer: null, - }, - ], - }, - await: false, - }, - { - type: 'IfStatement', - test: { - type: 'UnaryExpression', - operator: '!', - argument: { - type: 'MemberExpression', - object: { type: 'Identifier', name: '_results' }, - computed: false, - property: { type: 'Identifier', name: 'size' }, - optional: false, - }, - prefix: true, - }, - consequent: { - type: 'BlockStatement', - body: [ - { - type: 'ThrowStatement', - argument: { - type: 'TemplateLiteral', - expressions: [], - quasis: [ - { - type: 'TemplateElement', - value: { cooked: 'no solutions', raw: 'no solutions' }, - tail: true, - }, - ], - }, - }, - ], - }, - alternate: null, - }, - { - type: 'IfStatement', - test: { - type: 'BinaryExpression', - left: { - type: 'MemberExpression', - object: { type: 'Identifier', name: '_results' }, - computed: false, - property: { type: 'Identifier', name: 'size' }, - optional: false, - }, - right: { type: 'Literal', value: 1 }, - operator: '!==', - }, - consequent: { - type: 'BlockStatement', - body: [ + type: 'MemberExpression', + object: { + type: 'CallExpression', + callee: result, + arguments: [ + { + type: 'ObjectExpression', + properties: [ { - type: 'ThrowStatement', - argument: { - type: 'TemplateLiteral', - expressions: [ - { - type: 'CallExpression', - callee: { - type: 'MemberExpression', - object: { type: 'Identifier', name: '_results' }, - computed: false, - property: { type: 'Identifier', name: 'join' }, - optional: false, - }, - arguments: [{ type: 'Literal', value: ', ' }], - optional: false, - }, - ], - quasis: [ - { - type: 'TemplateElement', - value: { - cooked: 'invalid solutions: ', - raw: 'invalid solutions: ', - }, - tail: false, - }, - { - type: 'TemplateElement', - value: { cooked: '', raw: '' }, - tail: true, - }, - ], - }, - }, - ], - }, - alternate: null, - }, - { - type: 'ReturnStatement', - argument: { - type: 'MemberExpression', - object: { - type: 'CallExpression', - callee: { - type: 'MemberExpression', - object: { - type: 'CallExpression', - callee: { - type: 'MemberExpression', - object: { type: 'Identifier', name: '_results' }, - computed: false, - property: { type: 'Identifier', name: 'values' }, - optional: false, - }, - arguments: [], - optional: false, - }, + type: 'Property', + key: ident, + value: ident, + kind: 'init', computed: false, - property: { type: 'Identifier', name: 'next' }, - optional: false, + method: false, + shorthand: true, }, - arguments: [], - optional: false, - }, - computed: false, - property: { type: 'Identifier', name: 'value' }, - optional: false, + ], }, - }, - ], + ], + optional: false, + }, + computed: false, + property: ident, + optional: false, }, async: false, - expression: false, + expression: true, generator: false, }; } + function getFromPrepared(code) { + const resultObj = { n: null, sig: null }; + Function('_result', code)(resultObj); + return resultObj; + } + function multiTry(generators) { + return generateArrowFunction( + `\n(_input) => {\n const _results = new Set();\n const errors = [];\n for (const _generator of ${astring.generate({ type: 'ArrayExpression', elements: generators })}) {\n try {\n _results.add(_generator(_input));\n } catch (e) {\n errors.push(e);\n }\n }\n if (!_results.size) {\n throw \`no solutions: \${errors.join(", ")}\`;\n }\n if (_results.size !== 1) {\n throw \`invalid solutions: \${[..._results].map(x => JSON.stringify(x)).join(", ")}\`;\n }\n return _results.values().next().value;\n}\n`, + ); + } function main(input) { const preprocessedPlayer = input.type === 'player' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yt-dlp/yt_dlp/version.py new/yt-dlp/yt_dlp/version.py --- old/yt-dlp/yt_dlp/version.py 2026-03-03 17:37:32.000000000 +0100 +++ new/yt-dlp/yt_dlp/version.py 2026-03-13 09:45:29.000000000 +0100 @@ -1,8 +1,8 @@ # Autogenerated by devscripts/update-version.py -__version__ = '2026.03.03' +__version__ = '2026.03.13' -RELEASE_GIT_HEAD = '2ecc4c3bc300701d85e2cbaeb2b28a921a68f0f0' +RELEASE_GIT_HEAD = '92f1d99dbe1e10d942ef0963f625dbc5bc0768aa' VARIANT = None @@ -12,4 +12,4 @@ ORIGIN = 'yt-dlp/yt-dlp' -_pkg_version = '2026.03.03' +_pkg_version = '2026.03.13'
