ποΈ Why use an external Python app?
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.
π₯ How to get it
Add the following to your Cargo.toml file:
[dependencies]
yt-dlp = "2.2.0"
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.
π Optional features
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.
- π
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.
ποΈ Cache backends
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 |
At most one persistent backend may be enabled at a time. build.rs enforces this with a
compile_error! if multiple persistent features (cache-json, cache-redb, cache-redis)
are active simultaneously. The cache-memory feature (Moka L1) can 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.2.0", features = ["cache-memory"] }
JSON β persistent, file-system backed, no extra dependencies:
[dependencies]
yt-dlp = { version = "2.2.0", features = ["cache-json"] }
Redb β embedded, single-file, ACID-compliant, great for desktop/server apps:
[dependencies]
yt-dlp = { version = "2.2.0", features = ["cache-redb"] }
Redis β distributed, ideal for multi-node or cloud deployments:
[dependencies]
yt-dlp = { version = "2.2.0", features = ["cache-redis"] }
Tiered (Moka L1 + persistent L2) β best of both worlds:
[dependencies]
yt-dlp = { version = "2.2.0", features = ["cache-memory", "cache-redb"] }
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:
if !video.are_format_urls_fresh() {
}
if let Some(ts) = video.formats_available_at() {
println!("Format URLs valid until approx. {} (unix)", ts + yt_dlp::model::FORMAT_URL_LIFETIME);
}
π Observability & Tracing
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"
use tracing::Level;
use tracing_subscriber::FmtSubscriber;
let subscriber = FmtSubscriber::builder()
.with_max_level(Level::TRACE)
.finish();
tracing::subscriber::set_global_default(subscriber)
.expect("setting default subscriber failed");
Refer to the tracing-subscriber documentation for more advanced configuration (JSON output, log levels, targets, etc.).
π Documentation
The documentation is available on docs.rs.
ποΈ 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:
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?;
let youtube = downloader.youtube_extractor();
let results = youtube.search("rust programming", 10).await?;
let channel = youtube.fetch_channel("UCaYhcUwRBNscFNUKTjgPFiA").await?;
Ok(())
}
- π For any website (YouTube, Vimeo, TikTok, etc.):
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?;
let video = downloader.fetch_video_infos("https://vimeo.com/123456789").await?;
let video_path = downloader.download_video(
&video,
"output.mp4"
).await?;
Ok(())
}
π Examples
use yt_dlp::Downloader;
use std::path::PathBuf;
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let executables_dir = PathBuf::from("libs");
let output_dir = PathBuf::from("output");
let downloader = Downloader::with_new_binaries(
executables_dir,
output_dir
).await?.build().await?;
Ok(())
}
- π¦ Installing the
yt-dlp binary only:
use yt_dlp::client::deps::LibraryInstaller;
use std::path::PathBuf;
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let destination = PathBuf::from("libs");
let installer = LibraryInstaller::new(destination);
let youtube = installer.install_youtube(None).await.unwrap();
Ok(())
}
- π¦ Installing the
ffmpeg binary only:
use yt_dlp::client::deps::LibraryInstaller;
use std::path::PathBuf;
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let destination = PathBuf::from("libs");
let installer = LibraryInstaller::new(destination);
let ffmpeg = installer.install_ffmpeg(None).await.unwrap();
Ok(())
}
- π Updating the
yt-dlp binary:
use yt_dlp::Downloader;
use std::path::PathBuf;
use yt_dlp::client::deps::Libraries;
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let libraries_dir = PathBuf::from("libs");
let output_dir = PathBuf::from("output");
let youtube = libraries_dir.join("yt-dlp");
let ffmpeg = libraries_dir.join("ffmpeg");
let libraries = Libraries::new(youtube, ffmpeg);
let downloader = Downloader::builder(libraries, output_dir)
.build()
.await?;
downloader.update_downloader().await?;
Ok(())
}
- π₯ Fetching a video (with its audio) and downloading it:
use yt_dlp::Downloader;
use std::path::PathBuf;
use yt_dlp::client::deps::Libraries;
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let libraries_dir = PathBuf::from("libs");
let output_dir = PathBuf::from("output");
let youtube = libraries_dir.join("yt-dlp");
let ffmpeg = libraries_dir.join("ffmpeg");
let libraries = Libraries::new(youtube, ffmpeg);
let downloader = Downloader::builder(libraries, output_dir)
.build()
.await?;
let url = String::from("https://www.youtube.com/watch?v=gXtp6C-3JKo");
let video = downloader.fetch_video_infos(url).await?;
let video_path = downloader.download_video(&video, "my-video.mp4").await?;
Ok(())
}
- π Downloading a video to a specific path (ignoring
output_dir):
use yt_dlp::Downloader;
use std::path::PathBuf;
use yt_dlp::client::deps::Libraries;
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let libraries_dir = PathBuf::from("libs");
let output_dir = PathBuf::from("output");
let youtube = libraries_dir.join("yt-dlp");
let ffmpeg = libraries_dir.join("ffmpeg");
let libraries = Libraries::new(youtube, ffmpeg);
let downloader = Downloader::builder(libraries, output_dir)
.build()
.await?;
let url = String::from("https://www.youtube.com/watch?v=gXtp6C-3JKo");
let video = downloader.fetch_video_infos(url).await?;
let path = PathBuf::from("/Users/me/Videos/my-video.mp4");
let video_path = downloader.download_video_to_path(&video, path).await?;
Ok(())
}
- β¨ Using the fluent API with custom quality preferences:
use yt_dlp::Downloader;
use yt_dlp::model::selector::{VideoQuality, AudioQuality, VideoCodecPreference};
use std::path::PathBuf;
use yt_dlp::client::deps::Libraries;
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let libraries_dir = PathBuf::from("libs");
let output_dir = PathBuf::from("output");
let youtube = libraries_dir.join("yt-dlp");
let ffmpeg = libraries_dir.join("ffmpeg");
let libraries = Libraries::new(youtube, ffmpeg);
let downloader = Downloader::builder(libraries, output_dir)
.build()
.await?;
let url = String::from("https://www.youtube.com/watch?v=gXtp6C-3JKo");
let video = downloader.fetch_video_infos(url).await?;
let video_path = downloader.download(&video, "my-video.mp4")
.video_quality(VideoQuality::CustomHeight(1080))
.video_codec(VideoCodecPreference::AVC1)
.audio_quality(AudioQuality::Best)
.execute()
.await?;
Ok(())
}
- π¬ Fetching a video (without its audio) and downloading it:
use yt_dlp::Downloader;
use std::path::PathBuf;
use yt_dlp::client::deps::Libraries;
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let libraries_dir = PathBuf::from("libs");
let output_dir = PathBuf::from("output");
let youtube = libraries_dir.join("yt-dlp");
let ffmpeg = libraries_dir.join("ffmpeg");
let libraries = Libraries::new(youtube, ffmpeg);
let downloader = Downloader::builder(libraries, output_dir)
.build()
.await?;
let url = String::from("https://www.youtube.com/watch?v=gXtp6C-3JKo");
let video = downloader.fetch_video_infos(url).await?;
downloader.download_video_stream(&video, "video.mp4").await?;
Ok(())
}
- π΅ Fetching an audio and downloading it:
use yt_dlp::Downloader;
use std::path::PathBuf;
use yt_dlp::client::deps::Libraries;
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let libraries_dir = PathBuf::from("libs");
let output_dir = PathBuf::from("output");
let youtube = libraries_dir.join("yt-dlp");
let ffmpeg = libraries_dir.join("ffmpeg");
let libraries = Libraries::new(youtube, ffmpeg);
let downloader = Downloader::builder(libraries, output_dir)
.build()
.await?;
let url = String::from("https://www.youtube.com/watch?v=gXtp6C-3JKo");
let video = downloader.fetch_video_infos(url).await?;
downloader.download_audio_stream(&video, "audio.mp3").await?;
Ok(())
}
- π Fetching a specific format and downloading it:
use yt_dlp::Downloader;
use std::path::PathBuf;
use yt_dlp::client::deps::Libraries;
use yt_dlp::VideoSelection;
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let libraries_dir = PathBuf::from("libs");
let output_dir = PathBuf::from("output");
let youtube = libraries_dir.join("yt-dlp");
let ffmpeg = libraries_dir.join("ffmpeg");
let libraries = Libraries::new(youtube, ffmpeg);
let downloader = Downloader::builder(libraries, output_dir)
.build()
.await?;
let url = String::from("https://www.youtube.com/watch?v=gXtp6C-3JKo");
let video = downloader.fetch_video_infos(url).await?;
println!("Video title: {}", video.title);
let video_format = video.best_video_format().unwrap();
let format_path = downloader.download_format(&video_format, "my-video-stream.mp4").await?;
let audio_format = video.worst_audio_format().unwrap();
let audio_path = downloader.download_format(&audio_format, "my-audio-stream.mp3").await?;
Ok(())
}
- βοΈ Combining an audio and a video file into a single file:
use yt_dlp::Downloader;
use std::path::PathBuf;
use yt_dlp::client::deps::Libraries;
use yt_dlp::VideoSelection;
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let libraries_dir = PathBuf::from("libs");
let output_dir = PathBuf::from("output");
let youtube = libraries_dir.join("yt-dlp");
let ffmpeg = libraries_dir.join("ffmpeg");
let libraries = Libraries::new(youtube, ffmpeg);
let downloader = Downloader::builder(libraries, output_dir)
.build()
.await?;
let url = String::from("https://www.youtube.com/watch?v=gXtp6C-3JKo");
let video = downloader.fetch_video_infos(url).await?;
let audio_format = video.best_audio_format().unwrap();
let audio_path = downloader.download_format(&audio_format, "audio-stream.mp3").await?;
let video_format = video.worst_video_format().unwrap();
let video_path = downloader.download_format(&video_format, "video-stream.mp4").await?;
let output_path = downloader.combine_audio_and_video("audio-stream.mp3", "video-stream.mp4", "my-output.mp4").await?;
Ok(())
}
- πΈ Fetching a thumbnail and downloading it:
use yt_dlp::Downloader;
use std::path::PathBuf;
use yt_dlp::client::deps::Libraries;
use yt_dlp::model::selector::ThumbnailQuality;
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let libraries_dir = PathBuf::from("libs");
let output_dir = PathBuf::from("output");
let youtube = libraries_dir.join("yt-dlp");
let ffmpeg = libraries_dir.join("ffmpeg");
let libraries = Libraries::new(youtube, ffmpeg);
let downloader = Downloader::builder(libraries, output_dir)
.build()
.await?;
let url = String::from("https://www.youtube.com/watch?v=gXtp6C-3JKo");
let video = downloader.fetch_video_infos(url).await?;
let thumbnail_path = downloader.download_thumbnail(&video, ThumbnailQuality::Best, "thumbnail.jpg").await?;
Ok(())
}
- πΌοΈ Selecting a thumbnail by minimum resolution and downloading it:
use yt_dlp::Downloader;
use std::path::PathBuf;
use yt_dlp::client::deps::Libraries;
#[tokio::main]
pub 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?;
let url = "https://www.youtube.com/watch?v=gXtp6C-3JKo";
let video = downloader.fetch_video_infos(url).await?;
if let Some(thumb) = video.best_thumbnail() {
println!("Best thumbnail: {} β {:?}", thumb.url, thumb.resolution);
}
if let Some(thumb) = video.thumbnail_for_size(1280, 720) {
println!("HD thumbnail: {}", thumb.url);
}
Ok(())
}
- π Downloading subtitles or automatic captions:
use yt_dlp::Downloader;
use std::path::PathBuf;
use yt_dlp::client::deps::Libraries;
#[tokio::main]
pub 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?;
let url = "https://www.youtube.com/watch?v=gXtp6C-3JKo";
let video = downloader.fetch_video_infos(url).await?;
let langs = downloader.list_subtitle_languages(&video);
println!("Available languages: {:?}", langs);
let sub_path = downloader.download_subtitle(&video, "fr", "subtitles.srt", true).await?;
let paths = downloader.download_all_subtitles(&video, "subtitles/", true).await?;
Ok(())
}
- ποΈ Downloading storyboard preview frames:
use yt_dlp::Downloader;
use yt_dlp::model::StoryboardQuality;
use yt_dlp::VideoSelection;
use std::path::PathBuf;
use yt_dlp::client::deps::Libraries;
#[tokio::main]
pub 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?;
let url = "https://www.youtube.com/watch?v=gXtp6C-3JKo";
let video = downloader.fetch_video_infos(url).await?;
let frames = downloader
.download_storyboard(&video, StoryboardQuality::Best, "storyboard/")
.await?;
println!("Downloaded {} MHTML fragment(s)", frames.len());
if let Some(format) = video.best_storyboard_format() {
let frames = downloader.download_storyboard_format(format, "storyboard/").await?;
}
Ok(())
}
- π₯ Download with download manager and priority:
use yt_dlp::Downloader;
use yt_dlp::download::manager::{ManagerConfig, DownloadPriority};
use std::path::PathBuf;
use yt_dlp::client::deps::Libraries;
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ManagerConfig::builder()
.max_concurrent_downloads(5) .segment_size(1024 * 1024 * 10) .parallel_segments(8) .retry_attempts(5) .max_buffer_size(1024 * 1024 * 20) .build();
let libraries_dir = PathBuf::from("libs");
let output_dir = PathBuf::from("output");
let youtube = libraries_dir.join("yt-dlp");
let ffmpeg = libraries_dir.join("ffmpeg");
let libraries = Libraries::new(youtube, ffmpeg);
let downloader = Downloader::with_download_manager_config(libraries, output_dir, config)
.build()
.await?;
let url = String::from("https://www.youtube.com/watch?v=gXtp6C-3JKo");
let video = downloader.fetch_video_infos(url).await?;
let download_id = downloader.download_video_with_priority(
&video,
"video-high-priority.mp4",
Some(DownloadPriority::High)
).await?;
let status = downloader.wait_for_download(download_id).await;
println!("Final download status: {:?}", status);
Ok(())
}
- π Download with progress tracking:
use yt_dlp::Downloader;
use std::path::PathBuf;
use yt_dlp::client::deps::Libraries;
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let libraries_dir = PathBuf::from("libs");
let output_dir = PathBuf::from("output");
let youtube = libraries_dir.join("yt-dlp");
let ffmpeg = libraries_dir.join("ffmpeg");
let libraries = Libraries::new(youtube, ffmpeg);
let downloader = Downloader::builder(libraries, output_dir)
.build()
.await?;
let url = String::from("https://www.youtube.com/watch?v=gXtp6C-3JKo");
let video = downloader.fetch_video_infos(url).await?;
let download_id = downloader.download_video_with_progress(
&video,
"video-with-progress.mp4",
|downloaded, total| {
let percentage = if total > 0 {
(downloaded as f64 / total as f64 * 100.0) as u64
} else {
0
};
println!("Progress: {}/{} bytes ({}%)", downloaded, total, percentage);
}
).await?;
downloader.wait_for_download(download_id).await;
Ok(())
}
- π Canceling a download:
use yt_dlp::Downloader;
use std::path::PathBuf;
use yt_dlp::client::deps::Libraries;
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let libraries_dir = PathBuf::from("libs");
let output_dir = PathBuf::from("output");
let youtube = libraries_dir.join("yt-dlp");
let ffmpeg = libraries_dir.join("ffmpeg");
let libraries = Libraries::new(youtube, ffmpeg);
let downloader = Downloader::builder(libraries, output_dir)
.build()
.await?;
let url = String::from("https://www.youtube.com/watch?v=gXtp6C-3JKo");
let video = downloader.fetch_video_infos(url).await?;
let download_id = downloader.download_video_with_priority(
&video,
"video-to-cancel.mp4",
None
).await?;
let status = downloader.get_download_status(download_id).await;
println!("Download status: {:?}", status);
let canceled = downloader.cancel_download(download_id).await;
println!("Download canceled: {}", canceled);
Ok(())
}
ποΈ Format Selection
The library provides a powerful format selection system that allows you to download videos and audio with specific quality and codec preferences.
π¬ Video Quality Options
VideoQuality::Best - Selects the highest quality video format available
VideoQuality::High - Targets 1080p resolution
VideoQuality::Medium - Targets 720p resolution
VideoQuality::Low - Targets 480p resolution
VideoQuality::Worst - Selects the lowest quality video format available
VideoQuality::CustomHeight(u32) - Targets a specific height (e.g., CustomHeight(1440) for 1440p)
VideoQuality::CustomWidth(u32) - Targets a specific width (e.g., CustomWidth(1920) for 1920px width)
π΅ Audio Quality Options
AudioQuality::Best - Selects the highest quality audio format available
AudioQuality::High - Targets 192kbps bitrate
AudioQuality::Medium - Targets 128kbps bitrate
AudioQuality::Low - Targets 96kbps bitrate
AudioQuality::Worst - Selects the lowest quality audio format available
AudioQuality::CustomBitrate(u32) - Targets a specific bitrate in kbps (e.g., CustomBitrate(256) for 256kbps)
ποΈ Codec Preferences
πΉ Video Codecs
VideoCodecPreference::VP9 - Prefer VP9 codec
VideoCodecPreference::AVC1 - Prefer AVC1/H.264 codec
VideoCodecPreference::AV1 - Prefer AV01/AV1 codec
VideoCodecPreference::Custom(String) - Prefer a custom codec
VideoCodecPreference::Any - No codec preference
π Audio Codecs
AudioCodecPreference::Opus - Prefer Opus codec
AudioCodecPreference::AAC - Prefer AAC codec
AudioCodecPreference::MP3 - Prefer MP3 codec
AudioCodecPreference::Custom(String) - Prefer a custom codec
AudioCodecPreference::Any - No codec preference
π§ͺ Example: Downloading with Quality and Codec Preferences
use yt_dlp::Downloader;
use yt_dlp::model::selector::{VideoQuality, VideoCodecPreference, AudioQuality, AudioCodecPreference};
use std::path::PathBuf;
use yt_dlp::client::deps::Libraries;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let libraries_dir = PathBuf::from("libs");
let output_dir = PathBuf::from("output");
let youtube = libraries_dir.join("yt-dlp");
let ffmpeg = libraries_dir.join("ffmpeg");
let libraries = Libraries::new(youtube, ffmpeg);
let downloader = Downloader::builder(libraries, output_dir)
.build()
.await?;
let url = String::from("https://www.youtube.com/watch?v=gXtp6C-3JKo");
let video = downloader.fetch_video_infos(url).await?;
let video_path = downloader.download_video_with_quality(
&video,
"complete-video.mp4",
VideoQuality::High,
VideoCodecPreference::VP9,
AudioQuality::High,
AudioCodecPreference::Opus
).await?;
let video_stream_path = downloader.download_video_stream_with_quality(
&video,
"video-only.mp4",
VideoQuality::Medium,
VideoCodecPreference::AVC1
).await?;
let audio_stream_path = downloader.download_audio_stream_with_quality(
&video,
"audio-only.m4a",
AudioQuality::High,
AudioCodecPreference::AAC
).await?;
println!("Downloaded files:");
println!("Complete video: {}", video_path.display());
println!("Video stream: {}", video_stream_path.display());
println!("Audio stream: {}", audio_stream_path.display());
Ok(())
}
π Metadata
The project supports automatic addition of metadata to downloaded files in several formats:
- MP3: Title, artist, comment, genre (from tags), release year
- M4A: Title, artist, comment, genre (from tags), release year
- MP4: All basic metadata, plus technical information (resolution, FPS, video codec, video bitrate, audio codec, audio bitrate, audio channels, sample rate)
- WebM: All basic metadata (via Matroska format), plus technical information as with MP4
- FLAC: Title, artist, album, genre, date, description (via Vorbis comments through lofty), thumbnail embedding
- OGG/Opus: Title, artist, album, genre, date, description (via Vorbis comments through lofty)
- WAV: Title, artist, album, genre (via RIFF INFO through lofty)
- AAC: Title, artist, album, genre, date (via ID3v2 through lofty)
- AIFF: Title, artist, album, genre, date (via ID3v2 through lofty)
- AVI/TS/FLV: Basic metadata via FFmpeg fallback
Metadata is added automatically during download, without requiring any additional action from the user.
π§ Intelligent Metadata Management
The system intelligently manages the application of metadata based on the file type and intended use:
- For standalone files (audio or audio+video), metadata is applied immediately during download.
- For separate audio and video streams that will be combined later, metadata is not applied to individual files to avoid redundant work.
- When combining audio and video streams with
combine_audio_and_video(), complete metadata is applied to the final file, including information from both streams.
This optimized approach ensures that metadata is always present in the final file, while avoiding unnecessary processing of temporary files.
π Chapters
Videos may contain chapters that divide the content into logical segments. The library provides easy access to chapter information and automatically embeds chapters into downloaded video files (MP4/MKV/WebM):
- π Accessing video chapters:
use yt_dlp::Downloader;
use std::path::PathBuf;
use yt_dlp::client::deps::Libraries;
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let libraries_dir = PathBuf::from("libs");
let output_dir = PathBuf::from("output");
let youtube = libraries_dir.join("yt-dlp");
let ffmpeg = libraries_dir.join("ffmpeg");
let libraries = Libraries::new(youtube, ffmpeg);
let downloader = Downloader::builder(libraries, output_dir)
.build()
.await?;
let url = String::from("https://www.youtube.com/watch?v=gXtp6C-3JKo");
let video = downloader.fetch_video_infos(url).await?;
if video.has_chapters() {
println!("Video has {} chapters", video.get_chapters().len());
for chapter in video.get_chapters() {
println!(
"Chapter: {} ({:.2}s - {:.2}s)",
chapter.title.as_deref().unwrap_or("Untitled"),
chapter.start_time,
chapter.end_time
);
}
}
Ok(())
}
- π Finding a chapter at a specific timestamp:
use yt_dlp::Downloader;
use std::path::PathBuf;
use yt_dlp::client::deps::Libraries;
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let libraries_dir = PathBuf::from("libs");
let output_dir = PathBuf::from("output");
let youtube = libraries_dir.join("yt-dlp");
let ffmpeg = libraries_dir.join("ffmpeg");
let libraries = Libraries::new(youtube, ffmpeg);
let downloader = Downloader::builder(libraries, output_dir)
.build()
.await?;
let url = String::from("https://www.youtube.com/watch?v=gXtp6C-3JKo");
let video = downloader.fetch_video_infos(url).await?;
if let Some(chapter) = video.get_chapter_at_time(120.0) {
println!(
"At 2:00, you're in chapter: {}",
chapter.title.as_deref().unwrap_or("Untitled")
);
println!("Chapter duration: {:.2}s", chapter.duration());
}
Ok(())
}
Note: When downloading videos using download_video() or download_video_from_url(), chapters are automatically embedded into the video file metadata. Media players like VLC, MPV, and others will be able to navigate using the chapters!
π₯ Heatmap
Heatmap data (also known as "Most Replayed" segments) shows viewer engagement across different parts of a video. This feature allows you to identify which segments are most popular:
- π₯ Accessing heatmap data:
use yt_dlp::Downloader;
use std::path::PathBuf;
use yt_dlp::client::deps::Libraries;
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let libraries_dir = PathBuf::from("libs");
let output_dir = PathBuf::from("output");
let youtube = libraries_dir.join("yt-dlp");
let ffmpeg = libraries_dir.join("ffmpeg");
let libraries = Libraries::new(youtube, ffmpeg);
let downloader = Downloader::builder(libraries, output_dir)
.build()
.await?;
let url = String::from("https://www.youtube.com/watch?v=gXtp6C-3JKo");
let video = downloader.fetch_video_infos(url).await?;
if video.has_heatmap() {
if let Some(heatmap) = video.get_heatmap() {
println!("Video has {} heatmap segments", heatmap.points().len());
if let Some(most_replayed) = heatmap.most_engaged_segment() {
println!(
"Most replayed segment: {:.2}s - {:.2}s (engagement: {:.2})",
most_replayed.start_time,
most_replayed.end_time,
most_replayed.value
);
}
}
}
Ok(())
}
- π Analyzing engagement by threshold:
use yt_dlp::Downloader;
use std::path::PathBuf;
use yt_dlp::client::deps::Libraries;
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let libraries_dir = PathBuf::from("libs");
let output_dir = PathBuf::from("output");
let youtube = libraries_dir.join("yt-dlp");
let ffmpeg = libraries_dir.join("ffmpeg");
let libraries = Libraries::new(youtube, ffmpeg);
let downloader = Downloader::builder(libraries, output_dir)
.build()
.await?;
let url = String::from("https://www.youtube.com/watch?v=gXtp6C-3JKo");
let video = downloader.fetch_video_infos(url).await?;
if let Some(heatmap) = video.get_heatmap() {
let highly_engaged = heatmap.get_highly_engaged_segments(0.7);
println!("Found {} highly engaged segments", highly_engaged.len());
for segment in highly_engaged {
println!(
"High engagement: {:.2}s - {:.2}s (value: {:.2})",
segment.start_time,
segment.end_time,
segment.value
);
}
if let Some(point) = heatmap.get_point_at_time(120.0) {
println!(
"Engagement at 2:00 is {:.2}",
point.value
);
}
}
Ok(())
}
π Subtitles
The library provides comprehensive subtitle support, including downloading, language selection, and embedding subtitles into videos:
- π Listing available subtitle languages:
use yt_dlp::Downloader;
use std::path::PathBuf;
use yt_dlp::client::deps::Libraries;
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let libraries_dir = PathBuf::from("libs");
let output_dir = PathBuf::from("output");
let youtube = libraries_dir.join("yt-dlp");
let ffmpeg = libraries_dir.join("ffmpeg");
let libraries = Libraries::new(youtube, ffmpeg);
let downloader = Downloader::builder(libraries, output_dir)
.build()
.await?;
let url = String::from("https://www.youtube.com/watch?v=gXtp6C-3JKo");
let video = downloader.fetch_video_infos(url).await?;
let languages = downloader.list_subtitle_languages(&video);
println!("Available subtitle languages: {:?}", languages);
if downloader.has_subtitle_language(&video, "en") {
println!("English subtitles are available");
}
Ok(())
}
- π₯ Downloading a specific subtitle:
use yt_dlp::Downloader;
use std::path::PathBuf;
use yt_dlp::client::deps::Libraries;
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let libraries_dir = PathBuf::from("libs");
let output_dir = PathBuf::from("output");
let youtube = libraries_dir.join("yt-dlp");
let ffmpeg = libraries_dir.join("ffmpeg");
let libraries = Libraries::new(youtube, ffmpeg);
let downloader = Downloader::builder(libraries, output_dir)
.build()
.await?;
let url = String::from("https://www.youtube.com/watch?v=gXtp6C-3JKo");
let video = downloader.fetch_video_infos(url).await?;
let subtitle_path = downloader
.download_subtitle(&video, "en", "subtitle_en.srt", true)
.await?;
println!("Subtitle downloaded to: {:?}", subtitle_path);
Ok(())
}
- π₯ Downloading all available subtitles:
use yt_dlp::Downloader;
use std::path::PathBuf;
use yt_dlp::client::deps::Libraries;
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let libraries_dir = PathBuf::from("libs");
let output_dir = PathBuf::from("output");
let youtube = libraries_dir.join("yt-dlp");
let ffmpeg = libraries_dir.join("ffmpeg");
let libraries = Libraries::new(youtube, ffmpeg);
let downloader = Downloader::builder(libraries, output_dir.clone())
.build()
.await?;
let url = String::from("https://www.youtube.com/watch?v=gXtp6C-3JKo");
let video = downloader.fetch_video_infos(url).await?;
let subtitle_paths = downloader
.download_all_subtitles(&video, &output_dir, true)
.await?;
println!("Downloaded {} subtitle files", subtitle_paths.len());
Ok(())
}
- π¬ Embedding subtitles into a video:
use yt_dlp::Downloader;
use std::path::PathBuf;
use yt_dlp::client::deps::Libraries;
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let libraries_dir = PathBuf::from("libs");
let output_dir = PathBuf::from("output");
let youtube = libraries_dir.join("yt-dlp");
let ffmpeg = libraries_dir.join("ffmpeg");
let libraries = Libraries::new(youtube, ffmpeg);
let downloader = Downloader::builder(libraries, output_dir)
.build()
.await?;
let url = String::from("https://www.youtube.com/watch?v=gXtp6C-3JKo");
let video = downloader.fetch_video_infos(url).await?;
let video_path = downloader.download_video(&video, "video.mp4").await?;
let en_subtitle = downloader
.download_subtitle(&video, "en", "subtitle_en.srt", true)
.await?;
let fr_subtitle = downloader
.download_subtitle(&video, "fr", "subtitle_fr.srt", true)
.await?;
let video_with_subs = downloader
.embed_subtitles_in_video(
&video_path,
&[en_subtitle, fr_subtitle],
"video_with_subtitles.mp4",
)
.await?;
println!("Video with embedded subtitles: {:?}", video_with_subs);
Ok(())
}
- π Working with automatic captions:
use yt_dlp::Downloader;
use yt_dlp::model::caption::Subtitle;
use std::path::PathBuf;
use yt_dlp::client::deps::Libraries;
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let libraries_dir = PathBuf::from("libs");
let output_dir = PathBuf::from("output");
let youtube = libraries_dir.join("yt-dlp");
let ffmpeg = libraries_dir.join("ffmpeg");
let libraries = Libraries::new(youtube, ffmpeg);
let downloader = Downloader::builder(libraries, output_dir)
.build()
.await?;
let url = String::from("https://www.youtube.com/watch?v=gXtp6C-3JKo");
let video = downloader.fetch_video_infos(url).await?;
for (lang_code, subtitles) in &video.subtitles {
for subtitle in subtitles {
if subtitle.is_automatic {
println!(
"Auto-generated subtitle: {} ({})",
subtitle.language_name
.as_deref()
.unwrap_or(lang_code),
subtitle.file_extension()
);
}
}
}
for (lang_code, auto_captions) in &video.automatic_captions {
if let Some(caption) = auto_captions.first() {
let subtitle = Subtitle::from_automatic_caption(
caption,
lang_code.clone(),
);
println!("Converted: {}", subtitle);
}
}
Ok(())
}
π Playlists
The library provides full playlist support, including fetching playlist metadata and downloading videos with various selection options:
- π Fetching playlist information:
use yt_dlp::Downloader;
use std::path::PathBuf;
use yt_dlp::client::deps::Libraries;
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let libraries_dir = PathBuf::from("libs");
let output_dir = PathBuf::from("output");
let youtube = libraries_dir.join("yt-dlp");
let ffmpeg = libraries_dir.join("ffmpeg");
let libraries = Libraries::new(youtube, ffmpeg);
let downloader = Downloader::builder(libraries, output_dir)
.build()
.await?;
let playlist_url = String::from("https://www.youtube.com/playlist?list=PLrAXtmErZgOeiKm4sgNOknGvNjby9efdf");
let playlist = downloader.fetch_playlist_infos(playlist_url).await?;
println!("Playlist: {}", playlist.title);
println!("Videos: {}", playlist.entry_count());
println!("Uploader: {}", playlist.uploader.as_deref().unwrap_or("unknown"));
for entry in &playlist.entries {
println!(
"[{}] {} ({})",
entry.index.unwrap_or(0),
entry.title,
entry.id
);
}
Ok(())
}
- π₯ Downloading entire playlist:
use yt_dlp::Downloader;
use std::path::PathBuf;
use yt_dlp::client::deps::Libraries;
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let libraries_dir = PathBuf::from("libs");
let output_dir = PathBuf::from("output");
let youtube = libraries_dir.join("yt-dlp");
let ffmpeg = libraries_dir.join("ffmpeg");
let libraries = Libraries::new(youtube, ffmpeg);
let downloader = Downloader::builder(libraries, output_dir)
.build()
.await?;
let playlist_url = String::from("https://www.youtube.com/playlist?list=PLrAXtmErZgOeiKm4sgNOknGvNjby9efdf");
let playlist = downloader.fetch_playlist_infos(playlist_url).await?;
let video_paths = downloader
.download_playlist(&playlist, "%(playlist_index)s - %(title)s.mp4")
.await?;
println!("Downloaded {} videos", video_paths.len());
Ok(())
}
- π― Downloading specific videos by index:
use yt_dlp::Downloader;
use std::path::PathBuf;
use yt_dlp::client::deps::Libraries;
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let libraries_dir = PathBuf::from("libs");
let output_dir = PathBuf::from("output");
let youtube = libraries_dir.join("yt-dlp");
let ffmpeg = libraries_dir.join("ffmpeg");
let libraries = Libraries::new(youtube, ffmpeg);
let downloader = Downloader::builder(libraries, output_dir)
.build()
.await?;
let playlist_url = String::from("https://www.youtube.com/playlist?list=PLrAXtmErZgOeiKm4sgNOknGvNjby9efdf");
let playlist = downloader.fetch_playlist_infos(playlist_url).await?;
let indices = vec![0, 2, 5, 10]; let video_paths = downloader
.download_playlist_items(&playlist, &indices, "%(playlist_index)s - %(title)s.mp4")
.await?;
println!("Downloaded {} specific videos", video_paths.len());
Ok(())
}
- π Downloading a range of videos:
use yt_dlp::Downloader;
use std::path::PathBuf;
use yt_dlp::client::deps::Libraries;
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let libraries_dir = PathBuf::from("libs");
let output_dir = PathBuf::from("output");
let youtube = libraries_dir.join("yt-dlp");
let ffmpeg = libraries_dir.join("ffmpeg");
let libraries = Libraries::new(youtube, ffmpeg);
let downloader = Downloader::builder(libraries, output_dir)
.build()
.await?;
let playlist_url = String::from("https://www.youtube.com/playlist?list=PLrAXtmErZgOeiKm4sgNOknGvNjby9efdf");
let playlist = downloader.fetch_playlist_infos(playlist_url).await?;
let video_paths = downloader
.download_playlist_range(&playlist, 5, 15, "%(playlist_index)s - %(title)s.mp4")
.await?;
println!("Downloaded {} videos from range", video_paths.len());
Ok(())
}
- π Filtering and analyzing playlists:
use yt_dlp::Downloader;
use std::path::PathBuf;
use yt_dlp::client::deps::Libraries;
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let libraries_dir = PathBuf::from("libs");
let output_dir = PathBuf::from("output");
let youtube = libraries_dir.join("yt-dlp");
let ffmpeg = libraries_dir.join("ffmpeg");
let libraries = Libraries::new(youtube, ffmpeg);
let downloader = Downloader::builder(libraries, output_dir)
.build()
.await?;
let playlist_url = String::from("https://www.youtube.com/playlist?list=PLrAXtmErZgOeiKm4sgNOknGvNjby9efdf");
let playlist = downloader.fetch_playlist_infos(playlist_url).await?;
if playlist.is_complete() {
println!("All playlist videos have been fetched");
}
let available = playlist.available_entries();
println!("Available videos: {}/{}", available.len(), playlist.entry_count());
if let Some(first_video) = playlist.get_entry_by_index(0) {
println!("First video: {}", first_video.title);
if let Some(duration) = first_video.duration_minutes() {
println!("Duration: {:.2} minutes", duration);
}
}
let range = playlist.get_entries_in_range(0, 10);
println!("First 11 videos: {}", range.len());
Ok(())
}
π Events, Hooks & Webhooks
The library provides a comprehensive event system to monitor download lifecycle and react to events through Rust hooks or HTTP webhooks.
β‘ Event System
All download operations emit events that you can subscribe to:
- π‘ Subscribing to the event stream:
use yt_dlp::Downloader;
use tokio_stream::StreamExt;
use std::path::PathBuf;
use yt_dlp::client::deps::Libraries;
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let libraries_dir = PathBuf::from("libs");
let output_dir = PathBuf::from("output");
let libraries = Libraries::new(
libraries_dir.join("yt-dlp"),
libraries_dir.join("ffmpeg")
);
let downloader = Downloader::builder(libraries, output_dir).build().await?;
let mut stream = downloader.event_stream();
while let Some(Ok(event)) = stream.next().await {
println!("Event: {} - {:?}", event.event_type(), event);
}
Ok(())
}
βοΈ Available Events
The library emits 22 different event types covering the entire download lifecycle:
Download Lifecycle:
VideoFetched - Video metadata retrieved
DownloadQueued - Download added to queue
DownloadStarted - Download begins
DownloadProgress - Progress updates (bytes downloaded, speed, ETA)
DownloadPaused / DownloadResumed - Pause/resume events
DownloadCompleted - Download finished successfully
DownloadFailed - Download failed with error
DownloadCanceled - Download was canceled
Format & Metadata:
FormatSelected - Video/audio format chosen
MetadataApplied - Metadata tags written
ChaptersEmbedded - Chapters added to file
Post-Processing:
PostProcessStarted / PostProcessCompleted / PostProcessFailed - FFmpeg operations
Playlist Operations:
PlaylistFetched - Playlist metadata retrieved
PlaylistItemStarted / PlaylistItemCompleted / PlaylistItemFailed - Per-item events
PlaylistCompleted - Entire playlist finished
Advanced:
SegmentStarted / SegmentCompleted - Parallel segment downloads
πͺ Rust Hooks (Feature: hooks)
Register async functions to be called when events occur:
[dependencies]
yt-dlp = { version = "2.2.0", features = ["hooks"] }
- π£ Registering a hook for download events:
use yt_dlp::events::{EventHook, EventFilter, DownloadEvent, HookResult};
use async_trait::async_trait;
use yt_dlp::Downloader;
use std::path::PathBuf;
use yt_dlp::client::deps::Libraries;
#[derive(Clone)]
struct MyHook;
#[async_trait]
impl EventHook for MyHook {
async fn on_event(&self, event: &DownloadEvent) -> HookResult {
match event {
DownloadEvent::DownloadCompleted { download_id, output_path, .. } => {
println!("Download {} completed: {:?}", download_id, output_path);
}
DownloadEvent::DownloadFailed { download_id, error, .. } => {
eprintln!("Download {} failed: {}", download_id, error);
}
_ => {}
}
Ok(())
}
fn filter(&self) -> EventFilter {
EventFilter::only_terminal()
}
}
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let libraries_dir = PathBuf::from("libs");
let output_dir = PathBuf::from("output");
let libraries = Libraries::new(
libraries_dir.join("yt-dlp"),
libraries_dir.join("ffmpeg")
);
let mut downloader = Downloader::builder(libraries, output_dir).build().await?;
downloader.register_hook(MyHook).await;
Ok(())
}
Hook Features:
- Async execution
- Event filtering (by type, download ID, custom predicates)
- Parallel or sequential execution
- Automatic timeout protection (30s)
- Error isolation (hook failures don't stop downloads)
π Event Filters
use yt_dlp::events::EventFilter;
EventFilter::only_completed();
EventFilter::only_failed();
EventFilter::only_progress();
EventFilter::all().exclude_progress();
EventFilter::download_id(123);
EventFilter::only_terminal();
EventFilter::download_id(123).and_then(|e| e.is_terminal());
EventFilter::all().and_then(|event| {
true
});
π‘ HTTP Webhooks (Feature: webhooks)
Send events to external HTTP endpoints with automatic retry:
[dependencies]
yt-dlp = { version = "2.2.0", features = ["webhooks"] }
- π‘ Registering a webhook:
use yt_dlp::events::{WebhookConfig, WebhookMethod, EventFilter};
use std::time::Duration;
use yt_dlp::Downloader;
use std::path::PathBuf;
use yt_dlp::client::deps::Libraries;
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let libraries_dir = PathBuf::from("libs");
let output_dir = PathBuf::from("output");
let libraries = Libraries::new(
libraries_dir.join("yt-dlp"),
libraries_dir.join("ffmpeg")
);
let webhook = WebhookConfig::new("https://example.com/webhook")
.with_method(WebhookMethod::Post)
.with_header("Authorization", "Bearer your-token")
.with_filter(EventFilter::only_completed())
.with_timeout(Duration::from_secs(10));
let mut downloader = Downloader::builder(libraries, output_dir).build().await?;
downloader.register_webhook(webhook).await;
Ok(())
}
Webhook Features:
- HTTP POST/PUT/PATCH methods
- Custom headers (authentication, etc.)
- Event filtering (same as hooks)
- Automatic retry with exponential backoff (3 attempts by default)
- Configurable timeouts
- JSON payload with event data
- Environment variable configuration
Environment Variables:
export YTDLP_WEBHOOK_URL="https://example.com/webhook"
export YTDLP_WEBHOOK_METHOD="POST" export YTDLP_WEBHOOK_TIMEOUT="10"
- π§ Loading a webhook from environment variables:
use yt_dlp::Downloader;
use yt_dlp::client::deps::Libraries;
use yt_dlp::events::WebhookConfig;
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 mut downloader = Downloader::builder(libraries, PathBuf::from("output")).build().await?;
if let Some(webhook) = WebhookConfig::from_env() {
downloader.register_webhook(webhook).await;
}
Ok(())
}
Webhook Payload:
{
"event_type": "download_completed",
"download_id": 123,
"timestamp": "2025-01-21T10:30:00Z",
"data": {
"download_id": 123,
"output_path": "/path/to/video.mp4",
"duration": 45.2,
"total_bytes": 104857600
}
}
β»οΈ Retry Strategy
- β»οΈ Configuring a retry strategy:
use yt_dlp::events::RetryStrategy;
use std::time::Duration;
let strategy = RetryStrategy::exponential(
3, Duration::from_secs(1), Duration::from_secs(30) );
let strategy = RetryStrategy::linear(
3, Duration::from_secs(5) );
let strategy = RetryStrategy::none();
π Combining Hooks and Webhooks
Use both hooks and webhooks together:
- π Using hooks and webhooks simultaneously:
use yt_dlp::Downloader;
use yt_dlp::events::{EventHook, WebhookConfig, EventFilter};
use yt_dlp::client::deps::Libraries;
use std::path::PathBuf;
#[derive(Clone)]
struct MyLocalHook;
#[async_trait::async_trait]
impl EventHook for MyLocalHook {
async fn on_event(&self, _event: &yt_dlp::events::DownloadEvent) -> yt_dlp::events::HookResult { Ok(()) }
fn filter(&self) -> EventFilter { EventFilter::all() }
}
#[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 mut downloader = Downloader::builder(libraries, PathBuf::from("output")).build().await?;
downloader.register_hook(MyLocalHook).await;
let webhook = WebhookConfig::new("https://example.com/webhook")
.with_filter(EventFilter::only_completed());
downloader.register_webhook(webhook).await;
let video = downloader.fetch_video_infos("https://youtube.com/watch?v=...".to_string()).await?;
downloader.download_video(&video, "video.mp4").await?;
Ok(())
}
π Statistics & Analytics (Feature: statistics)
Enable real-time, aggregate metrics with zero manual bookkeeping:
[dependencies]
yt-dlp = { version = "2.2.0", features = ["statistics"] }
The StatisticsTracker subscribes to the internal event bus in a background task and continuously updates running counters. Call snapshot() at any time to obtain an atomic view of all metrics:
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?;
let video = downloader.fetch_video_infos("https://youtube.com/watch?v=...".to_string()).await?;
downloader.download_video(&video, "video.mp4").await?;
let snapshot = downloader.statistics().snapshot().await;
println!("Downloads completed: {}", snapshot.downloads.completed);
println!("Total bytes: {}", snapshot.downloads.total_bytes);
println!("Avg speed (B/s): {:?}", snapshot.downloads.avg_speed_bytes_per_sec);
println!("Download success %: {:?}", snapshot.downloads.success_rate);
println!("Fetch success %: {:?}", snapshot.fetches.success_rate);
println!("Post-process success: {:?}", snapshot.post_processing.success_rate);
Ok(())
}
The snapshot exposes:
downloads β attempted, completed, failed, canceled, total bytes, average speed, peak speed, success rate
fetches β attempted, succeeded, failed, average duration, success rate (video + playlist fetches)
post_processing β attempted, succeeded, failed, average duration
playlists β playlists fetched, failed, per-item success rate
recent_downloads β bounded history window of completed downloads with per-download details
π Advanced Features
π Proxy Support
The library supports HTTP, HTTPS, and SOCKS5 proxies for both yt-dlp and reqwest downloads:
- π Configuring a proxy with authentication:
use yt_dlp::Downloader;
use yt_dlp::client::proxy::{ProxyConfig, ProxyType};
use yt_dlp::client::deps::Libraries;
use std::path::PathBuf;
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let libraries_dir = PathBuf::from("libs");
let output_dir = PathBuf::from("output");
let libraries = Libraries::new(
libraries_dir.join("yt-dlp"),
libraries_dir.join("ffmpeg")
);
let proxy = ProxyConfig::new(ProxyType::Http, "http://proxy.example.com:8080")
.with_auth("username", "password")
.with_no_proxy(vec!["localhost".to_string(), "127.0.0.1".to_string()]);
let downloader = Downloader::builder(libraries, output_dir)
.with_proxy(proxy)
.build()
.await?;
let url = String::from("https://www.youtube.com/watch?v=gXtp6C-3JKo");
let video = downloader.fetch_video_infos(url).await?;
downloader.download_video(&video, "video.mp4").await?;
Ok(())
}
Supported proxy types:
- HTTP/HTTPS: Standard HTTP proxies
- SOCKS5: SOCKS5 proxies for more flexibility
- Authentication: Username/password authentication
- No-proxy list: Exclude specific domains from proxying
π Authentication & Cookies
Many platforms (YouTube bot-protection, Twitch, age-restricted content, etc.) require authentication.
The library supports three authentication modes that are propagated to both metadata extraction and
all download operations automatically.
Cookie file (Netscape format)
use yt_dlp::Downloader;
use yt_dlp::client::deps::Libraries;
use std::path::PathBuf;
#[tokio::main]
pub 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, PathBuf::from("output"))
.with_cookies("cookies.txt")
.build()
.await?;
let video = downloader.fetch_video_infos("https://www.youtube.com/watch?v=gXtp6C-3JKo").await?;
downloader.download_video(&video, "video.mp4").await?;
Ok(())
}
Browser cookies
use yt_dlp::Downloader;
use yt_dlp::client::deps::Libraries;
use std::path::PathBuf;
#[tokio::main]
pub 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, PathBuf::from("output"))
.with_cookies_from_browser("chrome") .build()
.await?;
let video = downloader.fetch_video_infos("https://www.youtube.com/watch?v=gXtp6C-3JKo").await?;
downloader.download_video(&video, "video.mp4").await?;
Ok(())
}
Runtime (after build)
use yt_dlp::Downloader;
use yt_dlp::client::deps::Libraries;
use std::path::PathBuf;
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let libraries = Libraries::new(
PathBuf::from("libs/yt-dlp"),
PathBuf::from("libs/ffmpeg"),
);
let mut downloader = Downloader::builder(libraries, PathBuf::from("output"))
.build()
.await?;
downloader.with_cookies("cookies.txt");
let video = downloader.fetch_video_infos("https://www.youtube.com/watch?v=gXtp6C-3JKo").await?;
downloader.download_video(&video, "video.mp4").await?;
Ok(())
}
.netrc
use yt_dlp::Downloader;
use yt_dlp::client::deps::Libraries;
use std::path::PathBuf;
#[tokio::main]
pub 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, PathBuf::from("output"))
.with_netrc()
.build()
.await?;
let video = downloader.fetch_video_infos("https://www.youtube.com/watch?v=gXtp6C-3JKo").await?;
downloader.download_video(&video, "video.mp4").await?;
Ok(())
}
βοΈ Clip extraction & chapter splitting
The library supports downloading a specific time range or a specific chapter from a video without fetching the whole file. Seeking is handled by media-seek β a pure Rust container index parser that translates timestamps into HTTP Range byte offsets.
- βοΈ Downloading a specific time range (seconds):
use yt_dlp::Downloader;
use yt_dlp::client::deps::Libraries;
use std::path::PathBuf;
#[tokio::main]
pub 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?;
let url = "https://www.youtube.com/watch?v=gXtp6C-3JKo";
let video = downloader.fetch_video_infos(url).await?;
let clip_path = downloader
.download(&video, "clip.mp4")
.time_range(60.0, 120.0)?
.execute()
.await?;
Ok(())
}
- π Downloading a specific chapter by index range:
use yt_dlp::Downloader;
use yt_dlp::client::deps::Libraries;
use std::path::PathBuf;
#[tokio::main]
pub 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?;
let url = "https://www.youtube.com/watch?v=gXtp6C-3JKo";
let video = downloader.fetch_video_infos(url).await?;
let clip_path = downloader
.download(&video, "chapters.mp4")
.chapters(0, 2)?
.execute()
.await?;
Ok(())
}
- πͺ Splitting a downloaded video into one file per chapter:
use yt_dlp::Downloader;
use yt_dlp::client::deps::Libraries;
use std::path::PathBuf;
#[tokio::main]
pub 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?;
let url = "https://www.youtube.com/watch?v=gXtp6C-3JKo";
let video = downloader.fetch_video_infos(url).await?;
let chapter_files: Vec<PathBuf> = downloader
.split_by_chapters(&video, "output/chapters/")
.await?;
for path in &chapter_files {
println!("Chapter file: {}", path.display());
}
Ok(())
}
π΄ Live Stream Recording (Feature: live-recording)
Record live streams using either the pure-Rust reqwest engine or FFmpeg as a fallback.
Enable the feature in your Cargo.toml:
[dependencies]
yt-dlp = { version = "2.2.0", features = ["live-recording"] }
π₯ Basic live recording (reqwest engine)
use yt_dlp::Downloader;
use yt_dlp::client::deps::Libraries;
use std::path::PathBuf;
use std::time::Duration;
#[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?;
let video = downloader.fetch_video_infos("https://youtube.com/watch?v=LIVE_ID").await?;
let result = downloader.record_live(&video, "live-recording.ts")
.with_max_duration(Duration::from_secs(3600))
.execute()
.await?;
println!("Recorded {} bytes in {:.1}s", result.total_bytes, result.total_duration.as_secs_f64());
Ok(())
}
π¬ FFmpeg fallback engine
use yt_dlp::Downloader;
use yt_dlp::events::RecordingMethod;
use yt_dlp::client::deps::Libraries;
use std::path::PathBuf;
use std::time::Duration;
#[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?;
let video = downloader.fetch_video_infos("https://youtube.com/watch?v=LIVE_ID").await?;
let result = downloader.record_live(&video, "live-recording.ts")
.with_method(RecordingMethod::Fallback)
.with_max_duration(Duration::from_secs(600))
.execute()
.await?;
Ok(())
}
Implementation details:
- Reqwest engine (default): Pure-Rust HLS segment fetcher. Polls the media playlist, downloads new segments, and writes them sequentially. Zero-copy
bytes::Bytes, progress events throttled at 50 ms.
- FFmpeg engine (fallback): Spawns
ffmpeg -i <url> -c copy <output>. Stops gracefully via stdin q. Useful for encrypted streams or complex HLS features.
- Recording stops on cancellation token,
#EXT-X-ENDLIST, or max duration.
- Live events:
LiveRecordingStarted, LiveRecordingProgress, LiveRecordingStopped, LiveRecordingFailed.
π¨ Post-Processing Options
Apply advanced post-processing to videos using FFmpeg:
π§ Basic codec conversion
- π§ Converting video codec and bitrate:
use yt_dlp::Downloader;
use yt_dlp::download::postprocess::{PostProcessConfig, VideoCodec, AudioCodec};
use yt_dlp::client::deps::Libraries;
use std::path::PathBuf;
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let libraries_dir = PathBuf::from("libs");
let output_dir = PathBuf::from("output");
let libraries = Libraries::new(
libraries_dir.join("yt-dlp"),
libraries_dir.join("ffmpeg")
);
let downloader = Downloader::builder(libraries, output_dir).build().await?;
let config = PostProcessConfig::new()
.with_video_codec(VideoCodec::H264)
.with_audio_codec(AudioCodec::AAC)
.with_video_bitrate("2M")
.with_audio_bitrate("192k");
downloader.postprocess_video("input.mp4", "output.mp4", config).await?;
Ok(())
}
ποΈ Advanced post-processing with filters
- ποΈ Applying resolution, framerate, and visual filters:
use yt_dlp::Downloader;
use yt_dlp::download::postprocess::{
PostProcessConfig, VideoCodec, Resolution, EncodingPreset,
FfmpegFilter, WatermarkPosition
};
use yt_dlp::client::deps::Libraries;
use std::path::PathBuf;
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let libraries_dir = PathBuf::from("libs");
let output_dir = PathBuf::from("output");
let libraries = Libraries::new(
libraries_dir.join("yt-dlp"),
libraries_dir.join("ffmpeg")
);
let downloader = Downloader::builder(libraries, output_dir).build().await?;
let config = PostProcessConfig::new()
.with_video_codec(VideoCodec::H265)
.with_resolution(Resolution::HD)
.with_framerate(30)
.with_preset(EncodingPreset::Medium)
.add_filter(FfmpegFilter::Brightness { value: 0.1 })
.add_filter(FfmpegFilter::Contrast { value: 1.2 })
.add_filter(FfmpegFilter::Watermark {
path: "logo.png".to_string(),
position: WatermarkPosition::BottomRight,
});
downloader.postprocess_video("input.mp4", "processed.mp4", config).await?;
Ok(())
}
π Available post-processing options
Video Codecs:
- H.264 (libx264) - Most compatible
- H.265 (libx265) - Better compression
- VP9 (libvpx-vp9) - Open format
- AV1 (libaom-av1) - Next-gen codec
- Copy - No re-encoding
Audio Codecs:
- AAC - High quality, widely supported
- MP3 (libmp3lame) - Universal compatibility
- Opus - Best quality/size ratio
- Vorbis - Open format
- Copy - No re-encoding
Resolutions:
- UHD8K (7680x4320)
- UHD4K (3840x2160)
- QHD (2560x1440)
- FullHD (1920x1080)
- HD (1280x720)
- SD (854x480)
- Low (640x360)
- Custom { width, height }
Encoding Presets:
- UltraFast, SuperFast, VeryFast, Fast
- Medium (balanced)
- Slow, Slower, VerySlow (best quality)
Video Filters:
- Crop:
Crop { width, height, x, y }
- Rotate:
Rotate { angle } (in degrees)
- Watermark:
Watermark { path, position }
- Brightness:
Brightness { value } (-1.0 to 1.0)
- Contrast:
Contrast { value } (0.0 to 4.0)
- Saturation:
Saturation { value } (0.0 to 3.0)
- Blur:
Blur { radius }
- FlipHorizontal, FlipVertical
- Denoise, Sharpen
- Custom:
Custom { filter } - Any FFmpeg filter string
β‘ Speed Profiles
The library includes an intelligent speed optimization system that automatically configures download parameters based on your internet connection speed. This feature significantly improves download performance for both individual videos and playlists.
π Available Speed Profiles
Three pre-configured profiles are available:
π’ Conservative (for connections < 50 Mbps)
- 3 concurrent downloads
- 4-8 parallel segments per file
- 5 MB segment size
- 10 MB buffer
- 2 concurrent playlist downloads
- Best for: Standard internet, avoiding network congestion, limited bandwidth
βοΈ Balanced (for connections 50-500 Mbps) - Default
- 4 concurrent downloads
- 5β20 parallel segments per file
- 8 MB segment size
- 20 MB buffer
- 3 concurrent playlist downloads
- Best for: Most modern internet connections, general use
π Aggressive (for connections > 500 Mbps)
- 6 concurrent downloads
- 6β24 parallel segments per file
- 10 MB segment size
- 30 MB buffer
- 5 concurrent playlist downloads
- Best for: High-bandwidth connections (fiber, gigabit), maximum speed
π Using Speed Profiles
- π Selecting a speed profile at build time:
use yt_dlp::Downloader;
use yt_dlp::download::SpeedProfile;
use yt_dlp::client::deps::Libraries;
use std::path::PathBuf;
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let libraries_dir = PathBuf::from("libs");
let output_dir = PathBuf::from("output");
let libraries = Libraries::new(
libraries_dir.join("yt-dlp"),
libraries_dir.join("ffmpeg")
);
let downloader = Downloader::builder(libraries, output_dir)
.with_speed_profile(SpeedProfile::Aggressive)
.build()
.await?;
let url = String::from("https://www.youtube.com/watch?v=gXtp6C-3JKo");
let video = downloader.fetch_video_infos(url).await?;
downloader.download_video(&video, "video.mp4").await?;
Ok(())
}
βοΈ Manual Configuration
- βοΈ Fine-grained control over download parameters:
use yt_dlp::Downloader;
use yt_dlp::download::ManagerConfig;
use yt_dlp::client::deps::Libraries;
use std::path::PathBuf;
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let libraries_dir = PathBuf::from("libs");
let output_dir = PathBuf::from("output");
let libraries = Libraries::new(
libraries_dir.join("yt-dlp"),
libraries_dir.join("ffmpeg")
);
let config = ManagerConfig::builder()
.max_concurrent_downloads(10) .segment_size(15 * 1024 * 1024) .parallel_segments(16) .build();
let downloader = Downloader::builder(libraries, output_dir)
.with_download_manager_config(config)
.build()
.await?;
Ok(())
}
π Performance Improvements
The speed optimization system includes several advanced features:
- HTTP/2 Support: Automatically enabled for better connection multiplexing
- Transparent Compression: Responses are automatically decompressed (gzip, brotli) for faster transfers
- TCP Nodelay: Nagle's algorithm is disabled for lower latency on small writes
- CDN-Friendly Range Probing: Uses
GET with Range: bytes=0-0 instead of HEAD for better CDN compatibility
- Progress Throttling: Progress callbacks are throttled to 50 ms intervals to reduce overhead
- Parallel Playlist Downloads: Playlists are downloaded in parallel by default (previously sequential)
- Dynamic Segment Allocation: Automatically adjusts the number of parallel segments based on file size
- Connection Pooling: Reuses HTTP connections for better performance
- Intelligent Buffering: Optimized buffer sizes based on your profile
- Async DNS (opt-in): Enable the
hickory-dns feature for a fully async, pure-Rust DNS resolver
Expected Performance Gains:
For individual videos:
- Conservative: ~30% faster (HTTP/2)
- Balanced: ~100% faster (2x segments + HTTP/2)
- Aggressive: ~200% faster (3x segments + HTTP/2)
For playlists:
- Conservative: ~150% faster (2 videos in parallel)
- Balanced: ~200% faster (3 videos in parallel)
- Aggressive: ~400% faster (5 videos in parallel)
Note: Actual performance gains depend on your internet speed, server limitations, and network conditions.
π Multi-Site Support
This library supports all 1,800+ extractors from yt-dlp!
While the examples focus on YouTube (the most common use case), the library works seamlessly with any site supported by yt-dlp. Simply pass the URL - yt-dlp automatically detects the correct extractor.
ποΈ Supported Sites
- Video platforms: YouTube, Vimeo, Dailymotion, Twitch
- Social media: Instagram, TikTok, Twitter/X, Facebook
- Streaming services: Netflix, Disney+, Crunchyroll (may require authentication)
- Music platforms: Spotify, SoundCloud, Bandcamp
- News outlets: CNN, BBC, Fox News
- And 1,800+ more...
For the complete list, see yt-dlp's supported sites.
π§© Examples
- π¬ Downloading from Vimeo:
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, PathBuf::from("output")).build().await?;
let url = "https://vimeo.com/148751763".to_string();
let video = downloader.fetch_video_infos(url).await?;
downloader.download_video(&video, "vimeo-video.mp4").await?;
Ok(())
}
- π± Downloading from TikTok:
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, PathBuf::from("output")).build().await?;
let url = "https://www.tiktok.com/@user/video/123".to_string();
let video = downloader.fetch_video_infos(url).await?;
downloader.download_video(&video, "tiktok-video.mp4").await?;
Ok(())
}
- πΈ Downloading from Instagram (may require cookies for authentication):
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, PathBuf::from("output")).build().await?;
let url = "https://www.instagram.com/p/ABC123/".to_string();
let video = downloader.fetch_video_infos(url).await?;
Ok(())
}
- π Detecting which extractor handles a URL:
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?;
let extractor = downloader.detect_extractor("https://vimeo.com/123").await?;
println!("This URL uses the '{}' extractor", extractor);
Ok(())
}
For detailed documentation, examples, and authentication instructions, see the extractor module documentation.
π¬ Profiling (Feature: profiling)
See PROFILING.md for the complete guide (flamegraph, samply, dhat-rs, heaptrack, Criterion, and the raw vs library comparison tool).
ποΈ Performances
The library fetches video metadata via yt-dlp --dump-json, then downloads format streams
directly over HTTP using parallel segments β bypassing yt-dlp's sequential download engine.
Run examples/compare.rs with any public YouTube URL to
reproduce these numbers on your own connection:
cargo run --example compare --features profiling --release -- https://www.youtube.com/watch?v=gXtp6C-3JKo --cookies-from-browser safari --runs 10
Results below are averages over 10 runs on a typical broadband connection.
Methodology: raw yt-dlp re-fetches metadata on every download. The library fetches metadata
once, caches it, and then downloads each format via parallel HTTP segments β this separation
is the core optimisation reflected in the results below.
π΅ Audio streams
| Scenario |
yt-dlp |
Conservative |
Balanced (default) |
Aggressive |
| Audio 96 kbps (Low) |
8.26s |
1.27s |
1.47s |
1.27s |
| Audio 128 kbps (Medium) |
7.83s |
1.11s |
1.12s |
1.18s |
| Audio 192 kbps (High) |
8.53s |
1.38s |
1.26s |
1.19s |
| Audio best quality |
8.51s |
1.22s |
1.15s |
1.15s |
π¬ Video streams (no audio)
| Scenario |
yt-dlp |
Conservative |
Balanced (default) |
Aggressive |
| Video 480p |
8.59s |
2.39s |
2.11s |
2.58s |
| Video 720p |
9.84s |
3.67s |
3.86s |
3.89s |
| Video 1080p |
17.6s |
8.65s |
8.60s |
8.81s |
| Video best quality |
16.6s |
11.7s |
11.8s |
12.0s |
π¦ Muxed streams β native (YouTube pre-muxed, no ffmpeg)
YouTube serves some formats already containing both video and audio tracks. No post-processing
is needed β the file is downloaded as-is.
| Scenario |
yt-dlp |
Conservative |
Balanced (default) |
Aggressive |
| Native 360p (mp4) |
9.69s |
2.11s |
2.00s |
2.16s |
| Native 720p (mp4) |
20.7s |
2.08s |
2.10s |
2.06s |
π¦ Muxed streams β combined by ffmpeg
For higher-quality streams, YouTube only provides separate video and audio tracks. The library
downloads both in parallel, then ffmpeg merges them using stream copy (no re-encoding) when
the audio container is compatible with the output format (e.g. AAC/m4a β mp4).
| Scenario |
yt-dlp |
Conservative |
Balanced (default) |
Aggressive |
| Muxed 480p |
9.41s |
3.51s |
3.48s |
3.34s |
| Muxed 720p |
10.4s |
4.95s |
4.92s |
4.97s |
| Muxed 1080p |
18.3s |
10.4s |
10.5s |
10.3s |
| Muxed best quality |
18.2s |
13.7s |
13.7s |
13.3s |
π Speed profiles
| Profile |
Parallel segments |
Segment size |
Use case |
Conservative |
1β16 |
5 MB |
< 50 Mbps connections |
Balanced (default) |
2β20 |
8 MB |
Most modern connections |
Aggressive |
3β24 |
10 MB |
Fibre / gigabit |
See PROFILING.md for detailed micro-benchmarks.
π‘Features coming soon
π The media-seek crate
This library uses media-seek, a standalone sub-crate published independently on crates.io, for container index parsing. When you use clip extraction or chapter range downloads, media-seek parses the stream header to find the exact byte offsets β no subprocess, no FFmpeg involved for the seek step.
Supported container formats: MP4/M4A (fMP4 SIDX), WebM/MKV (EBML Cues), MP3 (Xing/VBRI TOC or CBR), OGG (granule bisection), FLAC (SEEKTABLE), WAV, AIFF, AAC/ADTS, FLV (AMF0 keyframes), AVI (idx1), MPEG-TS (PCR binary search).
You can also use media-seek directly in your own project:
[dependencies]
media-seek = "0.2.0"
See the media-seek README for the full API reference and usage examples.
π€ Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Make sure to follow the Contributing Guidelines.
π License
This project is licensed under the GPL-3.0 License.