feat(downloads): automatic PO-token provider (bgutil sidecar) to beat YouTube bot-detection
YouTube increasingly demands a Proof-of-Origin token (or shows 'confirm you're not a bot'). Instead of manual cookies, run the bgutil POT provider as a sidecar that mints tokens on demand: - compose: bgutil-pot service (brainicism/bgutil-ytdlp-pot-provider:1.3.1, port 4416, init) - requirements: bgutil-ytdlp-pot-provider==1.3.1 plugin (version pinned to the image) - config: DOWNLOAD_POT_BASE_URL (http://bgutil-pot:4416) + DOWNLOAD_PLAYER_CLIENTS (web_safari,web,tv — POT-capable; the default android_vr client never requests a token) - formats.build_ydl_opts wires extractor_args youtube:player_client + youtubepot-bgutilhttp:base_url Verified: the sidecar mints a valid PO token (BotGuard challenge solved) and yt-dlp fetches it from the provider. NOTE: end-to-end download couldn't be confirmed here because the test IP got hard-flagged by YouTube from heavy testing (~16 GB + dozens of extractions) — a hard abuse block needs cooldown/cookies, which POT doesn't lift; POT bypasses the normal soft bot-check.
This commit is contained in:
parent
d7fe3c34e8
commit
03a1865aa8
5 changed files with 58 additions and 4 deletions
|
|
@ -138,6 +138,18 @@ class Settings(BaseSettings):
|
|||
# Whether THIS process runs the download worker loop. On for the dedicated worker
|
||||
# container, off for the API (which keeps the scheduler). Mirrors scheduler_enabled.
|
||||
worker_enabled: bool = False
|
||||
# Base URL of the bgutil PO-token provider sidecar. When set, the download worker asks it
|
||||
# for YouTube Proof-of-Origin tokens (bypasses bot-detection + unlocks high-quality
|
||||
# formats). Empty disables it (yt-dlp still works, but may hit "confirm you're not a bot").
|
||||
download_pot_base_url: str = "http://bgutil-pot:4416"
|
||||
# yt-dlp player clients to use (comma-separated). POT-capable clients (web_safari/web) must
|
||||
# be used for the PO token to actually apply; the default clients (android_vr) don't request
|
||||
# one. Empty = yt-dlp's own default. Admin-tunable when YouTube shifts what works.
|
||||
download_player_clients: str = "web_safari,web,tv"
|
||||
|
||||
@property
|
||||
def player_client_list(self) -> list[str]:
|
||||
return [c.strip() for c in self.download_player_clients.split(",") if c.strip()]
|
||||
# How many downloads the worker runs concurrently (claimed via FOR UPDATE SKIP LOCKED).
|
||||
download_worker_concurrency: int = 2
|
||||
# How often (empty pending queue) the worker polls for a new job, in seconds.
|
||||
|
|
|
|||
|
|
@ -81,8 +81,17 @@ def target_ext(spec: dict) -> str:
|
|||
return s["container"] or "mp4"
|
||||
|
||||
|
||||
def build_ydl_opts(spec: dict, outtmpl: str, progress_hook) -> dict:
|
||||
"""Assemble yt-dlp options for a single download to `outtmpl` (a full path template)."""
|
||||
def build_ydl_opts(
|
||||
spec: dict,
|
||||
outtmpl: str,
|
||||
progress_hook,
|
||||
pot_base_url: str | None = None,
|
||||
player_clients: list[str] | None = None,
|
||||
) -> dict:
|
||||
"""Assemble yt-dlp options for a single download to `outtmpl` (a full path template).
|
||||
|
||||
`pot_base_url` points the bgutil PO-token provider plugin at the sidecar server;
|
||||
`player_clients` picks which YouTube clients to use (POT-capable ones like web use the token)."""
|
||||
s = normalize(spec)
|
||||
h = s["max_height"]
|
||||
opts: dict = {
|
||||
|
|
@ -107,6 +116,17 @@ def build_ydl_opts(spec: dict, outtmpl: str, progress_hook) -> dict:
|
|||
"socket_timeout": 30,
|
||||
}
|
||||
|
||||
# Extractor args: which YouTube clients to use + where the bgutil PO-token sidecar lives
|
||||
# (== --extractor-args "youtube:player_client=…;youtubepot-bgutilhttp:base_url=…"). Values
|
||||
# are lists in the Python API.
|
||||
ea: dict = {}
|
||||
if player_clients:
|
||||
ea["youtube"] = {"player_client": player_clients}
|
||||
if pot_base_url:
|
||||
ea["youtubepot-bgutilhttp"] = {"base_url": [pot_base_url]}
|
||||
if ea:
|
||||
opts["extractor_args"] = ea
|
||||
|
||||
if s["mode"] == "a":
|
||||
opts["format"] = "bestaudio/best"
|
||||
opts["postprocessors"].append(
|
||||
|
|
|
|||
|
|
@ -293,7 +293,13 @@ def _download(job_id: int, asset_id: int, source_kind: str, source_ref: str, spe
|
|||
staging.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
outtmpl = str(staging / "%(id)s.%(ext)s")
|
||||
opts = formats.build_ydl_opts(spec, outtmpl, _make_progress_hook(job_id, asset_id))
|
||||
opts = formats.build_ydl_opts(
|
||||
spec,
|
||||
outtmpl,
|
||||
_make_progress_hook(job_id, asset_id),
|
||||
settings.download_pot_base_url,
|
||||
settings.player_client_list,
|
||||
)
|
||||
opts["postprocessor_hooks"] = [_make_pp_hook(job_id)]
|
||||
url = service.source_url(source_kind, source_ref)
|
||||
|
||||
|
|
|
|||
|
|
@ -16,4 +16,8 @@ argon2-cffi>=23.1,<26
|
|||
# Download center: yt-dlp does the extraction/download; ffmpeg (installed at the OS level in
|
||||
# the Dockerfile) does the merge + postprocessing. yt-dlp is release-often (YouTube changes),
|
||||
# so keep the floor loose and bump it when extraction breaks.
|
||||
yt-dlp>=2025.1
|
||||
yt-dlp>=2025.5.22
|
||||
# PO Token provider plugin: talks to the bgutil sidecar (brainicism/bgutil-ytdlp-pot-provider)
|
||||
# to mint YouTube Proof-of-Origin tokens on demand, bypassing "confirm you're not a bot" and
|
||||
# unlocking high-quality formats. Pin to match the sidecar image tag (versions must align).
|
||||
bgutil-ytdlp-pot-provider==1.3.1
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue