# Private yt-dlp build MeTube uses [yt-dlp](https://github.com/yt-dlp/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 1. `./build-ytdlp.sh` builds a wheel from a local yt-dlp checkout and stages it as `vendor/yt_dlp-*.whl` (the versioned filename is required by `uv`). 2. The `Dockerfile`, right after `uv sync`, force-reinstalls that wheel over the version pinned in `uv.lock`: ```dockerfile 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 ``` 3. `publish.sh` runs `./build-ytdlp.sh` before every build, so published images always carry the patched build (and the publish aborts loudly if the local checkout is missing). 4. The wheel is **git-ignored** (`/vendor/yt_dlp-*.whl`). `vendor/.gitkeep` and `vendor/.gitignore` are committed so the directory always exists for `COPY vendor ./vendor`. ## Building / updating the private wheel ```sh ./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: ```sh 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: ```sh # 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: ```sh 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 ```sh 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 ```sh docker compose up -d --build --force-recreate ``` Confirm the installed version is the official release: ```sh 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: ```sh 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())" ```