ITADN
boul2gom/yt-dlp
README.md
以下内容由 AI 翻译,如有问题请点此提交 issue 反馈

🎬️ 一个用于视频下载的 Rust 库(支持自动安装依赖)

该库是 yt-dlp 命令行工具的 Rust 异步封装,这是一个功能丰富的音频/视频下载器,支持 YouTube、Vimeo、TikTok、Instagram、Twitter 等。
该 crate 旨在从各种网站下载音频和视频。 您无需关心依赖项,yt-dlp 和 ffmpeg 将自动下载。

⚠️ 该项目仍在开发中,因此如果您遇到任何 bug 或有任何功能请求,请提交 issue 或 discussion。


Develop CI Release Downloads Ask DeepWiki

Discussions Issues Pull%20requests

License Stars Forks

Statistics

Codecov Quality Scorecard


💭️ 为什么要使用外部 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 - 启用 reqwest crate 中的 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-redisRedis✅ 是分布式,通过 SETEX 实现原生 TTL

可以同时编译多个持久化后端。当恰好启用一个时,它将 自动被选中。当启用多个时,必须显式设置 CacheConfig::persistent_backend;否则 CacheLayer::from_config 将在运行时返回一个 Error::AmbiguousCacheBackendcache-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 tracing crate。该库在其内部操作(下载、缓存查找、子进程执行等)中发出 debugtrace 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-dlpreqwest 下载:

  • 🔐 配置带身份验证的代理:
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 代理
  • 身份验证:用户名/密码身份验证
  • 无代理列表:将特定域名排除在代理之外

许多平台(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 或最大时长时停止。
  • 录制事件:LiveRecordingStartedLiveRecordingProgressLiveRecordingStoppedLiveRecordingFailed
  • 流式传输事件:LiveStreamStartedLiveStreamProgressLiveStreamStoppedLiveStreamFailed

📡 实时片段流式传输(功能: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 滤镜字符串

⚡ 速度配置

该库包含一个智能速度优化系统,可根据您的互联网连接速度自动配置下载参数。此功能显著改善