This library is a Rust asynchronous wrapper around the yt-dlp command line tool, a feature-rich audio/video downloader supporting YouTube, Vimeo, TikTok, Instagram, Twitter, and more.
The crate is designed to download audio and video from various websites. You don't need to care about dependencies, yt-dlp and ffmpeg will be downloaded automatically.
⚠️ The project is still in development, so if you encounter any bugs or have any feature requests, please open an issue or a discussion.
Report a Bug · Request a Feature · Ask a Question
Originally, to download videos from YouTube, I used the rustube crate, written in pure Rust and without any external dependencies.
However, I quickly realized that due to frequent breaking changes on the YouTube website, the crate was outdated and no longer functional.
After a few tests and research, I concluded that the python app yt-dlp was the best compromise, thanks to its regular updates and massive community.
Its standalone binaries and its ability to output the fetched data in JSON format make it a perfect candidate for a Rust wrapper.
Using an external program is not ideal, but it is the most reliable and maintained solution for now.
Add the following to your Cargo.toml file:
[dependencies]
yt-dlp = "2.7.2"
A new release is automatically published every two weeks, to keep up to date with dependencies and features. Make sure to check the releases page to see the latest version of the crate.
This library puts a lot of functionality behind optional features in order to optimize compile time for the most common use cases. The following features are available.
hooks - Enables Rust hooks and callbacks for download events. Allows registering async functions that will be called when events occur.webhooks - Enables HTTP webhooks delivery for download events. Allows sending events to external HTTP endpoints with retry logic.statistics - Enables real-time statistics and analytics on downloads and fetches. Exposes aggregate counters, averages, success rates, and a bounded history window.cache-memory (enabled by default) — In-memory Moka cache (pulls in moka). Fast TTL-based eviction; no persistence.cache-json — JSON file-system backend. One .json file per entry.cache-redb — Embedded redb backend. Single-file, pure-rust, ACID-compliant.cache-redis — Distributed Redis backend. Native TTL via SETEX.live-recording - Enables live stream recording via HLS segment fetching (reqwest) or FFmpeg fallback. Pulls in m3u8-rs for HLS manifest parsing.live-streaming - Enables live fragment streaming via HLS segment fetching (reqwest). Pulls in m3u8-rs for HLS manifest parsing.rustls - Enables the rustls-tls feature in the reqwest crate.
This enables building the application without openssl or other system sourced SSL libraries.hickory-dns - Enables async DNS resolution via Hickory DNS (passes reqwest/hickory-dns). Replaces the default blocking system resolver with a fully async, pure-Rust resolver.The library includes a tiered metadata cache that avoids redundant yt-dlp subprocess calls for video info, downloaded files, and playlists. The architecture uses an optional L1 in-memory layer (Moka) and an optional L2 persistent layer, selected exclusively via Cargo features:
| Feature | Backend | Persistence | Notes |
|---|---|---|---|
cache-memory (default) |
In-memory Moka | ❌ No | TTL-based eviction, async-ready |
cache-json |
JSON files on disk | ✅ Yes | One .json file per entry in the cache directory |
cache-redb |
Embedded redb | ✅ Yes | Single-file, pure-Rust, ACID transactions |
cache-redis |
Redis | ✅ Yes | Distributed, native TTL via SETEX |
Multiple persistent backends can be compiled in simultaneously. When exactly one is enabled, it is
selected automatically. When several are enabled, CacheConfig::persistent_backend must be set
explicitly; otherwise CacheLayer::from_config returns an Error::AmbiguousCacheBackend at runtime.
The cache-memory feature (Moka L1) can always be combined with any persistent backend for a
tiered L1 + L2 setup.
Default (in-memory Moka) — no persistence, TTL-based eviction, useful for short-lived processes:
[dependencies]
yt-dlp = { version = "2.7.2", features = ["cache-memory"] }
JSON — persistent, file-system backed, no extra dependencies:
[dependencies]
yt-dlp = { version = "2.7.2", features = ["cache-json"] }
Redb — embedded, single-file, ACID-compliant, great for desktop/server apps:
[dependencies]
yt-dlp = { version = "2.7.2", features = ["cache-redb"] }
Redis — distributed, ideal for multi-node or cloud deployments:
[dependencies]
yt-dlp = { version = "2.7.2", features = ["cache-redis"] }
Tiered (Moka L1 + persistent L2) — best of both worlds:
[dependencies]
yt-dlp = { version = "2.7.2", features = ["cache-memory", "cache-redb"] }
Multiple backends compiled in — select one at runtime via CacheConfig::persistent_backend:
[dependencies]
yt-dlp = { version = "2.7.2", features = ["cache-memory", "cache-json", "cache-redb"] }
```rust,ignore use yt_dlp::prelude::*;
let config = CacheConfig::builder() .cache_dir("cache") .persistent_backend(PersistentBackendKind::Redb) // required when multiple compiled in .build();
#### CDN URL expiry and cache invalidation
When a `Video` is cached, its stream format URLs are valid for approximately **6 hours** (YouTube CDN lifetime). The library tracks this automatically via the `available_at` field on each `Format`.
On every `fetch_video_infos` call, the cache checks whether the format URLs are still fresh. If they have expired, the cached entry is **silently invalidated** and the video is re-fetched — so you never end up downloading with stale CDN URLs. The configured TTL (default 24 h) acts as an upper bound; the effective TTL is `min(configured_ttl, cdn_url_lifetime)`.
This behavior is transparent and requires no changes to your code. You can inspect the expiry yourself:
```rust,ignore
if !video.are_format_urls_fresh() {
// URLs are stale — fetch_video_infos will re-fetch automatically
}
// Or get the earliest available_at timestamp across all downloadable formats:
if let Some(ts) = video.formats_available_at() {
println!("Format URLs valid until approx. {} (unix)", ts + yt_dlp::model::FORMAT_URL_LIFETIME);
}
This crate always includes the
tracing crate. The library emits debug and trace span events throughout its internal operations (downloads, cache lookups, subprocess execution, etc.).
⚠️ Important: tracing macros are pure no-ops without a configured subscriber. If you don't add one, there is zero runtime overhead.
To capture logs, add a subscriber in your application:
[dependencies]
tracing-subscriber = "0.3"
```rust,ignore use tracing::Level; use tracing_subscriber::FmtSubscriber;
let subscriber = FmtSubscriber::builder() // all spans/events with a level higher than TRACE (e.g, debug, info, warn, etc.) // will be written to stdout. .with_max_level(Level::TRACE) // completes the builder. .finish(); tracing::subscriber::set_global_default(subscriber) .expect("setting default subscriber failed");
Refer to the [`tracing-subscriber` documentation](https://docs.rs/tracing-subscriber) for more advanced configuration (JSON output, log levels, targets, etc.).
---
## 📖 Documentation
The documentation is available on [docs.rs](https://docs.rs/yt-dlp).
## 🏗️ Multi-Extractor Architecture
This library now supports downloading from **1,800+ websites** through a flexible extractor system:
- **`Downloader`** - Universal client supporting all sites via the extractors
- **`extractor::Youtube`** - Highly optimized YouTube extractor with platform-specific features:
- Player client selection (Android, iOS, Web, TvEmbedded) for bypassing restrictions
- Format presets (Best, Premium, High, Medium, Low, AudioOnly, ModernCodecs)
- YouTube-specific methods: `search()`, `fetch_channel()`, `fetch_user()`, `fetch_playlist_paginated()`
- **`extractor::Generic`** - Universal extractor for all other sites with authentication support
### 🧩 Usage Patterns
- 🎬️ For YouTube with optimizations:
```rust,no_run
use yt_dlp::Downloader;
use yt_dlp::client::deps::Libraries;
use std::path::PathBuf;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let libraries = Libraries::new(
PathBuf::from("libs/yt-dlp"),
PathBuf::from("libs/ffmpeg")
);
let downloader = Downloader::builder(libraries, "output")
.build()
.await?;
// Access YouTube-specific features
let youtube = downloader.youtube_extractor();
let results = youtube.search("rust programming", 10).await?;
let channel = youtube.fetch_channel("UCaYhcUwRBNscFNUKTjgPFiA").await?;
Ok(())
}
async fn main() -> Result<(), Box> { let libraries = Libraries::new( PathBuf::from("libs/yt-dlp"), PathBuf::from("libs/ffmpeg") ); let downloader = Downloader::builder(libraries, "output") .build() .await?;
// Works with any supported site
let video = downloader.fetch_video_infos("https://vimeo.com/123456789").await?;
let video_path = downloader.download_video(
&video,
"output.mp4"
).await?;
Ok(())
} ```
yt-dlp and ffmpeg binaries:
```rust,no_run
use yt_dlp::Downloader;
use std::path::PathBuf;pub async fn main() -> Result<(), Box
$ claude mcp add yt-dlp \
-- python -m otcore.mcp_server <graph>