# YouTube provider (ytdy) ## Context and problem statement A new library provider for YouTube, mounted at `/youtube`: - **Search** works without any login — creatable search-term nodes like `/tidal/search` (`%` creates a term, results list as tracks). - **Login is optional.** When configured, the user's **playlists** appear as an extra subtree; without it, the provider still works (search only). - **Captures** (`W`, download) must work on YouTube nodes — search results and playlists are downloadable. ## Assumptions (confirmed) - The captures machinery is provider-agnostic: anything that answers `get_lib_node`/`get_urls_for_track` and raises `is_downloadable` gets `w`/`W` for free — no new wire or TUI work at all. - The TUI's creatable/editable/deletable node flows (`%`/`e`/`d`) are generic; mirroring tidal's search-term semantics costs no TUI change. - `pkgs.yt-dlp` (2026.06.09) exists in nixpkgs; the devenv already pins the toolchain, so the engine binary is declared, not assumed. - The audio player streams plain https URLs; googlevideo stream URLs (from `bestaudio`) are plain https and also downloadable with the captures reqwest client. ## Decisions ### D1 — Extraction engine: `yt-dlp` subprocess Options considered: - *(a)* **Pure-Rust extractor crates** (`rustypipe`, `rusty_ytdl`): no external binary, but they chase YouTube's extraction changes with small maintainer teams, and logged-in user playlists are weakly or not supported. - *(b)* **Invidious/Piped instances**: no extraction code at all, but a hard runtime dependency on third-party servers of unpredictable availability — worse than a local binary for a self-hosted player. - *(c)* **`yt-dlp` as a subprocess** with `-J` JSON output: the de-facto standard extractor, fastest to track YouTube changes, supports search (`ytsearchN:`), playlists, cookies-based login, and direct stream URLs. Cost: a non-Rust runtime dependency and subprocess plumbing. **Decision: (c).** The binary is declared in `devenv.nix` (dev) and is a documented runtime requirement (deploys). All calls go through one `Engine` seam (`tokio::process::Command`, `kill_on_drop`, per-call timeout, bounded stdout, `serde_json` parsing) so tests fake the binary with a script and a future pure-Rust engine stays swappable. ### D2 — Crate `ytdy`, mounted at `/youtube`, non-fatal init New workspace crate `ytdy` implementing `ProviderClient`, following `tidaldy`'s shape. `init` probes ` --version` (with timeout); a missing or broken binary disables the provider with a warning — the server and every other provider keep running. Settings (`ytdy.toml`): ```toml binary = "yt-dlp" # optional; PATH lookup by default cookies = "/path/cookies.txt" # optional; presence = "logged in" search_results = 20 # ytsearchN cap ``` The orchestrator gains `youtube_client: Option>` and `/youtube` routing arms (same completeness as the other providers); `get_lib_root` lists `youtube` only when the probe succeeded. ### D3 — Tree shape - `/youtube` — children: `search` (always, `is_creatable`), `playlists` (only when cookies are configured). - `/youtube/search/` — created via `%` like tidal search; terms live in memory (`RwLock>`, dedup, recreated implicitly on stale paths), renamable and deletable. The node lists the top `search_results` results as **tracks** (`ytsearchN:`, `--flat-playlist`); it is queueable (results are homogeneous tracks, unlike tidal's mixed search). - `/youtube/playlists` — the user's playlists as children (`https://www.youtube.com/feed/playlists` with cookies, flat). - `/youtube/playlists/` — playlist entries as tracks. - Track paths: `/`; metadata from the flat entries (title, uploader as artist, duration). `get_metadata_for_track` runs a single-video `-J` when called directly. ### D4 — Streams, downloads, login - `get_urls_for_track`: `yt-dlp -f bestaudio/best -g --no-playlist