Pin all yt-dlp calls to tv_embedded client to stop IP rate limiting
Without a player_client, yt-dlp probes web+mweb+android+tv in sequence for every request, multiplying API calls ~4x and triggering YouTube IP blocks. tv_embedded exposes the full format list in one shot and is the least restricted client for authenticated sessions. Applies to: search, trending, video/channel metadata, playlists, subs, comments, and downloads — every yt-dlp invocation site. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -55,6 +55,12 @@ _meta_lock = threading.Lock()
|
|||||||
_meta_last_call: float = 0.0
|
_meta_last_call: float = 0.0
|
||||||
_META_MIN_GAP = 12.0 # seconds between any two metadata requests
|
_META_MIN_GAP = 12.0 # seconds between any two metadata requests
|
||||||
|
|
||||||
|
# Pin to tv_embedded client — it exposes full format list without SABR
|
||||||
|
# restrictions and makes exactly ONE set of API requests per operation.
|
||||||
|
# Without this yt-dlp probes web + mweb + android + tv in sequence,
|
||||||
|
# multiplying requests and triggering YouTube IP rate limits.
|
||||||
|
_YT_CLIENT = ["--extractor-args", "youtube:player_client=tv_embedded"]
|
||||||
|
|
||||||
# Active download counter — _meta_run pauses while any download is running so
|
# Active download counter — _meta_run pauses while any download is running so
|
||||||
# background discovery and a concurrent download never share the YouTube session
|
# background discovery and a concurrent download never share the YouTube session
|
||||||
# at the same time. Downloads set/clear this; _meta_run reads it.
|
# at the same time. Downloads set/clear this; _meta_run reads it.
|
||||||
@@ -211,6 +217,7 @@ def search_youtube(query: str, max_results: int = 40, polite: bool = False) -> l
|
|||||||
"--dump-json",
|
"--dump-json",
|
||||||
"--flat-playlist",
|
"--flat-playlist",
|
||||||
"--quiet",
|
"--quiet",
|
||||||
|
*_YT_CLIENT,
|
||||||
*_cookie_args(),
|
*_cookie_args(),
|
||||||
], timeout=60)
|
], timeout=60)
|
||||||
|
|
||||||
@@ -259,6 +266,7 @@ def fetch_trending(region: str = "US", max_results: int = 50) -> list[dict]:
|
|||||||
"--flat-playlist",
|
"--flat-playlist",
|
||||||
"--quiet",
|
"--quiet",
|
||||||
"--playlist-end", str(max_results),
|
"--playlist-end", str(max_results),
|
||||||
|
*_YT_CLIENT,
|
||||||
*_cookie_args(),
|
*_cookie_args(),
|
||||||
], timeout=60)
|
], timeout=60)
|
||||||
|
|
||||||
@@ -309,6 +317,7 @@ def fetch_video_metadata(video_id: str, polite: bool = False) -> dict | None:
|
|||||||
base_cmd = [
|
base_cmd = [
|
||||||
"yt-dlp", url,
|
"yt-dlp", url,
|
||||||
"--dump-json", "--no-download", "--no-playlist",
|
"--dump-json", "--no-download", "--no-playlist",
|
||||||
|
*_YT_CLIENT,
|
||||||
]
|
]
|
||||||
runner = _meta_run if polite else _run
|
runner = _meta_run if polite else _run
|
||||||
stdout, stderr, code = runner([*base_cmd, *cookie_args], timeout=30)
|
stdout, stderr, code = runner([*base_cmd, *cookie_args], timeout=30)
|
||||||
@@ -379,6 +388,7 @@ def fetch_channel_metadata(channel_id: str, max_videos: int = 30, start_video: i
|
|||||||
"--dump-single-json",
|
"--dump-single-json",
|
||||||
"--flat-playlist",
|
"--flat-playlist",
|
||||||
"--quiet",
|
"--quiet",
|
||||||
|
*_YT_CLIENT,
|
||||||
*_cookie_args(),
|
*_cookie_args(),
|
||||||
]
|
]
|
||||||
if start_video > 1:
|
if start_video > 1:
|
||||||
@@ -442,6 +452,7 @@ def fetch_channel_playlists(channel_id: str, max_results: int = 100) -> list[dic
|
|||||||
"--dump-json", "--flat-playlist",
|
"--dump-json", "--flat-playlist",
|
||||||
"--playlist-end", str(max_results),
|
"--playlist-end", str(max_results),
|
||||||
"--quiet",
|
"--quiet",
|
||||||
|
*_YT_CLIENT,
|
||||||
*_cookie_args(),
|
*_cookie_args(),
|
||||||
], timeout=60)
|
], timeout=60)
|
||||||
|
|
||||||
@@ -484,6 +495,7 @@ def fetch_playlist_videos(playlist_id: str, max_videos: int = 200) -> list[dict]
|
|||||||
"yt-dlp", url,
|
"yt-dlp", url,
|
||||||
"--dump-json", "--flat-playlist",
|
"--dump-json", "--flat-playlist",
|
||||||
"--quiet",
|
"--quiet",
|
||||||
|
*_YT_CLIENT,
|
||||||
*_cookie_args(),
|
*_cookie_args(),
|
||||||
]
|
]
|
||||||
if max_videos > 0:
|
if max_videos > 0:
|
||||||
@@ -532,6 +544,7 @@ def fetch_featured_channels(channel_id: str) -> list[str]:
|
|||||||
"--dump-json",
|
"--dump-json",
|
||||||
"--flat-playlist",
|
"--flat-playlist",
|
||||||
"--quiet",
|
"--quiet",
|
||||||
|
*_YT_CLIENT,
|
||||||
*_cookie_args(),
|
*_cookie_args(),
|
||||||
], timeout=30)
|
], timeout=30)
|
||||||
|
|
||||||
@@ -564,6 +577,7 @@ def fetch_channel_links(channel_id: str) -> list[str]:
|
|||||||
"--flat-playlist",
|
"--flat-playlist",
|
||||||
"--playlist-end", "1",
|
"--playlist-end", "1",
|
||||||
"--quiet",
|
"--quiet",
|
||||||
|
*_YT_CLIENT,
|
||||||
*_cookie_args(),
|
*_cookie_args(),
|
||||||
], timeout=30)
|
], timeout=30)
|
||||||
|
|
||||||
@@ -614,6 +628,7 @@ def download_subs_only(video_id: str, subtitle_langs: str) -> bool:
|
|||||||
"--sub-langs", subtitle_langs,
|
"--sub-langs", subtitle_langs,
|
||||||
"--convert-subs", "vtt",
|
"--convert-subs", "vtt",
|
||||||
"-o", output_template,
|
"-o", output_template,
|
||||||
|
*_YT_CLIENT,
|
||||||
*_cookie_args(),
|
*_cookie_args(),
|
||||||
], timeout=60)
|
], timeout=60)
|
||||||
if code == 0:
|
if code == 0:
|
||||||
@@ -628,7 +643,7 @@ def fetch_available_subs(video_id: str) -> dict:
|
|||||||
BCP-47 lang codes. Manual = human-made; auto = auto-generated captions.
|
BCP-47 lang codes. Manual = human-made; auto = auto-generated captions.
|
||||||
"""
|
"""
|
||||||
url = f"https://www.youtube.com/watch?v={video_id}"
|
url = f"https://www.youtube.com/watch?v={video_id}"
|
||||||
base_cmd = ["yt-dlp", url, "--dump-json", "--no-download", "--no-playlist"]
|
base_cmd = ["yt-dlp", url, "--dump-json", "--no-download", "--no-playlist", *_YT_CLIENT]
|
||||||
cookie_args = _cookie_args()
|
cookie_args = _cookie_args()
|
||||||
stdout, _, code = _meta_run([*base_cmd, *cookie_args], timeout=30)
|
stdout, _, code = _meta_run([*base_cmd, *cookie_args], timeout=30)
|
||||||
if code != 0 and cookie_args:
|
if code != 0 and cookie_args:
|
||||||
@@ -674,6 +689,7 @@ def fetch_video_comments(youtube_video_id: str, max_comments: int = 20) -> list[
|
|||||||
"--skip-download",
|
"--skip-download",
|
||||||
"--no-playlist",
|
"--no-playlist",
|
||||||
"--output", out_tmpl,
|
"--output", out_tmpl,
|
||||||
|
*_YT_CLIENT,
|
||||||
*_cookie_args(),
|
*_cookie_args(),
|
||||||
]
|
]
|
||||||
_run(args, timeout=90)
|
_run(args, timeout=90)
|
||||||
@@ -928,6 +944,7 @@ def start_download(
|
|||||||
"-o", output_template,
|
"-o", output_template,
|
||||||
"--newline", "--progress", "--no-colors",
|
"--newline", "--progress", "--no-colors",
|
||||||
*subtitle_args,
|
*subtitle_args,
|
||||||
|
*_YT_CLIENT,
|
||||||
*cookie_args,
|
*cookie_args,
|
||||||
]
|
]
|
||||||
cmd, tmp_cookie_path = _make_private_cookie_copy(cmd)
|
cmd, tmp_cookie_path = _make_private_cookie_copy(cmd)
|
||||||
|
|||||||
Reference in New Issue
Block a user