Add YouTube POT token provider (bgutil-pot, opt-in)

YouTube increasingly requires a per-video Proof-of-Origin token bound
to each video ID before handing back format URLs. Without one, yt-dlp
sees empty formats and downloads fail. The upstream solution is the
Brainicism bgutil-ytdlp-pot-provider Python plugin paired with a
long-running server that mints tokens via BotGuard.

We integrate the Rust port (jim60105/bgutil-ytdlp-pot-provider-rs) so
the server is a single static binary, no Node.js required.

## Architecture

- bgutil-pot binary lives alongside the bundled deno + yt-dlp at
  ~/.local/share/yt-offline/bin/bgutil-pot.
- The matching Python plugin is pip-installed into the bundled venv:
  python -m pip install bgutil-ytdlp-pot-provider.
- Downloader spawns the server lazily on first job (port 4416,
  loopback only) and kills it on Drop.
- Each yt-dlp invocation gets
  --extractor-args "youtubepot-bgutilhttp:base_url=http://127.0.0.1:4416"
  appended in spawn_job when use_pot_provider is on and the server
  child is alive.

## UX

- Settings → "POT token provider" row with toggle + Install/Update
  button (mirrors the bundled yt-dlp row).
- Disabled unless use_bundled_ytdlp is also on (plugin lives in that
  venv).
- Refuses install if the bundled yt-dlp venv isn't there yet, with a
  helpful 428 error.

## Lifecycle

- Lazy spawn: ensure_pot_server() runs at the top of start() so the
  server is up before yt-dlp queries the plugin.
- Drop: kill_server() on Downloader drop so we don't leave an orphaned
  process bound to 4416.
- start_pot_provider_update() kills the running child first so the
  install can overwrite the binary in place (no ETXTBSY).

3 new unit tests cover URL/extractor-args formatting; 77 total pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Luna 2026-05-31 06:08:25 -07:00
parent 4f15ab873f
commit 2696a51cc8
7 changed files with 439 additions and 4 deletions

View file

@ -35,6 +35,17 @@ pub struct BackupSection {
/// `yt-dlp` found on the system PATH.
#[serde(default)]
pub use_bundled_ytdlp: bool,
/// If true and the bundled yt-dlp is in use, spawn the bgutil-pot
/// HTTP server on startup and pass its extractor-args to every
/// yt-dlp invocation. YouTube increasingly requires a per-video
/// Proof-of-Origin token; without one, format URLs come back empty.
///
/// Only effective in tandem with `use_bundled_ytdlp` because the
/// matching Python plugin gets pip-installed into the bundled venv,
/// not the system Python. System-yt-dlp users who want POT support
/// install the plugin and run the server themselves.
#[serde(default)]
pub use_pot_provider: bool,
}
fn default_max_concurrent() -> usize { 3 }
@ -160,6 +171,7 @@ impl Config {
directory: dir,
max_concurrent: default_max_concurrent(),
use_bundled_ytdlp: false,
use_pot_provider: false,
},
player: PlayerSection::default(),
ui: UiSection::default(),