A few weeks back I wrote about the lessons I learned building 120+ free tools solo. A few people asked which tool was the hardest to actually keep working hands down, it's the video downloader. Not because the initial version was hard to build. Because YouTube (and every other platform) actively fights you, and the "happy path" breaks constantly in production.
Here's what building and maintaining it actually looked like.
The naive version (day 1)
Like everyone, I started with yt-dlp. It's genuinely excellent handles 1000+ sites out of the box, actively maintained, and for the first few weeks it just worked.
python:
import yt_dlp
def get_video_info(url):
with yt_dlp.YoutubeDL({'quiet': True}) as ydl:
return ydl.extract_info(url, download=False)
Clean, simple, done. Or so I thought.
Problem 1: YouTube blocks you back
Within a couple weeks of real traffic, YouTube started throwing bot-detection errors intermittently not always, just often enough to be genuinely annoying to debug. Some requests worked fine, others got flagged. Classic cat-and-mouse: yt-dlp ships a fix, YouTube tweaks detection, repeat.
Two things helped:
PO tokens (proof-of-origin tokens) — a bypass mechanism yt-dlp added support for
Cookie injection — passing a logged-in session's cookies through YOUTUBE_COOKIES_TEXT/YOUTUBE_COOKIES_FILE env vars, in Netscape cookie format
But neither is bulletproof, and yt-dlp itself needs constant updates to keep pace. I ended up version-checking and self-updating yt-dlp on every cold start (pip install --upgrade git+...) rather than pinning a version, since a week-old release could already be obsolete against YouTube's latest detection.
Problem 2: When the primary engine fails, fail gracefully
Even with cookies and PO tokens, yt-dlp occasionally gets fully blocked for a stretch. Instead of just erroring out to the user, I added a fallback engine: pytubefix, specifically for YouTube.
The interesting part wasn't just "try A, then try B", it was building a proper circuit breaker:
python
class YouTubeCircuitBreaker:
def init(self):
self.consecutive_failures = 0
self.backoff_until = None
self.backoff_minutes = 1
def record_failure(self):
self.consecutive_failures += 1
if self.consecutive_failures >= 5:
self.backoff_until = now() + timedelta(minutes=self.backoff_minutes)
self.backoff_minutes = min(self.backoff_minutes * 2, 60)
def should_bypass_primary(self):
return self.backoff_until and now() < self.backoff_until
After 5 consecutive yt-dlp failures on YouTube specifically, the circuit "trips" and routes straight to pytubefix for a backoff window — doubling up to a 60-minute ceiling. This meant users stopped seeing hard failures during YouTube's flakier periods, and yt-dlp got breathing room instead of hammering an API that was already rejecting it.
Problem 3: People will absolutely try to use this to hit internal infrastructure
Any tool that accepts a URL and fetches it server-side is a potential SSRF (Server-Side Request Forgery) vector. Someone will eventually try passing http://169.254.169.254/latest/meta-data/ (cloud metadata endpoints) or http://localhost:6379 (internal Redis) just to see what happens.
Before any URL reaches yt-dlp, it goes through a private-host check:
python
def _is_private_host(hostname):
ip = socket.gethostbyname(hostname)
ip_obj = ipaddress.ip_address(ip)
return (
ip_obj.is_private or
ip_obj.is_loopback or
ip_obj.is_link_local or
hostname.endswith(('.internal', '.local'))
)
Blocking private/loopback/link-local ranges and internal TLDs before the extractor ever sees the URL. Cheap to implement, and it closes off an entire class of abuse that's easy to forget about until someone finds it for you.
Problem 4: Format strings are basically user input into a shell-adjacent tool
yt-dlp's format selector syntax is powerful you can do things like bestvideo+bestaudio/best. But if you're building that string from user-supplied quality/format choices, you're one step away from format-selector injection. A whitelist regex on allowed format-selector patterns closed that gap before it became a real problem.
Problem 5: Rate limiting, because bandwidth isn't free
200MB max file size, 10 downloads per IP per hour, and a 5 minute in-memory cache (max 500 entries) for repeated metadata lookups mostly to avoid re-extracting info for the same viral video getting hit 40 times in an hour by different users.
What I'd tell someone building something similar
- Don't trust the "it just works" phase. It works until the platform you depend on changes something, and it will.
- A fallback engine is worth the complexity if your primary dependency has any adversarial relationship with the source (bot detection, rate limits, ToS enforcement).
- Circuit breakers aren't just for microservices even a single external dependency benefits from "stop hammering this after N failures" logic.
- Any tool that fetches user-supplied URLs server-side needs SSRF protection from day one, not as an afterthought after a security report.
The tool is live and free if you want to see it in action: dukotools.com/tools/video-downloader — supports YouTube, TikTok, Instagram, and 13 other platforms.
Happy to go deeper on any part of this in the comments especially curious if anyone's solved the yt-dlp bot-detection cat-and-mouse better than cookie injection + PO tokens.
Top comments (0)