mirror of
https://github.com/yt-dlp/yt-dlp.git
synced 2026-03-18 03:46:19 +03:00
Compare commits
17 Commits
2026.03.03
...
2026.03.17
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7fd74d1009 | ||
|
|
04d6974f50 | ||
|
|
18656b2f2a | ||
|
|
1b6ec8fc25 | ||
|
|
7fab4c2b23 | ||
|
|
66c4947e9c | ||
|
|
4fc768b7f7 | ||
|
|
e68afb2827 | ||
|
|
990fdf36dd | ||
|
|
92f1d99dbe | ||
|
|
db62e438a1 | ||
|
|
3e36cf9cdb | ||
|
|
ae025da023 | ||
|
|
48a61d0f38 | ||
|
|
f2bd3202c0 | ||
|
|
7e145ac1ca | ||
|
|
ff459e5fc0 |
32
.github/workflows/build.yml
vendored
32
.github/workflows/build.yml
vendored
@@ -231,7 +231,7 @@ jobs:
|
||||
[[ "${version}" != "${downgraded_version}" ]]
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: build-bin-${{ github.job }}
|
||||
path: |
|
||||
@@ -267,7 +267,7 @@ jobs:
|
||||
|
||||
- name: Set up QEMU
|
||||
if: matrix.qemu_platform
|
||||
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
|
||||
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
|
||||
with:
|
||||
image: tonistiigi/binfmt:qemu-v10.0.4-56@sha256:30cc9a4d03765acac9be2ed0afc23af1ad018aed2c28ea4be8c2eb9afe03fbd1
|
||||
cache-image: false
|
||||
@@ -294,7 +294,7 @@ jobs:
|
||||
docker compose up --build --exit-code-from "${SERVICE}" "${SERVICE}"
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: build-bin-${{ matrix.os }}_${{ matrix.arch }}
|
||||
path: |
|
||||
@@ -384,7 +384,7 @@ jobs:
|
||||
[[ "$version" != "$downgraded_version" ]]
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: build-bin-${{ github.job }}
|
||||
path: |
|
||||
@@ -407,23 +407,23 @@ jobs:
|
||||
runner: windows-2025
|
||||
python_version: '3.10'
|
||||
platform_tag: win_amd64
|
||||
pyi_version: '6.18.0'
|
||||
pyi_tag: '2026.01.29.160356'
|
||||
pyi_hash: bb9cd0b0b233e4d031a295211cb8aa7c7f8b3c12ff33f1d57a40849ab4d3cf42
|
||||
pyi_version: '6.19.0'
|
||||
pyi_tag: '2026.03.17.175201'
|
||||
pyi_hash: '1a5f4b844abd02bd758ae6b64c5243fed1a2fa641dbcab2f79480c6a7b957e2d'
|
||||
- arch: 'x86'
|
||||
runner: windows-2025
|
||||
python_version: '3.10'
|
||||
platform_tag: win32
|
||||
pyi_version: '6.18.0'
|
||||
pyi_tag: '2026.01.29.160356'
|
||||
pyi_hash: aa8f260e735d94f1e2e1aac42e322f508eb54d0433de803c2998c337f72045e4
|
||||
pyi_version: '6.19.0'
|
||||
pyi_tag: '2026.03.17.175201'
|
||||
pyi_hash: '9b3c791d7e5cc23f5b48dffc3c367dac10a516b86904db48b6096c2b5d1ffb41'
|
||||
- arch: 'arm64'
|
||||
runner: windows-11-arm
|
||||
python_version: '3.13' # arm64 only has Python >= 3.11 available
|
||||
platform_tag: win_arm64
|
||||
pyi_version: '6.18.0'
|
||||
pyi_tag: '2026.01.29.160356'
|
||||
pyi_hash: 4bbca67d0cdfa860d92ac9cc7e4c2586fd393d1e814e3f1375b8c62d5cfb6771
|
||||
pyi_version: '6.19.0'
|
||||
pyi_tag: '2026.03.17.175201'
|
||||
pyi_hash: 'd008e5c8bb2143f7c05c8b5fcc15dab5f079d79425f78af1936c6768f8e87504'
|
||||
env:
|
||||
CHANNEL: ${{ inputs.channel }}
|
||||
ORIGIN: ${{ needs.process.outputs.origin }}
|
||||
@@ -501,7 +501,7 @@ jobs:
|
||||
}
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: build-bin-${{ github.job }}-${{ matrix.arch }}
|
||||
path: |
|
||||
@@ -521,7 +521,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
path: artifact
|
||||
pattern: build-bin-*
|
||||
@@ -590,7 +590,7 @@ jobs:
|
||||
done
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: build-${{ github.job }}
|
||||
path: |
|
||||
|
||||
4
.github/workflows/challenge-tests.yml
vendored
4
.github/workflows/challenge-tests.yml
vendored
@@ -50,13 +50,13 @@ jobs:
|
||||
with:
|
||||
deno-version: '2.0.0' # minimum supported version
|
||||
- name: Install Bun
|
||||
uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2.1.2
|
||||
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
|
||||
with:
|
||||
# minimum supported version is 1.0.31 but earliest available Windows version is 1.1.0
|
||||
bun-version: ${{ (matrix.os == 'windows-latest' && '1.1.0') || '1.0.31' }}
|
||||
no-cache: true
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
with:
|
||||
node-version: '20.0' # minimum supported version
|
||||
- name: Install QuickJS (Linux)
|
||||
|
||||
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@@ -36,12 +36,12 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
|
||||
uses: github/codeql-action/init@b1bff81932f5cdfc8695c7752dcee935dcd061c8 # v4.33.0
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: none
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
|
||||
uses: github/codeql-action/analyze@b1bff81932f5cdfc8695c7752dcee935dcd061c8 # v4.33.0
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
2
.github/workflows/release-master.yml
vendored
2
.github/workflows/release-master.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
path: dist
|
||||
name: build-pypi
|
||||
|
||||
4
.github/workflows/release-nightly.yml
vendored
4
.github/workflows/release-nightly.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
run: echo "head=$(git rev-parse HEAD)" | tee -a "${GITHUB_OUTPUT}"
|
||||
|
||||
- name: Cache nightly commit hash
|
||||
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
||||
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||
env:
|
||||
SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1
|
||||
with:
|
||||
@@ -94,7 +94,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
path: dist
|
||||
name: build-pypi
|
||||
|
||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -214,7 +214,7 @@ jobs:
|
||||
|
||||
- name: Upload artifacts
|
||||
if: github.event.workflow != '.github/workflows/release.yml' # Reusable workflow_call
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: build-pypi
|
||||
path: |
|
||||
@@ -243,7 +243,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
path: artifact
|
||||
pattern: build-*
|
||||
|
||||
8
.github/workflows/test-workflows.yml
vendored
8
.github/workflows/test-workflows.yml
vendored
@@ -26,8 +26,8 @@ concurrency:
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
env:
|
||||
ACTIONLINT_VERSION: "1.7.9"
|
||||
ACTIONLINT_SHA256SUM: 233b280d05e100837f4af1433c7b40a5dcb306e3aa68fb4f17f8a7f45a7df7b4
|
||||
ACTIONLINT_VERSION: "1.7.11"
|
||||
ACTIONLINT_SHA256SUM: 900919a84f2229bac68ca9cd4103ea297abc35e9689ebb842c6e34a3d1b01b0a
|
||||
ACTIONLINT_REPO: https://github.com/rhysd/actionlint
|
||||
|
||||
jobs:
|
||||
@@ -76,8 +76,8 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Run zizmor
|
||||
uses: zizmorcore/zizmor-action@135698455da5c3b3e55f73f4419e481ab68cdd95 # v0.4.1
|
||||
uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2
|
||||
with:
|
||||
advanced-security: false
|
||||
persona: pedantic
|
||||
version: v1.22.0
|
||||
version: v1.23.1
|
||||
|
||||
4
.github/zizmor.yml
vendored
4
.github/zizmor.yml
vendored
@@ -9,6 +9,10 @@ rules:
|
||||
obfuscation:
|
||||
ignore:
|
||||
- release.yml # Not actual obfuscation
|
||||
secrets-outside-env:
|
||||
ignore:
|
||||
- build.yml
|
||||
- release.yml
|
||||
unpinned-uses:
|
||||
config:
|
||||
policies:
|
||||
|
||||
@@ -874,3 +874,6 @@ LordMZTE
|
||||
regulad
|
||||
stastix
|
||||
syphyr
|
||||
FriederHannenheim
|
||||
Peter-Devine
|
||||
SparseOrnament15
|
||||
|
||||
28
Changelog.md
28
Changelog.md
@@ -4,6 +4,34 @@
|
||||
# To create a release, dispatch the https://github.com/yt-dlp/yt-dlp/actions/workflows/release.yml workflow on master
|
||||
-->
|
||||
|
||||
### 2026.03.17
|
||||
|
||||
#### Extractor changes
|
||||
- **youtube**
|
||||
- [Always respect `webpage_client` extractor-arg](https://github.com/yt-dlp/yt-dlp/commit/66c4947e9cb70c9de96f7da75f9acbe4192d6c9d) ([#16250](https://github.com/yt-dlp/yt-dlp/issues/16250)) by [bashonly](https://github.com/bashonly)
|
||||
- [Fix `--live-from-start` support](https://github.com/yt-dlp/yt-dlp/commit/1b6ec8fc2589a1733a0937270faa4230ce6b1ca5) ([#16254](https://github.com/yt-dlp/yt-dlp/issues/16254)) by [bashonly](https://github.com/bashonly)
|
||||
- [Update ejs to 0.8.0](https://github.com/yt-dlp/yt-dlp/commit/04d6974f502bbdfaed72c624344f262e30ad9708) ([#16269](https://github.com/yt-dlp/yt-dlp/issues/16269)) by [bashonly](https://github.com/bashonly), [Grub4K](https://github.com/Grub4K)
|
||||
|
||||
#### Misc. changes
|
||||
- **build**: [Use PyInstaller v6.19.0 for Windows](https://github.com/yt-dlp/yt-dlp/commit/7fab4c2b23e16c4a4f94020a37a6bdf8d502be37) ([#16265](https://github.com/yt-dlp/yt-dlp/issues/16265)) by [bashonly](https://github.com/bashonly)
|
||||
- **ci**: [Bump actions pins](https://github.com/yt-dlp/yt-dlp/commit/4fc768b7f7194a05b13ad3e7bc5bfde84ed9ede7) ([#16252](https://github.com/yt-dlp/yt-dlp/issues/16252)) by [bashonly](https://github.com/bashonly)
|
||||
- **docs**: [Fix `player_client` extractor-arg documentation](https://github.com/yt-dlp/yt-dlp/commit/e68afb28277b4bee39726dbcbb06801edde9f659) ([#16235](https://github.com/yt-dlp/yt-dlp/issues/16235)) by [bashonly](https://github.com/bashonly)
|
||||
- **test**: networking: [Mark all CurlCFFIRH tests as flaky for any OS](https://github.com/yt-dlp/yt-dlp/commit/18656b2f2af41a138793c7012a88f467c0d90274) ([#16266](https://github.com/yt-dlp/yt-dlp/issues/16266)) by [bashonly](https://github.com/bashonly)
|
||||
|
||||
### 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
|
||||
|
||||
6
Makefile
6
Makefile
@@ -202,9 +202,9 @@ CONTRIBUTORS: Changelog.md
|
||||
|
||||
# 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.8.0
|
||||
EJS_WHEEL_NAME = yt_dlp_ejs-0.8.0-py3-none-any.whl
|
||||
EJS_WHEEL_HASH = sha256:79300e5fca7f937a1eeede11f0456862c1b41107ce1d726871e0207424f4bdb4
|
||||
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
|
||||
|
||||
11
README.md
11
README.md
@@ -1860,12 +1860,13 @@ 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`, `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_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_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_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` 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_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_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 `main`, 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`). 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 @@ The following extractors use this feature:
|
||||
* `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)
|
||||
|
||||
@@ -55,7 +55,7 @@ default = [
|
||||
"requests>=2.32.2,<3",
|
||||
"urllib3>=2.0.2,<3",
|
||||
"websockets>=13.0",
|
||||
"yt-dlp-ejs==0.5.0",
|
||||
"yt-dlp-ejs==0.8.0",
|
||||
]
|
||||
curl-cffi = [
|
||||
"curl-cffi>=0.5.10,!=0.6.*,!=0.7.*,!=0.8.*,!=0.9.*,<0.15; implementation_name=='cpython'",
|
||||
|
||||
@@ -53,117 +53,65 @@ class Challenge:
|
||||
|
||||
|
||||
CHALLENGES: list[Challenge] = [
|
||||
Challenge('3d3ba064', Variant.tce, JsChallengeType.N, {
|
||||
'ZdZIqFPQK-Ty8wId': 'qmtUsIz04xxiNW',
|
||||
'4GMrWHyKI5cEvhDO': 'N9gmEX7YhKTSmw',
|
||||
# 20522
|
||||
Challenge('74edf1a3', Variant.main, JsChallengeType.N, {
|
||||
'IlLiA21ny7gqA2m4p37': '9nRTxrbM1f0yHg',
|
||||
'eabGFpsUKuWHXGh6FR4': 'izmYqDEY6kl7Sg',
|
||||
'eabGF/ps%UK=uWHXGh6FR4': 'LACmqlhaBpiPlgE-a',
|
||||
}),
|
||||
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('74edf1a3', Variant.main, JsChallengeType.SIG, {
|
||||
'NJAJEij0EwRgIhAI0KExTgjfPk-MPM9MAdzyyPRt=BM8-XO5tm5hlMCSVpAiEAv7eP3CURqZNSPow8BXXAoazVoXgeMP7gH9BdylHCwgw=gwzz':
|
||||
'AJEij0EwRgIhAI0KExTgjfPk-MPM9MNdzyyPRtzBM8-XO5tm5hlMCSVpAiEAv7eP3CURqZNSPow8BXXAoazVoXgeMP7gH9BdylHCwgw=',
|
||||
'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',
|
||||
}),
|
||||
Challenge('94667337', Variant.tv, JsChallengeType.N, {
|
||||
'BQoJvGBkC2nj1ZZLK-': 'ib1ShEOGoFXIIw',
|
||||
# 20523
|
||||
Challenge('901741ab', Variant.main, JsChallengeType.N, {
|
||||
'BQoJvGBkC2nj1ZZLK-': 'UMPovvBZRh-sjb',
|
||||
}),
|
||||
Challenge('94667337', Variant.tv, JsChallengeType.SIG, {
|
||||
Challenge('901741ab', Variant.main, JsChallengeType.SIG, {
|
||||
'NJAJEij0EwRgIhAI0KExTgjfPk-MPM9MAdzyyPRt=BM8-XO5tm5hlMCSVpAiEAv7eP3CURqZNSPow8BXXAoazVoXgeMP7gH9BdylHCwgw=gwzz':
|
||||
'AJEij0EwRgIhAI0KExTgjfPk-MPM9MNdzyyPRtzBM8-XO5tm5hlMCSVpAiEAv7eP3CURqZNSPow8BXXAoazVoXgeMP7gH9BdylHCwgw=',
|
||||
'wgwCHlydB9Hg7PMegXoVzaoAXXB8woPSNZqRUC3Pe7vAEiApVSCMlhwmt5ON-8MB=5RPyyzdAM9MPM-kPfjgTxEK0IAhIgRwE0jiEJA',
|
||||
}),
|
||||
Challenge('94667337', Variant.es6, JsChallengeType.N, {
|
||||
'BQoJvGBkC2nj1ZZLK-': 'ib1ShEOGoFXIIw',
|
||||
# 20524
|
||||
Challenge('e7573094', Variant.main, JsChallengeType.N, {
|
||||
'IlLiA21ny7gqA2m4p37': '3KuQ3235dojTSjo4',
|
||||
}),
|
||||
Challenge('94667337', Variant.es6, JsChallengeType.SIG, {
|
||||
Challenge('e7573094', Variant.main, JsChallengeType.SIG, {
|
||||
'NJAJEij0EwRgIhAI0KExTgjfPk-MPM9MAdzyyPRt=BM8-XO5tm5hlMCSVpAiEAv7eP3CURqZNSPow8BXXAoazVoXgeMP7gH9BdylHCwgw=gwzz':
|
||||
'AJEij0EwRgIhAI0KExTgjfPk-MPM9MNdzyyPRtzBM8-XO5tm5hlMCSVpAiEAv7eP3CURqZNSPow8BXXAoazVoXgeMP7gH9BdylHCwgw=',
|
||||
'yEij0EwRgIhAI0KExTgjfPk-MPM9MAdzyNPRt=BM8-XO5tm5hlMCSVNAiEAvpeP3CURqZJSPow8BXXAoazVoXgeMP7gH9BdylHCwgw=g',
|
||||
}),
|
||||
Challenge('94667337', Variant.tce, JsChallengeType.N, {
|
||||
'BQoJvGBkC2nj1ZZLK-': 'ib1ShEOGoFXIIw',
|
||||
# 20525
|
||||
Challenge('9fcf08e8', Variant.main, JsChallengeType.N, {
|
||||
'Dyc5ALyWiO0VqwCiT': 'H2PLmmAmJsYjKA',
|
||||
}),
|
||||
Challenge('94667337', Variant.tce, JsChallengeType.SIG, {
|
||||
'NJAJEij0EwRgIhAI0KExTgjfPk-MPM9MAdzyyPRt=BM8-XO5tm5hlMCSVpAiEAv7eP3CURqZNSPow8BXXAoazVoXgeMP7gH9BdylHCwgw=gwzz':
|
||||
'AJEij0EwRgIhAI0KExTgjfPk-MPM9MNdzyyPRtzBM8-XO5tm5hlMCSVpAiEAv7eP3CURqZNSPow8BXXAoazVoXgeMP7gH9BdylHCwgw=',
|
||||
Challenge('9fcf08e8', Variant.main, JsChallengeType.SIG, {
|
||||
'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a':
|
||||
'\x6a\x69\x68\x67\x66\x65\x64\x63\x62\x61\x60\x5f\x5e\x5d\x5c\x5b\x5a\x59\x58\x57\x56\x55\x54\x53\x52\x51\x50\x4f\x4e\x4d\x4c\x4b\x4a\x49\x48\x47\x46\x45\x44\x43\x42\x41\x40\x3f\x3e\x3d\x3c\x3b\x3a\x39\x38\x37\x36\x35\x34\x33\x32\x31\x30\x2f\x2e\x2d\x2c\x2b\x2a\x29\x28\x27\x26\x25\x24\x23\x22\x21\x20\x1f\x1e\x1d\x1c\x1b\x1a\x19\x18\x17\x16\x15\x14\x13\x12\x11\x10\x0f\x0e\x0d\x0c\x0b\x03\x09\x08\x07\x06\x05\x04\x0a',
|
||||
}),
|
||||
Challenge('94667337', Variant.es6_tce, JsChallengeType.N, {
|
||||
'BQoJvGBkC2nj1ZZLK-': 'ib1ShEOGoFXIIw',
|
||||
# 20527
|
||||
Challenge('21cd2156', Variant.main, JsChallengeType.N, {
|
||||
'CiOxDbW1WEE8Ti4w': 'ZcBE4klItiC4rQ',
|
||||
}),
|
||||
Challenge('94667337', Variant.es6_tce, JsChallengeType.SIG, {
|
||||
'NJAJEij0EwRgIhAI0KExTgjfPk-MPM9MAdzyyPRt=BM8-XO5tm5hlMCSVpAiEAv7eP3CURqZNSPow8BXXAoazVoXgeMP7gH9BdylHCwgw=gwzz':
|
||||
'AJEij0EwRgIhAI0KExTgjfPk-MPM9MNdzyyPRtzBM8-XO5tm5hlMCSVpAiEAv7eP3CURqZNSPow8BXXAoazVoXgeMP7gH9BdylHCwgw=',
|
||||
Challenge('21cd2156', Variant.main, JsChallengeType.SIG, {
|
||||
'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a':
|
||||
'\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x00\x46\x47\x48\x49\x4a\x4b\x6a\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x4c',
|
||||
}),
|
||||
# 20528
|
||||
Challenge('5e55da5a', Variant.tv, JsChallengeType.N, {
|
||||
'FgTvzyq4jKv482R7': 'l26nyYSotkzDxg',
|
||||
}),
|
||||
Challenge('5e55da5a', Variant.tv, JsChallengeType.SIG, {
|
||||
'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a':
|
||||
'\x46\x66\x65\x64\x63\x62\x61\x60\x5f\x5e\x67\x6a\x5b\x5a\x59\x58\x57\x56\x55\x54\x53\x52\x51\x50\x4f\x4e\x4d\x4c\x4b\x4a\x49\x48\x47\x2c\x45\x44\x43\x42\x41\x40\x3f\x3e\x3d\x3c\x3b\x3a\x39\x38\x13\x36\x35\x34\x33\x32\x31\x30\x2f\x2e\x2d\x5d\x2b\x2a\x29\x28\x27\x26\x25\x24\x23\x22\x21\x20\x1f\x1e\x1d\x1c\x1b\x1a\x19\x18\x17\x16\x15\x14\x0c\x12\x11\x10\x0f\x0e\x0d\x00\x0b\x0a\x09\x08\x07\x06\x05\x04\x03\x02\x01\x37',
|
||||
}),
|
||||
# 20529
|
||||
Challenge('631d3938', Variant.main, JsChallengeType.N, {
|
||||
'KBx1qz7jMhxELa8c': 'ttPvh7WIptsgSw',
|
||||
}),
|
||||
Challenge('631d3938', Variant.main, JsChallengeType.SIG, {
|
||||
'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66':
|
||||
'\x19\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x00\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63',
|
||||
}),
|
||||
]
|
||||
|
||||
|
||||
@@ -312,7 +312,7 @@ class TestRequestHandlerBase:
|
||||
|
||||
|
||||
@pytest.mark.parametrize('handler', ['Urllib', 'Requests', 'CurlCFFI'], indirect=True)
|
||||
@pytest.mark.handler_flaky('CurlCFFI', os.name == 'nt', reason='segfaults')
|
||||
@pytest.mark.handler_flaky('CurlCFFI', reason='segfaults')
|
||||
class TestHTTPRequestHandler(TestRequestHandlerBase):
|
||||
|
||||
def test_verify_cert(self, handler):
|
||||
@@ -1100,7 +1100,7 @@ class TestRequestsRequestHandler(TestRequestHandlerBase):
|
||||
|
||||
|
||||
@pytest.mark.parametrize('handler', ['CurlCFFI'], indirect=True)
|
||||
@pytest.mark.handler_flaky('CurlCFFI', os.name == 'nt', reason='segfaults')
|
||||
@pytest.mark.handler_flaky('CurlCFFI', reason='segfaults')
|
||||
class TestCurlCFFIRequestHandler(TestRequestHandlerBase):
|
||||
|
||||
@pytest.mark.parametrize('params,extensions', [
|
||||
|
||||
@@ -220,7 +220,7 @@ class TikTokBaseIE(InfoExtractor):
|
||||
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 @@ class TikTokBaseIE(InfoExtractor):
|
||||
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 @@ class TikTokBaseIE(InfoExtractor):
|
||||
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 @@ class TikTokBaseIE(InfoExtractor):
|
||||
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)
|
||||
|
||||
@@ -104,7 +104,6 @@ INNERTUBE_CLIENTS = {
|
||||
},
|
||||
'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_CLIENTS = {
|
||||
},
|
||||
'INNERTUBE_CONTEXT_CLIENT_NAME': 1,
|
||||
'SUPPORTS_COOKIES': True,
|
||||
'SUPPORTS_AD_PLAYBACK_CONTEXT': True,
|
||||
**WEB_PO_TOKEN_POLICIES,
|
||||
},
|
||||
'web_embedded': {
|
||||
@@ -223,16 +221,17 @@ INNERTUBE_CLIENTS = {
|
||||
},
|
||||
'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 short_client_name(client_name):
|
||||
|
||||
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 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
|
||||
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 {}
|
||||
|
||||
@@ -81,7 +81,7 @@ class YoutubeTabBaseInfoExtractor(YoutubeBaseInfoExtractor):
|
||||
'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 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor):
|
||||
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:
|
||||
|
||||
@@ -140,11 +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', '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,13 +1875,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
'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_VARIANT = 'tv'
|
||||
_DEFAULT_PLAYER_JS_VERSION = 'actual'
|
||||
_DEFAULT_PLAYER_JS_VARIANT = 'main'
|
||||
_PLAYER_JS_VARIANT_MAP = {
|
||||
'main': 'player_ias.vflset/en_US/base.js',
|
||||
'tcc': 'player_ias_tcc.vflset/en_US/base.js',
|
||||
@@ -1895,6 +1892,28 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
}
|
||||
_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 _webpage_client(self):
|
||||
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
|
||||
return webpage_client
|
||||
|
||||
@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
|
||||
@@ -1920,13 +1939,13 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
start_time = time.time()
|
||||
formats = [f for f in formats if f.get('is_from_start')]
|
||||
|
||||
def refetch_manifest(format_id, delay):
|
||||
def refetch_manifest(itag, client_name, delay):
|
||||
nonlocal formats, start_time, is_live
|
||||
if time.time() <= start_time + delay:
|
||||
return
|
||||
|
||||
_, _, _, _, prs, player_url = self._initial_extract(
|
||||
url, smuggled_data, webpage_url, 'web', video_id)
|
||||
url, smuggled_data, webpage_url, self._webpage_client, video_id)
|
||||
video_details = traverse_obj(prs, (..., 'videoDetails'), expected_type=dict)
|
||||
microformats = traverse_obj(
|
||||
prs, (..., 'microformat', 'playerMicroformatRenderer'),
|
||||
@@ -1935,20 +1954,20 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
is_live = live_status == 'is_live'
|
||||
start_time = time.time()
|
||||
|
||||
def mpd_feed(format_id, delay):
|
||||
def mpd_feed(itag, client_name, delay):
|
||||
"""
|
||||
@returns (manifest_url, manifest_stream_number, is_live) or None
|
||||
"""
|
||||
for retry in self.RetryManager(fatal=False):
|
||||
with lock:
|
||||
refetch_manifest(format_id, delay)
|
||||
refetch_manifest(itag, client_name, delay)
|
||||
|
||||
f = next((f for f in formats if f['format_id'] == format_id), None)
|
||||
f = next((f for f in formats if f.get('_itag') == itag and f.get('_client') == client_name), None)
|
||||
if not f:
|
||||
if not is_live:
|
||||
retry.error = f'{video_id}: Video is no longer live'
|
||||
else:
|
||||
retry.error = f'Cannot find refreshed manifest for format {format_id}{bug_reports_message()}'
|
||||
retry.error = f'Cannot find refreshed manifest for format {itag}{bug_reports_message()}'
|
||||
continue
|
||||
|
||||
# Formats from ended premieres will be missing a manifest_url
|
||||
@@ -1961,7 +1980,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
|
||||
for f in formats:
|
||||
f['is_live'] = is_live
|
||||
gen = functools.partial(self._live_dash_fragments, video_id, f['format_id'],
|
||||
gen = functools.partial(self._live_dash_fragments, video_id, f['_itag'], f['_client'],
|
||||
live_start_time, mpd_feed, not is_live and f.copy())
|
||||
if is_live:
|
||||
f['fragments'] = gen
|
||||
@@ -1970,7 +1989,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
f['fragments'] = LazyList(gen({}))
|
||||
del f['is_from_start']
|
||||
|
||||
def _live_dash_fragments(self, video_id, format_id, live_start_time, mpd_feed, manifestless_orig_fmt, ctx):
|
||||
def _live_dash_fragments(self, video_id, itag, client_name, live_start_time, mpd_feed, manifestless_orig_fmt, ctx):
|
||||
FETCH_SPAN, MAX_DURATION = 5, 432000
|
||||
|
||||
mpd_url, stream_number, is_live = None, None, True
|
||||
@@ -1994,7 +2013,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
old_mpd_url = mpd_url
|
||||
last_error = ctx.pop('last_error', None)
|
||||
expire_fast = immediate or (last_error and isinstance(last_error, HTTPError) and last_error.status == 403)
|
||||
mpd_url, stream_number, is_live = (mpd_feed(format_id, 5 if expire_fast else 18000)
|
||||
mpd_url, stream_number, is_live = (mpd_feed(itag, client_name, 5 if expire_fast else 18000)
|
||||
or (mpd_url, stream_number, False))
|
||||
if not refresh_sequence:
|
||||
if expire_fast and not is_live:
|
||||
@@ -2020,7 +2039,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
_last_seq = int(re.search(r'(?:/|^)sq/(\d+)', fragments[-1]['path']).group(1))
|
||||
return True, _last_seq
|
||||
|
||||
self.write_debug(f'[{video_id}] Generating fragments for format {format_id}')
|
||||
self.write_debug(f'[{video_id}] Generating fragments for format {itag}')
|
||||
while is_live:
|
||||
fetch_time = time.time()
|
||||
if no_fragment_score > 30:
|
||||
@@ -2082,15 +2101,14 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
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 +2698,14 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
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 +2950,19 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
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,
|
||||
@@ -3715,11 +3747,15 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
sub[STREAMING_DATA_CLIENT_NAME] = client_name
|
||||
subtitles = self._merge_subtitles(subs, subtitles) # Prioritize HLS subs over DASH
|
||||
for f in formats:
|
||||
if process_manifest_format(f, 'dash', client_name, f['format_id'], require_po_token and not po_token):
|
||||
# Save original itag value as format_id because process_manifest_format mutates f
|
||||
format_id = f['format_id']
|
||||
if process_manifest_format(f, 'dash', client_name, format_id, require_po_token and not po_token):
|
||||
f['filesize'] = int_or_none(self._search_regex(
|
||||
r'/clen/(\d+)', f.get('fragment_base_url') or f['url'], 'file size', default=None))
|
||||
if needs_live_processing:
|
||||
f['is_from_start'] = True
|
||||
f['_itag'] = format_id
|
||||
f['_client'] = client_name
|
||||
yield f
|
||||
yield subtitles
|
||||
|
||||
@@ -3880,10 +3916,9 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
|
||||
base_url = self.http_scheme() + '//www.youtube.com/'
|
||||
webpage_url = base_url + 'watch?v=' + video_id
|
||||
webpage_client = 'web'
|
||||
|
||||
webpage, webpage_ytcfg, initial_data, is_premium_subscriber, player_responses, player_url = self._initial_extract(
|
||||
url, smuggled_data, webpage_url, webpage_client, video_id)
|
||||
url, smuggled_data, webpage_url, self._webpage_client, video_id)
|
||||
|
||||
playability_statuses = traverse_obj(
|
||||
player_responses, (..., 'playabilityStatus'), expected_type=dict)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# This file is generated by devscripts/update_ejs.py. DO NOT MODIFY!
|
||||
|
||||
VERSION = '0.5.0'
|
||||
VERSION = '0.8.0'
|
||||
HASHES = {
|
||||
'yt.solver.bun.lib.js': '6ff45e94de9f0ea936a183c48173cfa9ce526ee4b7544cd556428427c1dd53c8073ef0174e79b320252bf0e7c64b0032cc1cf9c4358f3fda59033b7caa01c241',
|
||||
'yt.solver.core.js': '9742868113d7b0c29e24a95c8eb2c2bec7cdf95513dc7f55f523ba053c0ecf2af7dcb0138b1d933578304f0dda633a6b3bfff64e912b4c547b99dad083428c4b',
|
||||
'yt.solver.core.min.js': 'aee8c3354cfd535809c871c2a517d03231f89cd184e903af82ee274bcc2e90991ef19cb3f65f2ccc858c4963856ea87f8692fe16d71209f4fc7f41c44b828e36',
|
||||
'yt.solver.core.js': 'c163a6f376db6ce3da47d516a28a8f2a0554ae95c58dc766f0a6e2b3894f2cef1ee07fa84beb442fa471aac4f300985added1657c7c94c4d1cfefe68920ab599',
|
||||
'yt.solver.core.min.js': 'ee5b307d07f55e91e4723edf5ac205cc877a474187849d757dc1322e38427b157a9d706d510c1723d3670f98e5a3f8cbcde77874a80406bd7204bc9fea30f283',
|
||||
'yt.solver.deno.lib.js': '9c8ee3ab6c23e443a5a951e3ac73c6b8c1c8fb34335e7058a07bf99d349be5573611de00536dcd03ecd3cf34014c4e9b536081de37af3637c5390c6a6fd6a0f0',
|
||||
'yt.solver.lib.js': '1ee3753a8222fc855f5c39db30a9ccbb7967dbe1fb810e86dc9a89aa073a0907f294c720e9b65427d560a35aa1ce6af19ef854d9126a05ca00afe03f72047733',
|
||||
'yt.solver.lib.min.js': '8420c259ad16e99ce004e4651ac1bcabb53b4457bf5668a97a9359be9a998a789fee8ab124ee17f91a2ea8fd84e0f2b2fc8eabcaf0b16a186ba734cf422ad053',
|
||||
|
||||
@@ -39,284 +39,8 @@ var jsc = (function (meriyah, astring) {
|
||||
function isOneOf(value, ...of) {
|
||||
return of.includes(value);
|
||||
}
|
||||
function _optionalChain$2(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 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 = {
|
||||
or: [
|
||||
{
|
||||
type: 'ExpressionStatement',
|
||||
expression: {
|
||||
type: 'AssignmentExpression',
|
||||
operator: '=',
|
||||
left: { or: [{ type: 'Identifier' }, { type: 'MemberExpression' }] },
|
||||
right: { type: 'FunctionExpression' },
|
||||
},
|
||||
},
|
||||
{ type: 'FunctionDeclaration' },
|
||||
{
|
||||
type: 'VariableDeclaration',
|
||||
declarations: {
|
||||
anykey: [
|
||||
{
|
||||
type: 'VariableDeclarator',
|
||||
init: { type: 'FunctionExpression' },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
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 generateArrowFunction(data) {
|
||||
return meriyah.parse(data).body[0].expression;
|
||||
}
|
||||
function _optionalChain$1(ops) {
|
||||
let lastAccessLHS = undefined;
|
||||
@@ -341,156 +65,117 @@ var jsc = (function (meriyah, astring) {
|
||||
}
|
||||
const identifier = {
|
||||
or: [
|
||||
{
|
||||
type: 'ExpressionStatement',
|
||||
expression: {
|
||||
type: 'AssignmentExpression',
|
||||
operator: '=',
|
||||
left: { or: [{ type: 'Identifier' }, { type: 'MemberExpression' }] },
|
||||
right: { type: 'FunctionExpression', async: false },
|
||||
},
|
||||
},
|
||||
{ type: 'FunctionDeclaration', async: false, id: { type: 'Identifier' } },
|
||||
{
|
||||
type: 'VariableDeclaration',
|
||||
kind: 'var',
|
||||
declarations: {
|
||||
anykey: [
|
||||
{
|
||||
type: 'VariableDeclarator',
|
||||
id: { type: 'Identifier' },
|
||||
init: {
|
||||
type: 'ArrayExpression',
|
||||
elements: [{ type: 'Identifier' }],
|
||||
},
|
||||
init: { type: 'FunctionExpression', async: false },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
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);
|
||||
if (
|
||||
_optionalChain$1([tryNode, 'optionalAccess', (_3) => _3.type]) !==
|
||||
'TryStatement' ||
|
||||
_optionalChain$1([
|
||||
tryNode,
|
||||
'access',
|
||||
(_4) => _4.handler,
|
||||
'optionalAccess',
|
||||
(_5) => _5.type,
|
||||
]) !== 'CatchClause'
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
const catchBody = tryNode.handler.body.body;
|
||||
if (matchesStructure(catchBody, catchBlockBody)) {
|
||||
return makeSolverFuncFromName(name);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
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 options = [];
|
||||
if (node.type === 'FunctionDeclaration') {
|
||||
if (
|
||||
node.id &&
|
||||
_optionalChain$1([
|
||||
node,
|
||||
'access',
|
||||
(_) => _.body,
|
||||
'optionalAccess',
|
||||
(_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') {
|
||||
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);
|
||||
if (node.expression.type !== 'AssignmentExpression') {
|
||||
return null;
|
||||
}
|
||||
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 });
|
||||
}
|
||||
} else if (node.type === 'VariableDeclaration') {
|
||||
for (const declaration of node.declarations) {
|
||||
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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
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`,
|
||||
`\nif (typeof globalThis.XMLHttpRequest === "undefined") {\n globalThis.XMLHttpRequest = { prototype: {} };\n}\nif (typeof URL === "undefined") {\n globalThis.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 globalThis.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}\nif (typeof globalThis.window === "undefined") {\n globalThis.window = globalThis;\n}\n`,
|
||||
).body;
|
||||
function _optionalChain(ops) {
|
||||
let lastAccessLHS = undefined;
|
||||
@@ -585,235 +270,59 @@ var jsc = (function (meriyah, astring) {
|
||||
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 makeSolver(result, ident) {
|
||||
return {
|
||||
type: 'ArrowFunctionExpression',
|
||||
params: [ident],
|
||||
body: {
|
||||
type: 'MemberExpression',
|
||||
object: {
|
||||
type: 'CallExpression',
|
||||
callee: result,
|
||||
arguments: [
|
||||
{
|
||||
type: 'ObjectExpression',
|
||||
properties: [
|
||||
{
|
||||
type: 'Property',
|
||||
key: ident,
|
||||
value: ident,
|
||||
kind: 'init',
|
||||
computed: false,
|
||||
method: false,
|
||||
shorthand: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
optional: false,
|
||||
},
|
||||
computed: false,
|
||||
property: ident,
|
||||
optional: false,
|
||||
},
|
||||
async: false,
|
||||
expression: true,
|
||||
generator: false,
|
||||
};
|
||||
}
|
||||
function getFromPrepared(code) {
|
||||
const resultObj = { n: null, sig: null };
|
||||
Function('_result', code)(resultObj);
|
||||
return resultObj;
|
||||
}
|
||||
function multiTry(generators) {
|
||||
return {
|
||||
type: 'ArrowFunctionExpression',
|
||||
params: [{ type: 'Identifier', name: '_input' }],
|
||||
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: '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,
|
||||
},
|
||||
computed: false,
|
||||
property: { type: 'Identifier', name: 'next' },
|
||||
optional: false,
|
||||
},
|
||||
arguments: [],
|
||||
optional: false,
|
||||
},
|
||||
computed: false,
|
||||
property: { type: 'Identifier', name: 'value' },
|
||||
optional: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
async: false,
|
||||
expression: false,
|
||||
generator: false,
|
||||
};
|
||||
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 =
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Autogenerated by devscripts/update-version.py
|
||||
|
||||
__version__ = '2026.03.03'
|
||||
__version__ = '2026.03.17'
|
||||
|
||||
RELEASE_GIT_HEAD = '2ecc4c3bc300701d85e2cbaeb2b28a921a68f0f0'
|
||||
RELEASE_GIT_HEAD = '04d6974f502bbdfaed72c624344f262e30ad9708'
|
||||
|
||||
VARIANT = None
|
||||
|
||||
@@ -12,4 +12,4 @@ CHANNEL = 'stable'
|
||||
|
||||
ORIGIN = 'yt-dlp/yt-dlp'
|
||||
|
||||
_pkg_version = '2026.03.03'
|
||||
_pkg_version = '2026.03.17'
|
||||
|
||||
Reference in New Issue
Block a user