🎬️ 一个用于视频下载的 Rust 库(支持自动安装依赖)
💭️ 为什么要使用外部 Python 应用?
最初,为了从 YouTube 下载视频,我使用了 rustube crate,它由纯 Rust 编写且没有任何外部依赖。
然而,我很快意识到,由于 YouTube 网站频繁发生破坏性变更,该 crate 已经过时且不再可用。
经过几次测试和研究后,我得出结论,Python 应用 [yt-dlp 是最佳折中方案,得益于其定期更新和庞大的社区。
其独立的二进制文件以及将获取的数据输出为 JSON 格式的能力,使其成为 Rust 封装器的完美候选者。
使用外部程序并非理想选择,但目前这是最可靠且维护良好的解决方案。
📥 如何获取
将以下内容添加到你的 Cargo.toml 文件中:
[dependencies]
yt-dlp = "2.8.1"
每两周会自动发布一个新版本,以保持与依赖项和功能的同步。 请确保查看 releases 页面以查看该 crate 的最新版本。
🔌 可选功能
该库将大量功能置于可选功能之后,以优化 最常见用例的编译时间。以下功能 可用。
- 🪝
hooks- 启用下载事件的 Rust 钩子和回调。允许注册在事件发生时被调用的异步函数。 - 📡
webhooks- 启用下载事件的 HTTP Webhook 投递。允许将事件发送到外部 HTTP 端点,并带有重试逻辑。 - 📊
statistics- 启用下载和获取的实时统计和分析。暴露聚合计数器、平均值、成功率以及有界的历史窗口。 - ⚡
cache-memory(默认启用) — 内存 Moka 缓存(引入moka)。基于 TTL 的快速淘汰;无持久化。 - 🗃️
cache-json— JSON 文件系统后端。每个条目一个.json文件。 - 🗄️
cache-redb— 嵌入式redb后端。单文件,纯 Rust,符合 ACID 规范。 - 🌐
cache-redis— 分布式Redis后端。通过SETEX实现原生 TTL。 - 🔴
live-recording- 启用通过 HLS 分段获取(reqwest)或 FFmpeg 回退的实时流录制。引入m3u8-rs用于 HLS 清单解析。 - 📡
live-streaming- 启用通过 HLS 分段获取(reqwest)的实时片段流传输。引入m3u8-rs用于 HLS 清单解析。 - 🔒
rustls- 启用reqwestcrate 中的rustls-tls功能。 这使得可以在没有 openssl 或其他系统来源的 SSL 库的情况下构建应用程序。 - 🌍
hickory-dns- 启用通过Hickory DNS的异步 DNS 解析(传递reqwest/hickory-dns)。用完全异步的纯 Rust 解析器替换默认的阻塞系统解析器。
🗄️ 缓存后端
该库包含一个分层元数据缓存,以避免对视频信息、已下载文件和播放列表进行冗余的 yt-dlp 子进程调用。该架构使用可选的 L1 内存层 (Moka)和可选的 L2 持久层,仅通过 Cargo 特性进行选择:
| 特性 | 后端 | 持久化 | 备注 |
|---|---|---|---|
cache-memory (默认) | 内存 Moka | ❌ 否 | 基于 TTL 的驱逐,支持异步 |
cache-json | 磁盘上的 JSON 文件 | ✅ 是 | 缓存目录中每个条目一个 .json 文件 |
cache-redb | 嵌入式 redb | ✅ 是 | 单文件,纯 Rust,ACID 事务 |
cache-redis | Redis | ✅ 是 | 分布式,通过 SETEX 实现原生 TTL |
可以同时编译多个持久化后端。当恰好启用一个时,它将
自动被选中。当启用多个时,必须显式设置 CacheConfig::persistent_backend;否则 CacheLayer::from_config 将在运行时返回一个 Error::AmbiguousCacheBackend。
cache-memory 特性(Moka L1)始终可以与任何持久化后端组合,以实现
分层 L1 + L2 配置。
默认(内存 Moka) — 无持久化,基于 TTL 的驱逐,适用于短生命周期进程:
[dependencies]
yt-dlp = { version = "2.8.1", features = ["cache-memory"] }
JSON — 持久化,基于文件系统,无额外依赖:
[dependencies]
yt-dlp = { version = "2.8.1", features = ["cache-json"] }
Redb — 嵌入式,单文件,符合 ACID,非常适合桌面/服务器应用:
[dependencies]
yt-dlp = { version = "2.8.1", features = ["cache-redb"] }
Redis — 分布式,适用于多节点或云部署:
[dependencies]
yt-dlp = { version = "2.8.1", features = ["cache-redis"] }
分层(Moka L1 + 持久化 L2) — 兼得两者之长:
[dependencies]
yt-dlp = { version = "2.8.1", features = ["cache-memory", "cache-redb"] }
编译了多个后端 — 在运行时通过 CacheConfig::persistent_backend 选择一个:
[dependencies]
yt-dlp = { version = "2.8.1", features = ["cache-memory", "cache-json", "cache-redb"] }
use yt_dlp::prelude::*;
let config = CacheConfig::builder()
.cache_dir("cache")
.persistent_backend(PersistentBackendKind::Redb) // required when multiple compiled in
.build();
CDN URL 过期与缓存失效
当 Video 被缓存时,其流格式 URL 的有效期约为 6 小时(YouTube CDN 生命周期)。该库通过每个 Format 上的 available_at 字段自动跟踪此状态。
每次调用 fetch_video_infos 时,缓存都会检查格式 URL 是否仍然有效。如果已过期,缓存条目将被静默失效,并重新获取视频——因此您绝不会使用过期的 CDN URL 进行下载。配置的 TTL(默认 24 小时)作为上限;有效 TTL 为 min(configured_ttl, cdn_url_lifetime)。
此行为是透明的,无需对您的代码进行任何更改。您可以自行检查过期时间:
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);
}
🔍 可观测性与追踪
此 crate 始终包含
tracing crate。该库在其内部操作(下载、缓存查找、子进程执行等)中发出 debug 和 trace span 事件。
⚠️ 重要: 在没有配置 subscriber 的情况下,tracing 宏是纯空操作。如果您不添加一个,则运行时开销为零。
要捕获日志,请在您的应用程序中添加一个 subscriber:
[dependencies]
tracing-subscriber = "0.3"
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");
有关更高级的配置(JSON 输出、日志级别、目标等),请参阅 tracing-subscriber 文档。
📖 文档
文档可在 docs.rs 上查阅。
🏗️ 多提取器架构
本库现在通过灵活的提取器系统支持从 1,800+ 个网站 下载:
Downloader- 通过提取器支持所有网站的通用客户端extractor::Youtube- 高度优化的 YouTube 提取器,具有平台特定功能:- 播放器客户端选择(Android、iOS、Web、TvEmbedded)以绕过限制
- 格式预设(Best、Premium、High、Medium、Low、AudioOnly、ModernCodecs)
- YouTube 特定方法:
search()、fetch_channel()、fetch_user()、fetch_playlist_paginated()
extractor::Generic- 支持身份验证的其他所有网站的通用提取器
🧩 使用模式
- 🎬️ 针对 YouTube 的优化:
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(())
}
- 🌐 针对任意网站(YouTube、Vimeo、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, "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(())
}
📚 示例
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");
// Create fetcher and install binaries
let downloader = Downloader::with_new_binaries(
executables_dir,
output_dir
).await?.build().await?;
Ok(())
}
- 📦 仅安装
yt-dlp二进制文件:
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(())
}
- 📦 仅安装
ffmpeg二进制文件:
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(())
}
- 🔄 更新
yt-dlp二进制文件:
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(())
}
- 📥 获取视频(含音频)并下载:
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(())
}
- 📁 将视频下载到特定路径(忽略
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?;
// Download to an absolute path — the file is written directly to the given path,
// bypassing the configured output_dir.
let path = PathBuf::from("/Users/me/Videos/my-video.mp4");
let video_path = downloader.download_video_to_path(&video, path).await?;
Ok(())
}
- ✨ 使用流畅 API 并自定义质量偏好:
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?;
// Use the fluent download builder API
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(())
}
- 🎬 获取视频(不含音频)并下载:
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(())
}
- 🎵 获取音频并下载:
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(())
}
- 📜 获取特定格式并下载:
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(())
}
- ⚙️ 将音频和视频文件合并为单个文件:
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(())
}
- 📸 获取缩略图并下载:
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(())
}
- 🖼️ 选择最低分辨率的缩略图并下载:
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?;
// Best thumbnail by area (width × height)
if let Some(thumb) = video.best_thumbnail() {
println!("Best thumbnail: {} — {:?}", thumb.url, thumb.resolution);
}
// Smallest thumbnail that is at least 1280×720
if let Some(thumb) = video.thumbnail_for_size(1280, 720) {
println!("HD thumbnail: {}", thumb.url);
}
Ok(())
}
- 📝 下载字幕或自动生成的字幕:
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?;
// Check available languages (merges subtitles + automatic captions)
let langs = downloader.list_subtitle_languages(&video);
println!("Available languages: {:?}", langs);
// Download French subtitles (falls back to automatic captions if no manual ones)
let sub_path = downloader.download_subtitle(&video, "fr", "subtitles.srt", true).await?;
// Download all available subtitles/captions
let paths = downloader.download_all_subtitles(&video, "subtitles/", true).await?;
Ok(())
}
- 🎞️ 下载故事板预览帧:
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?;
// Download the best (highest resolution) storyboard into a directory
let frames = downloader
.download_storyboard(&video, StoryboardQuality::Best, "storyboard/")
.await?;
println!("Downloaded {} MHTML fragment(s)", frames.len());
// Or pick a specific storyboard format directly
if let Some(format) = video.best_storyboard_format() {
let frames = downloader.download_storyboard_format(format, "storyboard/").await?;
}
Ok(())
}
- 📥 使用下载管理器和优先级进行下载:
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>> {
// Custom download manager configuration using the typed builder
let config = ManagerConfig::builder()
.max_concurrent_downloads(5) // Maximum 5 concurrent downloads
.segment_size(1024 * 1024 * 10) // 10 MB per segment
.parallel_segments(8) // 8 parallel segments per download
.retry_attempts(5) // 5 retry attempts on failure
.max_buffer_size(1024 * 1024 * 20) // 20 MB maximum buffer
.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);
// Create a fetcher with custom configuration
let downloader = Downloader::with_download_manager_config(libraries, output_dir, config)
.build()
.await?;
// Download a video with high priority
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?;
// Wait for download completion
let status = downloader.wait_for_download(download_id).await;
println!("Final download status: {:?}", status);
Ok(())
}
- 📊 带进度跟踪的下载:
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?;
// Download with progress callback
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?;
// Wait for download completion
downloader.wait_for_download(download_id).await;
Ok(())
}
- 🛑 取消下载:
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?;
// Start a download
let download_id = downloader.download_video_with_priority(
&video,
"video-to-cancel.mp4",
None
).await?;
// Check status
let status = downloader.get_download_status(download_id).await;
println!("Download status: {:?}", status);
// Cancel the download
let canceled = downloader.cancel_download(download_id).await;
println!("Download canceled: {}", canceled);
Ok(())
}
🎛️ 格式选择
该库提供了一个强大的格式选择系统,允许您根据特定的质量和编解码器偏好下载视频和音频。
🎬 视频质量选项
VideoQuality::Best- 选择可用的最高质量视频格式VideoQuality::High- 目标为 1080p 分辨率VideoQuality::Medium- 目标为 720p 分辨率VideoQuality::Low- 目标为 480p 分辨率VideoQuality::Worst- 选择可用的最低质量视频格式VideoQuality::CustomHeight(u32)- 目标为特定高度(例如,CustomHeight(1440)对应 1440p)VideoQuality::CustomWidth(u32)- 目标为特定宽度(例如,CustomWidth(1920)对应 1920px 宽度)
🎵 音频质量选项
AudioQuality::Best- 选择可用的最高质量音频格式AudioQuality::High- 目标为 192kbps 比特率AudioQuality::Medium- 目标为 128kbps 比特率AudioQuality::Low- 目标为 96kbps 比特率AudioQuality::Worst- 选择可用的最低质量音频格式AudioQuality::CustomBitrate(u32)- 目标为特定 kbps 比特率(例如,CustomBitrate(256)对应 256kbps)
🎞️ 编解码器偏好
📹 视频编解码器
VideoCodecPreference::VP9- 优先使用 VP9 编解码器VideoCodecPreference::AVC1- 优先使用 AVC1/H.264 编解码器VideoCodecPreference::AV1- 优先使用 AV01/AV1 编解码器VideoCodecPreference::Custom(String)- 优先使用自定义编解码器VideoCodecPreference::Any- 无编解码器偏好
🔊 音频编解码器
AudioCodecPreference::Opus- 优先使用 Opus 编解码器AudioCodecPreference::AAC- 优先使用 AAC 编解码器AudioCodecPreference::MP3- 优先使用 MP3 编解码器AudioCodecPreference::Custom(String)- 优先使用自定义编解码器AudioCodecPreference::Any- 无编解码器偏好
🧪 示例:根据质量和编解码器偏好下载
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?;
// Download a high quality video with VP9 codec and high quality audio with Opus codec
let video_path = downloader.download_video_with_quality(
&video,
"complete-video.mp4",
VideoQuality::High,
VideoCodecPreference::VP9,
AudioQuality::High,
AudioCodecPreference::Opus
).await?;
// Download just the video stream with medium quality and AVC1 codec
let video_stream_path = downloader.download_video_stream_with_quality(
&video,
"video-only.mp4",
VideoQuality::Medium,
VideoCodecPreference::AVC1
).await?;
// Download just the audio stream with high quality and AAC codec
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(())
}
📋 元数据
该项目支持以多种格式自动为下载的文件添加元数据:
- MP3:标题、艺术家、注释、流派(来自标签)、发行年份
- M4A:标题、艺术家、注释、流派(来自标签)、发行年份
- MP4:所有基本元数据,以及技术信息(分辨率、FPS、视频编解码器、视频比特率、音频编解码器、音频比特率、音频声道、采样率)
- WebM:所有基本元数据(通过 Matroska 格式),以及与 MP4 相同的技术信息
- FLAC:标题、艺术家、专辑、流派、日期、描述(通过 lofty 的 Vorbis 注释),缩略图嵌入
- OGG/Opus:标题、艺术家、专辑、流派、日期、描述(通过 lofty 的 Vorbis 注释)
- WAV:标题、艺术家、专辑、流派(通过 lofty 的 RIFF INFO)
- AAC:标题、艺术家、专辑、流派、日期(通过 lofty 的 ID3v2)
- AIFF:标题、艺术家、专辑、流派、日期(通过 lofty 的 ID3v2)
- AVI/TS/FLV:通过 FFmpeg 回退机制添加基本元数据
元数据会在下载过程中自动添加,无需用户执行任何额外操作。
🧠 智能元数据管理
系统会根据文件类型和预期用途智能地管理元数据的添加:
- 对于独立文件(音频或音频+视频),元数据会在下载过程中立即应用。
- 对于稍后合并的独立音频和视频流,元数据不会应用于单个文件,以避免冗余工作。
- 当使用
combine_audio_and_video()合并音频和视频流时,完整的元数据会应用于最终文件,包括来自两个流的信息。
这种优化方法确保最终文件中始终包含元数据,同时避免对临时文件进行不必要的处理。
📖 章节
视频可能包含将内容划分为逻辑片段的章节。该库提供了对章节信息的便捷访问,并自动将章节嵌入到下载的视频文件中(MP4/MKV/WebM):
- 📖 访问视频章节:
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?;
// Check if video has chapters
if video.has_chapters() {
println!("Video has {} chapters", video.get_chapters().len());
// Iterate over all chapters
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(())
}
- 🕒 查找特定时间戳处的章节:
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?;
// Find chapter at 120 seconds (2 minutes)
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(())
}
注意:当使用 download_video() 或 download_video_from_url() 下载视频时,章节会自动嵌入到视频文件的元数据中。VLC、MPV 等媒体播放器将能够使用章节进行导航!
🔥 热力图
热力图数据(也称为“最常播放”片段)显示了视频不同部分的观众参与度。此功能允许您识别哪些片段最受欢迎:
- 🔥 访问热力图数据:
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?;
// Check if video has heatmap data
if video.has_heatmap() {
if let Some(heatmap) = video.get_heatmap() {
println!("Video has {} heatmap segments", heatmap.points().len());
// Find the most replayed segment
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(())
}
- 📊 按阈值分析参与度:
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() {
// Get segments with high engagement (> 0.7)
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
);
}
// Get engagement at specific timestamp
if let Some(point) = heatmap.get_point_at_time(120.0) {
println!(
"Engagement at 2:00 is {:.2}",
point.value
);
}
}
Ok(())
}
📝 字幕
该库提供了全面的字幕支持,包括下载、语言选择以及将字幕嵌入到视频中:
- 📋 列出可用的字幕语言:
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?;
// List all available subtitle languages
let languages = downloader.list_subtitle_languages(&video);
println!("Available subtitle languages: {:?}", languages);
// Check if specific language is available
if downloader.has_subtitle_language(&video, "en") {
println!("English subtitles are available");
}
Ok(())
}
- 📥 下载特定字幕:
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?;
// Download English subtitles
let subtitle_path = downloader
.download_subtitle(&video, "en", "subtitle_en.srt", true)
.await?;
println!("Subtitle downloaded to: {:?}", subtitle_path);
Ok(())
}
- 📥 下载所有可用字幕:
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?;
// Download all available subtitles
let subtitle_paths = downloader
.download_all_subtitles(&video, &output_dir, true)
.await?;
println!("Downloaded {} subtitle files", subtitle_paths.len());
Ok(())
}
- 🎬 将字幕嵌入视频:
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?;
// Download video
let video_path = downloader.download_video(&video, "video.mp4").await?;
// Download subtitles
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?;
// Embed subtitles into video
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(())
}
- 🔄 处理自动字幕:
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?;
// Iterate over subtitles and filter automatic ones
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()
);
}
}
}
// Convert automatic captions to Subtitle struct
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(())
}
📂 播放列表
该库提供完整的播放列表支持,包括获取播放列表元数据以及使用各种选择选项下载视频:
- 📋 获取播放列表信息:
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"));
// List all videos in the playlist
for entry in &playlist.entries {
println!(
"[{}] {} ({})",
entry.index.unwrap_or(0),
entry.title,
entry.id
);
}
Ok(())
}
- 📥 下载整个播放列表:
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?;
// Download all videos with a pattern
// Use %(playlist_index)s for index, %(title)s for title, %(id)s for video ID
let video_paths = downloader
.download_playlist(&playlist, "%(playlist_index)s - %(title)s.mp4")
.await?;
println!("Downloaded {} videos", video_paths.len());
Ok(())
}
- 🎯 按索引下载特定视频:
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?;
// Download specific videos by index (0-based)
let indices = vec![0, 2, 5, 10]; // Videos at positions 1, 3, 6, and 11
let video_paths = downloader
.download_playlist_items(&playlist, &indices, "%(playlist_index)s - %(title)s.mp4")
.await?;
println!("Downloaded {} specific videos", video_paths.len());
Ok(())
}
- 📊 下载视频范围:
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?;
// Download videos 5-15 (0-based, inclusive)
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(())
}
- 🔍 过滤和分析播放列表:
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?;
// Check if playlist is complete
if playlist.is_complete() {
println!("All playlist videos have been fetched");
}
// Get only available videos
let available = playlist.available_entries();
println!("Available videos: {}/{}", available.len(), playlist.entry_count());
// Get specific entry
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);
}
}
// Get entries in a range
let range = playlist.get_entries_in_range(0, 10);
println!("First 11 videos: {}", range.len());
Ok(())
}
🔔 事件、钩子与 Webhooks
该库提供了一个全面的事件系统,用于监控下载生命周期,并通过 Rust 钩子或 HTTP webhooks 对事件做出响应。
⚡ 事件系统
所有下载操作都会发出事件,您可以订阅这些事件:
- 📡 订阅事件流:
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(())
}
✉️ 可用事件
该库发出 22 种不同的事件类型,涵盖整个下载生命周期:
下载生命周期:
VideoFetched- 已获取视频元数据DownloadQueued- 下载已加入队列DownloadStarted- 下载开始DownloadProgress- 进度更新(已下载字节数、速度、预计完成时间)DownloadPaused/DownloadResumed- 暂停/恢复事件DownloadCompleted- 下载成功完成DownloadFailed- 下载因错误而失败DownloadCanceled- 下载已取消
格式与元数据:
FormatSelected- 已选择视频/音频格式MetadataApplied- 已写入元数据标签ChaptersEmbedded- 已为文件添加章节
后处理:
PostProcessStarted/PostProcessCompleted/PostProcessFailed- FFmpeg 操作
播放列表操作:
PlaylistFetched- 已获取播放列表元数据PlaylistItemStarted/PlaylistItemCompleted/PlaylistItemFailed- 每个项目的事件PlaylistCompleted- 整个播放列表已完成
高级功能:
SegmentStarted/SegmentCompleted- 并行分段下载
🪝 Rust 钩子(功能:hooks)
注册异步函数,以便在事件发生时被调用:
[dependencies]
yt-dlp = { version = "2.8.1", features = ["hooks"] }
- 🎣 注册下载事件的钩子:
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 {
// Only receive terminal events (completed, failed, canceled)
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(())
}
钩子特性:
- 异步执行
- 事件过滤(按类型、下载 ID、自定义谓词)
- 并行或顺序执行
- 自动超时保护(30s)
- 错误隔离(钩子失败不会停止下载)
🔍 事件过滤器
use yt_dlp::events::EventFilter;
// Only completed downloads
EventFilter::only_completed();
// Only failed downloads
EventFilter::only_failed();
// Progress updates only
EventFilter::only_progress();
// Exclude progress events
EventFilter::all().exclude_progress();
// Specific download ID
EventFilter::download_id(123);
// Terminal events (completed, failed, canceled)
EventFilter::only_terminal();
// Chain filters
EventFilter::download_id(123).and_then(|e| e.is_terminal());
// Custom filter
EventFilter::all().and_then(|event| {
// Your custom logic
true
});
📡 HTTP Webhooks (Feature: webhooks)
向外部 HTTP 端点发送事件,并支持自动重试:
[dependencies]
yt-dlp = { version = "2.8.1", features = ["webhooks"] }
- 📡 注册 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 功能:
- HTTP POST/PUT/PATCH 方法
- 自定义请求头(认证等)
- 事件过滤(与 hooks 相同)
- 自动重试,采用指数退避策略(默认 3 次)
- 可配置的超时时间
- 包含事件数据的 JSON 负载
- 环境变量配置
环境变量:
export YTDLP_WEBHOOK_URL="https://example.com/webhook"
export YTDLP_WEBHOOK_METHOD="POST" # Optional, default: POST
export YTDLP_WEBHOOK_TIMEOUT="10" # Optional, default: 10 seconds
- 🔧 从环境变量加载 Webhook:
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?;
// Load webhook from environment variables
if let Some(webhook) = WebhookConfig::from_env() {
downloader.register_webhook(webhook).await;
}
Ok(())
}
Webhook 负载:
{
"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
}
}
♻️ 重试策略
- ♻️ 配置重试策略:
use yt_dlp::events::RetryStrategy;
use std::time::Duration;
// Exponential backoff (default)
let strategy = RetryStrategy::exponential(
3, // max attempts
Duration::from_secs(1), // initial delay
Duration::from_secs(30) // max delay
);
// Linear backoff
let strategy = RetryStrategy::linear(
3, // max attempts
Duration::from_secs(5) // fixed delay
);
// No retries
let strategy = RetryStrategy::none();
🔗 组合使用 Hooks 和 Webhooks
同时使用 hooks 和 webhooks:
- 🔗 同时使用 hooks 和 webhooks:
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?;
// Register Rust hook for immediate in-process handling
downloader.register_hook(MyLocalHook).await;
// Register webhook for external notifications
let webhook = WebhookConfig::new("https://example.com/webhook")
.with_filter(EventFilter::only_completed());
downloader.register_webhook(webhook).await;
// Start downloads - both hooks and webhooks will receive events
let video = downloader.fetch_video_infos("https://youtube.com/watch?v=...".to_string()).await?;
downloader.download_video(&video, "video.mp4").await?;
Ok(())
}
📊 统计与分析(功能:statistics)
启用实时、聚合指标,无需手动记录:
[dependencies]
yt-dlp = { version = "2.8.1", features = ["statistics"] }
The StatisticsTracker 在后台任务中订阅内部事件总线,并持续更新运行计数器。随时调用 snapshot() 以获取所有指标的原子视图:
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?;
// Perform some downloads and fetches ...
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(())
}
该快照暴露了:
downloads— 尝试次数、已完成、失败、已取消、总字节数、平均速度、峰值速度、成功率fetches— 尝试次数、成功、失败、平均时长、成功率(视频 + 播放列表获取)post_processing— 尝试次数、成功、失败、平均时长playlists— 已获取的播放列表、失败、单项成功率recent_downloads— 已完成下载的有界历史窗口,包含每次下载的详细信息
🚀 高级功能
🔐 代理支持
该库支持 HTTP、HTTPS 和 SOCKS5 代理,适用于 yt-dlp 和 reqwest 下载:
- 🔐 配置带身份验证的代理:
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")
);
// Configure proxy with authentication
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()]);
// Build Downloader with proxy
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?;
// All downloads (video, audio, thumbnails) will use the proxy
downloader.download_video(&video, "video.mp4").await?;
Ok(())
}
支持的代理类型:
- HTTP/HTTPS:标准 HTTP 代理
- SOCKS5:提供更多灵活性的 SOCKS5 代理
- 身份验证:用户名/密码身份验证
- 无代理列表:将特定域名排除在代理之外
🔑 身份验证与 Cookie
许多平台(YouTube 机器人防护、Twitch、年龄限制内容等)需要身份验证。 该库支持三种身份验证模式,这些模式会自动传播到元数据提取和 所有下载操作中。
Cookie 文件(Netscape 格式)
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"),
);
// Export cookies from your browser with a browser extension (e.g. "Get cookies.txt LOCALLY")
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(())
}
浏览器 Cookie
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"),
);
// yt-dlp will read cookies directly from your browser's cookie store
let downloader = Downloader::builder(libraries, PathBuf::from("output"))
.with_cookies_from_browser("chrome") // or "firefox", "safari", "edge", …
.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(())
}
运行时(构建后)
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?;
// Apply cookies after build — propagates to both extractors and download args
downloader.set_cookies("cookies.txt");
// or: downloader.set_cookies_from_browser("chrome");
// or: downloader.set_netrc();
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(())
}
✂️ 片段提取与章节分割
该库支持从视频中下载特定时间范围或特定章节,而无需获取整个文件。定位由 media-seek 处理——这是一个纯 Rust 容器索引解析器,可将时间戳转换为 HTTP 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?;
// Download seconds [60, 120] only — no re-encoding
let clip_path = downloader
.download(&video, "clip.mp4")
.time_range(60.0, 120.0)?
.execute()
.await?;
Ok(())
}
- 📖 按索引范围下载特定章节:
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?;
// Download chapters 0 through 2 (inclusive)
let clip_path = downloader
.download(&video, "chapters.mp4")
.chapters(0, 2)?
.execute()
.await?;
Ok(())
}
- 🔪 将已下载的视频按章节分割为单个文件:
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?;
// Download and split into one file per chapter — FFmpeg stream copy, no re-encoding
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-recording)
使用纯 Rust reqwest 引擎或 FFmpeg 作为后备方案来录制直播流。
在您的 Cargo.toml 中启用该功能:
[dependencies]
yt-dlp = { version = "2.8.1", features = ["live-recording"] }
📥 基础实时录制(reqwest 引擎)
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?;
// Record for 1 hour maximum
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 回退引擎
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(())
}
实现细节:
- Reqwest 引擎(默认):纯 Rust HLS 片段获取器。轮询媒体播放列表,下载新片段,并按顺序写入。零拷贝
bytes::Bytes,进度事件以 50 毫秒为间隔进行节流。 - FFmpeg 引擎(回退):生成
ffmpeg -i <url> -c copy <output>。通过 stdin 优雅停止q。适用于加密流或复杂的 HLS 功能。 - 录制在取消令牌、
#EXT-X-ENDLIST或最大时长时停止。 - 录制事件:
LiveRecordingStarted、LiveRecordingProgress、LiveRecordingStopped、LiveRecordingFailed。 - 流式传输事件:
LiveStreamStarted、LiveStreamProgress、LiveStreamStopped、LiveStreamFailed。
📡 实时片段流式传输(功能:live-streaming)
在您的 Cargo.toml 中启用该功能:
[dependencies]
yt-dlp = { version = "2.8.1", features = ["live-streaming"] }
use yt_dlp::Downloader;
use yt_dlp::client::deps::Libraries;
use std::path::PathBuf;
use tokio_stream::StreamExt;
#[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 mut stream = downloader.stream_live(&video)
.execute()
.await?;
while let Some(fragment) = stream.next().await {
let fragment = fragment?;
println!("Fragment {} bytes", fragment.data.len());
}
Ok(())
}
🎨 后期处理选项
使用 FFmpeg 对视频应用高级后期处理:
🔧 基础编解码器转换
- 🔧 转换视频编解码器和比特率:
use yt_dlp::Downloader;
use yt_dlp::download::{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?;
// Configure post-processing
let config = PostProcessConfig::new()
.with_video_codec(VideoCodec::H264)
.with_audio_codec(AudioCodec::AAC)
.with_video_bitrate("2M")
.with_audio_bitrate("192k");
// Apply to existing video
downloader.postprocess_video("input.mp4", "output.mp4", config).await?;
Ok(())
}
🎛️ 使用滤镜的高级后期处理
- 🎛️ 应用分辨率、帧率和视觉滤镜:
use yt_dlp::Downloader;
use yt_dlp::download::{
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?;
// Advanced configuration with filters
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(())
}
📋 可用的后期处理选项
视频编解码器:
- H.264 (libx264) - 兼容性最高
- H.265 (libx265) - 压缩率更好
- VP9 (libvpx-vp9) - 开放格式
- AV1 (libaom-av1) - 下一代编解码器
- Copy - 不重新编码
音频编解码器:
- AAC - 高质量,广泛支持
- MP3 (libmp3lame) - 通用兼容性
- Opus - 最佳质量/大小比
- Vorbis - 开放格式
- Copy - 不重新编码
分辨率:
- UHD8K (7680x4320)
- UHD4K (3840x2160)
- QHD (2560x1440)
- FullHD (1920x1080)
- HD (1280x720)
- SD (854x480)
- Low (640x360)
- Custom { width, height }
编码预设:
- UltraFast, SuperFast, VeryFast, Fast
- Medium (平衡)
- Slow, Slower, VerySlow (最佳质量)
视频滤镜:
- Crop:
Crop { width, height, x, y } - Rotate:
Rotate { angle }(以度为单位) - Watermark:
Watermark { path, position } - Brightness:
Brightness { value }(-1.0 到 1.0) - Contrast:
Contrast { value }(0.0 到 4.0) - Saturation:
Saturation { value }(0.0 到 3.0) - Blur:
Blur { radius } - FlipHorizontal, FlipVertical
- Denoise, Sharpen
- Custom:
Custom { filter }- 任意 FFmpeg 滤镜字符串
⚡ 速度配置
该库包含一个智能速度优化系统,可根据您的互联网连接速度自动配置下载参数。此功能显著改善