diff --git a/Dockerfile b/Dockerfile index 375b4fd9..f4429b67 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,6 +29,11 @@ COPY requirements.txt . RUN pip install --no-cache-dir --upgrade pip && \ pip install --no-cache-dir -r requirements.txt +# yt-dlp must track YouTube faster than its stable channel ships — stable can +# lag months behind a breaking YouTube change while extraction is broken +# ("Requested format is not available"). Build images with the NIGHTLY channel. +RUN pip install --no-cache-dir -U --pre "yt-dlp[default]" + # Stage 2: Runtime — only runtime dependencies, no build tools FROM python:3.11-slim @@ -44,14 +49,25 @@ ENV PATH="/opt/venv/bin:$PATH" # Set working directory WORKDIR /app -# Install runtime-only system dependencies (no gcc/build tools) +# Install runtime-only system dependencies (no gcc/build tools). +# unzip is needed by the Deno installer below. RUN apt-get update && apt-get install -y --no-install-recommends \ curl \ gosu \ ffmpeg \ libchromaprint-tools \ + unzip \ && rm -rf /var/lib/apt/lists/* +# Deno — JavaScript runtime for yt-dlp. YouTube gates its downloadable formats +# behind JS challenges (nsig); without a JS runtime, yt-dlp's extraction is +# deprecated and streams / music-video downloads fail with "Requested format +# is not available". Deno is yt-dlp's default-enabled runtime; the official +# installer auto-detects amd64/arm64. `deno --version` fails the build early +# if the install ever breaks. +RUN curl -fsSL https://deno.land/install.sh | DENO_INSTALL=/usr/local sh && \ + deno --version + # Create non-root user for security RUN useradd --create-home --shell /bin/bash --uid 1000 soulsync diff --git a/README.md b/README.md index 325ee12b..80af1b57 100644 --- a/README.md +++ b/README.md @@ -317,6 +317,11 @@ cd .. If `webui/static/dist/.vite/manifest.json` is missing or stale, React-owned routes and route handoffs may not load correctly. +**YouTube streaming / music videos** need two extra things on bare-metal installs (Docker bundles both): + +- **Deno** — yt-dlp now requires a JavaScript runtime to unlock YouTube formats. Without it, streams and music-video downloads fail with `Requested format is not available`. Install: `winget install DenoLand.Deno` (Windows) or see [deno.com](https://docs.deno.com/runtime/), then restart SoulSync. +- **yt-dlp nightly** — the stable release can lag months behind YouTube changes. If YouTube breaks, update with: `python -m pip install -U --pre "yt-dlp[default]"` + ### Local Development This is only for contributors working on the WebUI with hot reload. Normal Python/no-Docker installs should build once with `npm run build` as shown above, then run only Gunicorn. @@ -355,6 +360,7 @@ on any OS. `./dev.sh` remains available as a Unix shell wrapper. - **slskd** running and accessible ([Download](https://github.com/slskd/slskd/releases)) — required for Soulseek downloads - **Spotify API** credentials ([Dashboard](https://developer.spotify.com/dashboard)) — optional but recommended for discovery - **Media Server** (optional): Plex, Jellyfin, or Navidrome +- **Deno** (Python/no-Docker installs only): JavaScript runtime required by yt-dlp for YouTube streaming/music videos — `winget install DenoLand.Deno` or [deno.com](https://docs.deno.com/runtime/). Docker images bundle it. - **Deezer ARL token** (optional): For Deezer downloads — get from browser cookies after logging into deezer.com - **Tidal account** (optional): For Tidal downloads — authenticate via device flow in Settings - **Qobuz account** (optional): For Qobuz downloads — email/password login in Settings diff --git a/core/youtube_client.py b/core/youtube_client.py index bd72e674..a1cef67b 100644 --- a/core/youtube_client.py +++ b/core/youtube_client.py @@ -93,6 +93,29 @@ class YouTubeSearchResult: from core.download_plugins.base import DownloadSourcePlugin +_JS_RUNTIME_WARNED = False + + +def _warn_if_no_js_runtime(): + """One-time startup check: YouTube gates downloadable formats behind JS + challenges (nsig), and yt-dlp needs a JavaScript runtime (Deno, its only + default-enabled one) to solve them. Without it every stream / music-video + download dies with the cryptic "Requested format is not available" — say + so plainly in the log instead. Docker images bundle Deno; bare-metal + installs need it on PATH.""" + global _JS_RUNTIME_WARNED + if _JS_RUNTIME_WARNED: + return + _JS_RUNTIME_WARNED = True + import shutil as _sh + if not _sh.which('deno'): + logger.warning( + "No JavaScript runtime found (deno not on PATH). YouTube streaming and " + "music-video downloads will fail with 'Requested format is not available'. " + "Install Deno (https://docs.deno.com/runtime/) and restart SoulSync. " + "Windows: winget install DenoLand.Deno" + ) + class YouTubeClient(DownloadSourcePlugin): """ @@ -110,6 +133,7 @@ class YouTubeClient(DownloadSourcePlugin): self.download_path.mkdir(parents=True, exist_ok=True) logger.info(f"YouTube client using download path: {self.download_path}") + _warn_if_no_js_runtime() # Callback for shutdown check (avoids circular imports) self.shutdown_check = None diff --git a/requirements.txt b/requirements.txt index 148d3c0c..b7fe18a6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -46,7 +46,14 @@ tzdata>=2024.1 # keeps existing schedules running at the same wall-clock time. tzlocal>=5.0 -# YouTube support -- unpinned; yt-dlp must track upstream releases to stay functional +# YouTube support -- unpinned; yt-dlp must track upstream releases to stay functional. +# NOTE: pip resolves this to the latest STABLE release, which can lag months +# behind a breaking YouTube change. If streams/music-video downloads fail with +# "Requested format is not available", switch to the nightly channel: +# pip install -U --pre "yt-dlp[default]" +# YouTube extraction ALSO requires a JavaScript runtime (Deno) on the system -- +# it is NOT a Python package and cannot be listed here. Docker images bundle +# it; bare-metal installs need it on PATH: https://docs.deno.com/runtime/ yt-dlp>=2026.3.17 # Lyrics support