3.9 KiB
Private yt-dlp build
MeTube uses yt-dlp as its core downloader,
pinned to the official PyPI release via pyproject.toml / uv.lock.
There is currently an extractor bug in the official release that is not yet
fixed upstream. Until it is, the Docker image bakes a wheel built from a local,
patched yt-dlp checkout (~/Dev/github/yt-dlp) instead of the pinned release.
This document explains how the override works, how to build/update the private wheel, and — most importantly — how to switch back to the official build once the upstream fix lands.
How the override works
-
./build-ytdlp.shbuilds a wheel from a local yt-dlp checkout and stages it asvendor/yt_dlp-*.whl(the versioned filename is required byuv). -
The
Dockerfile, right afteruv sync, force-reinstalls that wheel over the version pinned inuv.lock:COPY vendor ./vendor RUN if [ -n "$(ls /app/vendor/yt_dlp-*.whl 2>/dev/null || true)" ]; then \ uv pip install --python /usr/local/bin/python --force-reinstall --no-deps /app/vendor/yt_dlp-*.whl; \ else \ echo "No private yt-dlp wheel in vendor/ - using pinned yt-dlp from uv.lock"; \ fi -
publish.shruns./build-ytdlp.shbefore every build, so published images always carry the patched build (and the publish aborts loudly if the local checkout is missing). -
The wheel is git-ignored (
/vendor/yt_dlp-*.whl).vendor/.gitkeepandvendor/.gitignoreare committed so the directory always exists forCOPY vendor ./vendor.
Building / updating the private wheel
./build-ytdlp.sh # builds from ~/Dev/github/yt-dlp
YTDLP_REPO=/path/to/yt-dlp ./build-ytdlp.sh # custom checkout path
Rebuild whenever the local checkout changes, then rebuild the image:
docker compose up -d --build --force-recreate
The script fails with a clear error if the configured checkout is missing.
Switching back to the official build (when upstream fixes it)
1. Remove the Dockerfile override
Delete the COPY vendor / RUN uv pip install block from Dockerfile
(added in the "Private yt-dlp build" section, right after uv sync).
2. Remove the build step from publish.sh
Delete the block:
# Bake the private yt-dlp build (see build-ytdlp.sh) into the image until the
# upstream fix lands. Fails loudly if the local checkout is missing.
./build-ytdlp.sh
3. Update the pinned version
Bump yt-dlp to the fixed release and re-resolve the lockfile:
uv add "yt-dlp[default,curl-cffi]@latest" # or pin the exact fixed version
uv lock
Commit the updated pyproject.toml and uv.lock.
4. Clean up the override artifacts
rm build-ytdlp.sh # no longer needed
rm -f vendor/yt_dlp-*.whl # delete any staged wheel
You may also remove the vendor/ directory (.gitkeep, .gitignore) and the
/vendor/yt_dlp-*.whl entry from .gitignore.
5. Update the docs
Remove the "Private yt-dlp build" section from DEPLOY.md (and delete this file
if you no longer want the guide around).
6. Rebuild and verify
docker compose up -d --build --force-recreate
Confirm the installed version is the official release:
docker compose exec metube python3 -c "import yt_dlp; print(yt_dlp.version.__version__)"
7. Publish
When you next publish, ./publish.sh will no longer stage a private wheel and
the image will use the official yt-dlp release.
Verifying which build is running
The /version endpoint reports METUBE_VERSION; the yt-dlp version is logged
by the app at startup. To check inside a running container:
docker compose exec metube python3 -c \
"import yt_dlp, yt_dlp.extractor.spankbang as s; import inspect; print(yt_dlp.version.__version__, 'patched:', 'spankbang' in inspect.getsource(s).lower())"