Repository navigation
fix: bundle yt-dlp's vendored JS solver script, which v1.6.1 dropped - #33
Merged
Merged
Conversation
v1.6.1 did not work. Reported from the church PC: the download succeeded but
came down at 360p with 1080p selected.
Evidence from that machine — the log line added in v1.6.1 is what found it:
JS engine: D:\...\quickjs_win64\qjs.exe
Downloading: ...watch?v=Mg1Aa34cHjI with quality 1080p
Download failed: ERROR: [youtube] Mg1Aa34cHjI: This video is not available
Retrying with player clients: default,android,ios,web_safari,tv
Succeeded on retry ...
The engine was bundled, resolved and handed to yt-dlp, `qjs.exe --help` runs
fine on that box (QuickJS-ng 0.16.2), and the file was 3,425,955 bytes — the
360p size to the byte. So the retry rescued it while the engine did nothing.
An engine is only half of what the challenge takes. yt-dlp runs a *vendored
JavaScript file*, yt.solver.core.js, through it — and those three .js files are
the only non-Python assets in the entire yt_dlp package, so a plain PyInstaller
freeze drops them. `vendor.load_script()` reports that by returning None
**silently**, so the failure is indistinguishable from having no engine at all.
Not a missing-module problem: parsing the shipped exe's PYZ shows all 12
`youtube.jsc` provider modules present. It is purely the data files, and no
yt_dlp data file appears anywhere in the shipped zip.
Proved causally both ways:
- From source, hiding only those three .js files reproduces the church PC's
error verbatim; restoring them gives 1920x1080.
- Frozen probes built both ways: without collect_data_files the solver is
MISSING and the download FAILS; with it the script loads (12,607 bytes) and
the same video resolves 399+251 1920x1080. That also confirms
importlib.resources.files() resolves collected data inside a frozen app.
Why v1.6.1 passed everything: from a source checkout those .js files are always
there, so the bug is invisible to every source-level test. Verifying in the
shipped artifact is the lesson, and CLAUDE.md now says so.
Also logs `solver script: ok|MISSING` beside the engine path, so the half that
was silently absent is now visible in the log rather than only in a resolution
nobody measures.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ThorSPB
force-pushed
the
fix/bundle-yt-dlp-solver-script
branch
from
September 8, 2026 15:38
9a629df to
ebd847a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
v1.6.1 did not work
Reported from the church PC: the download succeeded, but came down at 360p
with 1080p selected. He was right — the file is
3,425,955bytes, the 360p sizeto the byte. The 1080p version is 7.27 MB.
The log line added in v1.6.1 is what found it:
So the engine was bundled, resolved and handed to yt-dlp —
js_runtime_pathisset in settings,
qjs.exe --helpprintsQuickJS-ng version 0.16.2on thatmachine — and the challenge still went unsolved. The player-client retry then
rescued the download at 360p, which is exactly the safety net working and
masking the real failure.
Cause
An engine is only half of what the challenge takes. yt-dlp runs a vendored
JavaScript file —
yt.solver.core.js— through the engine. Those three.jsfiles are the only non-Python assets in the entire
yt_dlppackage, so aplain PyInstaller freeze drops them, and
vendor.load_script()reports that byreturning
Nonesilently. The result is indistinguishable from having noengine at all: the same misleading "This video is not available".
Not a missing-module problem — I checked that first and was wrong. Parsing the
shipped exe's PYZ shows all 12
youtube.jscprovider modules present,quickjsincluded. It is purely the data files, and noyt_dlpdata fileappears anywhere in the shipped zip.
Proved causally, both directions
.jsfiles reproduces the churchPC's error verbatim; restoring them gives
1920x1080.collect_data_files("yt_dlp")thesolver is
MISSINGand the downloadFAILs; with it the script loads(12,607 bytes) and the same video resolves
399+251 1920x1080. That alsoconfirms
importlib.resources.files()resolves collected data inside a frozenapp, which was the risky part of the fix.
The fix
One line in
youtubeweekly.spec:datas=[...] + collect_data_files("yt_dlp").Plus
download_videonow logssolver script: ok|MISSINGbeside the enginepath, so the half that was silently absent is visible in the log instead of only
in a resolution nobody measures.
Why v1.6.1 passed everything
From a source checkout those
.jsfiles are always present, so the bug isinvisible to every source-level test — and my verification ran from source.
Verifying in the shipped artifact is the lesson; CLAUDE.md now carries it as an
explicit warning next to the engine docs.
306 passed. Both new tests were mutation-checked: removingcollect_data_filesfrom the spec fails one, and renaming the solver script fails the other.
Notes
Again, in Full Quality" bullet promises exactly this, and it will now be true.
changelog.pyexcludes non-version headings, sonotes sitting under
## Unreleasednever reach users. v1.6.1 shipped withboth bullets still under
## Unreleased, so it showed no release notes atall. Not touched here — stamping the version looks like a deliberate manual
step in your release commits.
🤖 Generated with Claude Code