yt_dlp/lib.rs
1#![doc = include_str!("../README.md")]
2
3use crate::client::deps::{Libraries, LibraryInstaller};
4use crate::download::manager::ManagerConfig;
5use crate::error::{Error, Result};
6use crate::executor::Executor;
7use crate::utils::fs;
8#[cfg(feature = "cache")]
9use cache::{DownloadCache, PlaylistCache, VideoCache};
10use std::fmt::{self, Display};
11use std::path::{Path, PathBuf};
12use std::sync::Arc;
13use std::time::Duration;
14
15// Core modules
16#[cfg(feature = "cache")]
17pub mod cache;
18pub mod error;
19pub mod executor;
20pub mod metadata;
21pub use metadata::PlaylistMetadata;
22pub mod model;
23pub mod utils;
24
25// Architecture modules
26pub mod client;
27pub mod download;
28
29// Convenience modules
30pub mod macros;
31pub mod prelude;
32
33// Re-export of common traits to facilitate their use
34pub use model::utils::{AllTraits, CommonTraits};
35
36// Re-export main types for easy access
37pub use client::{DownloadBuilder, YoutubeBuilder};
38pub use download::{DownloadManager, DownloadPriority, DownloadStatus};
39
40/// A YouTube video fetcher that uses yt-dlp to fetch video information and download it.
41///
42/// The 'yt-dlp' executable and 'ffmpeg' build can be installed with this fetcher.
43///
44/// The video can be downloaded with or without its audio, and the audio and video can be combined.
45/// The video thumbnail can also be downloaded.
46///
47/// The major implementations of this struct are located in the 'fetcher' module.
48///
49/// # Examples
50///
51/// ```rust, no_run
52/// # use yt_dlp::Youtube;
53/// # use std::path::PathBuf;
54/// # use yt_dlp::client::deps::Libraries;
55/// # #[tokio::main]
56/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
57/// let libraries_dir = PathBuf::from("libs");
58/// let output_dir = PathBuf::from("output");
59///
60/// let youtube = libraries_dir.join("yt-dlp");
61/// let ffmpeg = libraries_dir.join("ffmpeg");
62///
63/// let libraries = Libraries::new(youtube, ffmpeg);
64/// let mut fetcher = Youtube::new(libraries, output_dir)?;
65///
66/// let url = String::from("https://www.youtube.com/watch?v=dQw4w9WgXcQ");
67/// let video = fetcher.fetch_video_infos(url).await?;
68/// println!("Video title: {}", video.title);
69///
70/// fetcher.download_video(&video, "video.mp4").await?;
71/// # Ok(())
72/// # }
73/// ```
74#[derive(Clone, Debug)]
75pub struct Youtube {
76 /// The required libraries.
77 pub libraries: Libraries,
78
79 /// The directory where the video (or formats) will be downloaded.
80 pub output_dir: PathBuf,
81 /// The arguments to pass to 'yt-dlp'.
82 pub args: Vec<String>,
83 /// The timeout for command execution.
84 pub timeout: Duration,
85 /// Optional proxy configuration for HTTP requests and yt-dlp.
86 pub proxy: Option<client::proxy::ProxyConfig>,
87 /// The cache for video metadata.
88 #[cfg(feature = "cache")]
89 pub cache: Option<Arc<cache::VideoCache>>,
90 /// The cache for downloaded files.
91 #[cfg(feature = "cache")]
92 pub download_cache: Option<Arc<cache::DownloadCache>>,
93 /// The cache for playlist metadata.
94 #[cfg(feature = "cache")]
95 pub playlist_cache: Option<Arc<cache::PlaylistCache>>,
96 /// The download manager for managing parallel downloads.
97 pub download_manager: Arc<DownloadManager>,
98 /// Cancellation token for graceful shutdown.
99 pub(crate) cancellation_token: tokio_util::sync::CancellationToken,
100}
101
102impl fmt::Display for Youtube {
103 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104 write!(
105 f,
106 "Youtube: output_dir={:?}, args={:?}, proxy={}",
107 self.output_dir,
108 self.args,
109 self.proxy.is_some()
110 )
111 }
112}
113
114impl Youtube {
115 /// Creates a new builder for constructing a Youtube instance with a fluent API.
116 ///
117 /// This is the recommended way to create a Youtube instance as it provides
118 /// a clean and intuitive interface for configuration.
119 ///
120 /// # Arguments
121 ///
122 /// * `libraries` - The required libraries (yt-dlp and ffmpeg paths)
123 /// * `output_dir` - The directory where videos will be downloaded
124 ///
125 /// # Examples
126 ///
127 /// ```rust,no_run
128 /// # use yt_dlp::Youtube;
129 /// # use yt_dlp::client::deps::Libraries;
130 /// # use std::path::PathBuf;
131 /// # #[tokio::main]
132 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
133 /// let libraries = Libraries::new("libs/yt-dlp", "libs/ffmpeg");
134 ///
135 /// let youtube = Youtube::builder(libraries, "output")
136 /// .with_timeout(std::time::Duration::from_secs(120))
137 /// .with_max_concurrent_downloads(4)
138 /// .build()
139 /// .await?;
140 /// # Ok(())
141 /// # }
142 /// ```
143 pub fn builder(libraries: Libraries, output_dir: impl Into<PathBuf>) -> YoutubeBuilder {
144 YoutubeBuilder::new(libraries, output_dir)
145 }
146
147 /// Creates a new download builder for downloading a video with custom quality and codec preferences.
148 ///
149 /// This provides a fluent API for configuring and executing downloads with
150 /// custom quality, codec preferences, priority, and progress tracking.
151 ///
152 /// # Arguments
153 ///
154 /// * `url` - The YouTube video URL to download
155 /// * `output` - The output filename for the downloaded video
156 ///
157 /// # Returns
158 ///
159 /// A `DownloadBuilder` instance that can be configured with various options
160 /// before calling `execute()` to start the download.
161 ///
162 /// # Examples
163 ///
164 /// ```rust,no_run
165 /// # use yt_dlp::Youtube;
166 /// # use yt_dlp::client::deps::Libraries;
167 /// # use yt_dlp::model::selector::{VideoQuality, AudioQuality, VideoCodecPreference};
168 /// # use std::path::PathBuf;
169 /// # #[tokio::main]
170 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
171 /// # let libraries = Libraries::new("libs/yt-dlp", "libs/ffmpeg");
172 /// # let fetcher = Youtube::new(libraries, "output")?;
173 /// let url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ";
174 ///
175 /// let video_path = fetcher.download(url, "my-video.mp4")
176 /// .video_quality(VideoQuality::Q1080p)
177 /// .video_codec(VideoCodecPreference::H264)
178 /// .audio_quality(AudioQuality::Best)
179 /// .execute()
180 /// .await?;
181 /// # Ok(())
182 /// # }
183 /// ```
184 pub fn download(
185 &self,
186 url: impl Into<String>,
187 output: impl Into<PathBuf>,
188 ) -> client::DownloadBuilder<'_> {
189 client::DownloadBuilder::new(self, url, output)
190 }
191
192 /// Creates a new YouTube fetcher with the given yt-dlp executable, ffmpeg executable and video URL.
193 /// The output directory can be void if you only want to fetch the video information.
194 ///
195 /// # Arguments
196 ///
197 /// * `libraries` - The required libraries.
198 /// * `output_dir` - The directory where the video will be downloaded.
199 ///
200 /// # Errors
201 ///
202 /// This function will return an error if the parent directories of the executables and output directory could not be created.
203 ///
204 /// # Examples
205 ///
206 /// ```rust, no_run
207 /// # use yt_dlp::Youtube;
208 /// # use std::path::PathBuf;
209 /// # use yt_dlp::client::deps::Libraries;
210 /// # #[tokio::main]
211 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
212 /// let libraries_dir = PathBuf::from("libs");
213 /// let output_dir = PathBuf::from("output");
214 ///
215 /// let youtube = libraries_dir.join("yt-dlp");
216 /// let ffmpeg = libraries_dir.join("ffmpeg");
217 ///
218 /// let libraries = Libraries::new(youtube, ffmpeg);
219 /// let fetcher = Youtube::new(libraries, output_dir)?;
220 /// # Ok(())
221 /// # }
222 /// ```
223 pub async fn new(
224 libraries: Libraries,
225 output_dir: impl AsRef<Path> + std::fmt::Debug,
226 ) -> Result<Self> {
227 #[cfg(feature = "tracing")]
228 tracing::debug!("Creating a new video fetcher");
229
230 fs::create_parent_dir(&output_dir)?;
231
232 // Initialize cache in the output directory
233 let cache_dir = output_dir.as_ref().join("cache");
234 fs::create_parent_dir(&cache_dir)?;
235 #[cfg(feature = "cache")]
236 let cache = VideoCache::new(cache_dir.clone(), None).await?;
237 #[cfg(feature = "cache")]
238 let download_cache = DownloadCache::new(cache_dir.clone(), None).await?;
239 #[cfg(feature = "cache")]
240 let playlist_cache = PlaylistCache::new(cache_dir.join("playlists.db")).await?;
241
242 // Initialize download manager with default configuration
243 let download_manager = DownloadManager::new();
244
245 Ok(Self {
246 libraries,
247 output_dir: output_dir.as_ref().to_path_buf(),
248 args: Vec::new(),
249 timeout: Duration::from_secs(30),
250 proxy: None,
251 #[cfg(feature = "cache")]
252 cache: Some(Arc::new(cache)),
253 #[cfg(feature = "cache")]
254 download_cache: Some(Arc::new(download_cache)),
255 #[cfg(feature = "cache")]
256 playlist_cache: Some(Arc::new(playlist_cache)),
257 download_manager: Arc::new(download_manager),
258 cancellation_token: tokio_util::sync::CancellationToken::new(),
259 })
260 }
261
262 /// Creates a new YouTube fetcher with a custom download manager configuration.
263 ///
264 /// # Arguments
265 ///
266 /// * `libraries` - The required libraries.
267 /// * `output_dir` - The directory where the video will be downloaded.
268 /// * `download_manager_config` - The configuration for the download manager.
269 ///
270 /// # Errors
271 ///
272 /// This function will return an error if the parent directories of the executables and output directory could not be created.
273 pub async fn with_download_manager_config(
274 libraries: Libraries,
275 output_dir: impl AsRef<Path> + std::fmt::Debug,
276 download_manager_config: ManagerConfig,
277 ) -> Result<Self> {
278 #[cfg(feature = "tracing")]
279 tracing::debug!("Creating a new video fetcher with custom download manager config");
280
281 fs::create_parent_dir(&output_dir)?;
282
283 // Initialize cache in the output directory
284 let cache_dir = output_dir.as_ref().join("cache");
285 fs::create_parent_dir(&cache_dir)?;
286 #[cfg(feature = "cache")]
287 let cache = VideoCache::new(cache_dir.clone(), None).await?;
288 #[cfg(feature = "cache")]
289 let download_cache = DownloadCache::new(cache_dir.clone(), None).await?;
290 #[cfg(feature = "cache")]
291 let playlist_cache = PlaylistCache::new(cache_dir.join("playlists.db")).await?;
292
293 // Initialize download manager with custom configuration
294 let download_manager = DownloadManager::with_config(download_manager_config);
295
296 Ok(Self {
297 libraries,
298 output_dir: output_dir.as_ref().to_path_buf(),
299 args: Vec::new(),
300 timeout: Duration::from_secs(30),
301 proxy: None,
302 #[cfg(feature = "cache")]
303 cache: Some(Arc::new(cache)),
304 #[cfg(feature = "cache")]
305 download_cache: Some(Arc::new(download_cache)),
306 #[cfg(feature = "cache")]
307 playlist_cache: Some(Arc::new(playlist_cache)),
308 download_manager: Arc::new(download_manager),
309 cancellation_token: tokio_util::sync::CancellationToken::new(),
310 })
311 }
312
313 /// Creates a new YouTube fetcher, and installs the yt-dlp and ffmpeg binaries.
314 /// The output directory can be void if you only want to fetch the video information.
315 /// Be careful, this function may take a while to execute.
316 ///
317 /// # Arguments
318 ///
319 /// * `executables_dir` - The directory where the binaries will be installed.
320 /// * `output_dir` - The directory where the video will be downloaded.
321 ///
322 /// # Errors
323 ///
324 /// This function will return an error if the executables could not be installed.
325 ///
326 /// # Examples
327 ///
328 /// ```rust, no_run
329 /// # use yt_dlp::Youtube;
330 /// # use std::path::PathBuf;
331 /// # #[tokio::main]
332 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
333 /// let executables_dir = PathBuf::from("libs");
334 /// let output_dir = PathBuf::from("output");
335 ///
336 /// let fetcher = Youtube::with_new_binaries(executables_dir, output_dir).await?;
337 /// # Ok(())
338 /// # }
339 /// ```
340 pub async fn with_new_binaries(
341 executables_dir: impl AsRef<Path> + std::fmt::Debug + Send + Sync,
342 output_dir: impl AsRef<Path> + std::fmt::Debug + Send + Sync,
343 ) -> Result<Self> {
344 #[cfg(feature = "tracing")]
345 tracing::debug!("Creating a new video fetcher with binaries installation");
346
347 let installer = LibraryInstaller::new(executables_dir.as_ref().to_path_buf());
348
349 // Check if binaries already exist
350 let youtube_path = executables_dir
351 .as_ref()
352 .join(utils::find_executable("yt-dlp"));
353 let ffmpeg_path = executables_dir
354 .as_ref()
355 .join(utils::find_executable("ffmpeg"));
356
357 let youtube = if youtube_path.exists() {
358 youtube_path
359 } else {
360 installer.install_youtube(None).await?
361 };
362
363 let ffmpeg = if ffmpeg_path.exists() {
364 ffmpeg_path
365 } else {
366 installer.install_ffmpeg(None).await?
367 };
368
369 let libraries = Libraries::new(youtube, ffmpeg);
370 Self::new(libraries, output_dir).await
371 }
372
373 /// Sets the arguments to pass to yt-dlp.
374 ///
375 /// # Arguments
376 ///
377 /// * `args` - The arguments to pass to yt-dlp.
378 ///
379 /// # Examples
380 ///
381 /// ```rust, no_run
382 /// # use yt_dlp::Youtube;
383 /// # use std::path::PathBuf;
384 /// # use yt_dlp::client::deps::Libraries;
385 /// # #[tokio::main]
386 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
387 /// # let libraries_dir = PathBuf::from("libs");
388 /// # let output_dir = PathBuf::from("output");
389 /// # let youtube = libraries_dir.join("yt-dlp");
390 /// # let ffmpeg = libraries_dir.join("ffmpeg");
391 /// # let libraries = Libraries::new(youtube, ffmpeg);
392 /// let mut fetcher = Youtube::new(libraries, output_dir)?;
393 ///
394 /// let args = vec!["--no-progress".to_string()];
395 /// fetcher.with_args(args);
396 /// # Ok(())
397 /// # }
398 /// ```
399 pub fn with_args(&mut self, mut args: Vec<String>) -> &mut Self {
400 self.args.append(&mut args);
401 self
402 }
403
404 /// Sets the timeout for command execution.
405 ///
406 /// # Arguments
407 ///
408 /// * `timeout` - The timeout duration for command execution.
409 ///
410 /// # Examples
411 ///
412 /// ```rust, no_run
413 /// # use yt_dlp::Youtube;
414 /// # use std::path::PathBuf;
415 /// # use yt_dlp::client::deps::Libraries;
416 /// # use std::time::Duration;
417 /// # #[tokio::main]
418 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
419 /// # let libraries_dir = PathBuf::from("libs");
420 /// # let output_dir = PathBuf::from("output");
421 /// # let youtube = libraries_dir.join("yt-dlp");
422 /// # let ffmpeg = libraries_dir.join("ffmpeg");
423 /// # let libraries = Libraries::new(youtube, ffmpeg);
424 /// let mut fetcher = Youtube::new(libraries, output_dir)?;
425 ///
426 /// // Set a longer timeout for large videos
427 /// fetcher.with_timeout(Duration::from_secs(300));
428 /// # Ok(())
429 /// # }
430 /// ```
431 pub fn with_timeout(&mut self, timeout: Duration) -> &mut Self {
432 self.timeout = timeout;
433 self
434 }
435
436 /// Adds an argument to pass to yt-dlp.
437 ///
438 /// # Arguments
439 ///
440 /// * `arg` - The argument to pass to yt-dlp.
441 ///
442 /// # Examples
443 ///
444 /// ```rust, no_run
445 /// # use yt_dlp::Youtube;
446 /// # use std::path::PathBuf;
447 /// # use yt_dlp::client::deps::Libraries;
448 /// # #[tokio::main]
449 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
450 /// # let libraries_dir = PathBuf::from("libs");
451 /// # let output_dir = PathBuf::from("output");
452 /// # let youtube = libraries_dir.join("yt-dlp");
453 /// # let ffmpeg = libraries_dir.join("ffmpeg");
454 /// # let libraries = Libraries::new(youtube, ffmpeg);
455 /// let mut fetcher = Youtube::new(libraries, output_dir)?;
456 ///
457 /// fetcher.with_arg("--no-progress");
458 /// # Ok(())
459 /// # }
460 /// ```
461 pub fn with_arg(&mut self, arg: impl AsRef<str>) -> &mut Self {
462 self.args.push(arg.as_ref().to_string());
463 self
464 }
465
466 /// Updates the yt-dlp executable.
467 /// Be careful, this function may take a while to execute.
468 ///
469 /// # Errors
470 ///
471 /// This function will return an error if the yt-dlp executable could not be updated.
472 ///
473 /// # Examples
474 ///
475 /// ```rust, no_run
476 /// # use yt_dlp::Youtube;
477 /// # use std::path::PathBuf;
478 /// # use yt_dlp::client::deps::Libraries;
479 /// # #[tokio::main]
480 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
481 /// # let libraries_dir = PathBuf::from("libs");
482 /// # let output_dir = PathBuf::from("output");
483 /// # let youtube = libraries_dir.join("yt-dlp");
484 /// # let ffmpeg = libraries_dir.join("ffmpeg");
485 /// # let libraries = Libraries::new(youtube, ffmpeg);
486 /// let fetcher = Youtube::new(libraries, output_dir)?;
487 ///
488 /// fetcher.update_downloader().await?;
489 /// # Ok(())
490 /// # }
491 /// ```
492 pub async fn update_downloader(&self) -> Result<()> {
493 #[cfg(feature = "tracing")]
494 tracing::debug!("Updating the downloader");
495
496 let args = vec!["--update"];
497
498 let executor = Executor {
499 executable_path: self.libraries.youtube.clone(),
500 timeout: self.timeout,
501 args: utils::to_owned(args),
502 };
503
504 executor.execute().await?;
505 Ok(())
506 }
507
508 /// Combines the audio and video files into a single file.
509 /// Be careful, this function may take a while to execute.
510 ///
511 /// # Arguments
512 ///
513 /// * `audio_file` - The name of the audio file to combine.
514 /// * `video_file` - The name of the video file to combine.
515 /// * `output_file` - The name of the output file.
516 ///
517 /// # Errors
518 ///
519 /// This function will return an error if the audio and video files could not be combined.
520 ///
521 /// # Examples
522 ///
523 /// ```rust, no_run
524 /// # use yt_dlp::Youtube;
525 /// # use std::path::PathBuf;
526 /// # use yt_dlp::client::deps::Libraries;
527 /// # #[tokio::main]
528 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
529 /// # let libraries_dir = PathBuf::from("libs");
530 /// # let output_dir = PathBuf::from("output");
531 /// # let youtube = libraries_dir.join("yt-dlp");
532 /// # let ffmpeg = libraries_dir.join("ffmpeg");
533 /// # let libraries = Libraries::new(youtube, ffmpeg);
534 /// let fetcher = Youtube::new(libraries, output_dir)?;
535 ///
536 /// let url = String::from("https://www.youtube.com/watch?v=dQw4w9WgXcQ");
537 /// let video = fetcher.fetch_video_infos(url).await?;
538 ///
539 /// let audio_format = video.best_audio_format().unwrap();
540 /// let audio_path = fetcher.download_format(&audio_format, "audio-stream.mp3").await?;
541 ///
542 /// let video_format = video.worst_video_format().unwrap();
543 /// let format_path = fetcher.download_format(&video_format, "video-stream.mp4").await?;
544 ///
545 /// let output_path = fetcher.combine_audio_and_video("audio-stream.mp3", "video-stream.mp4", "my-output.mp4").await?;
546 /// # Ok(())
547 /// # }
548 /// ```
549 pub async fn combine_audio_and_video(
550 &self,
551 audio_file: impl AsRef<str> + std::fmt::Debug + Display,
552 video_file: impl AsRef<str> + std::fmt::Debug + Display,
553 output_file: impl AsRef<str> + std::fmt::Debug + Display,
554 ) -> Result<PathBuf> {
555 #[cfg(feature = "tracing")]
556 tracing::debug!(
557 "Combining audio and video files {} and {}, into {}",
558 audio_file,
559 video_file,
560 output_file
561 );
562
563 let audio_path = self.output_dir.join(audio_file.as_ref());
564 let video_path = self.output_dir.join(video_file.as_ref());
565 let output_path = self.output_dir.join(output_file.as_ref());
566
567 // Perform the combination with FFmpeg
568 self.execute_ffmpeg_combine(&audio_path, &video_path, &output_path)
569 .await?;
570
571 // Add metadata to the combined file, propagating potential errors
572 self.add_metadata_to_combined_file(&audio_path, &video_path, &output_path)
573 .await?;
574
575 Ok(output_path)
576 }
577
578 /// Executes the FFmpeg command to combine audio and video files
579 async fn execute_ffmpeg_combine(
580 &self,
581 audio_path: impl AsRef<Path>,
582 video_path: impl AsRef<Path>,
583 output_path: impl AsRef<Path>,
584 ) -> Result<()> {
585 let audio = audio_path
586 .as_ref()
587 .to_str()
588 .ok_or(Error::Unknown("Invalid audio path".to_string()))?;
589 let video = video_path
590 .as_ref()
591 .to_str()
592 .ok_or(Error::Unknown("Invalid video path".to_string()))?;
593 let output = output_path
594 .as_ref()
595 .to_str()
596 .ok_or(Error::Unknown("Invalid output path".to_string()))?;
597
598 let args = vec![
599 "-i", audio, "-i", video, "-c:v", "copy", "-c:a", "aac", output,
600 ];
601
602 let executor = Executor {
603 executable_path: self.libraries.ffmpeg.clone(),
604 timeout: self.timeout,
605 args: utils::to_owned(args),
606 };
607
608 executor.execute().await?;
609 Ok(())
610 }
611
612 /// Adds metadata to the combined file by extracting the video ID and
613 /// retrieving information from the original audio and video formats
614 async fn add_metadata_to_combined_file(
615 &self,
616 audio_path: impl AsRef<Path>,
617 video_path: impl AsRef<Path>,
618 output_path: impl AsRef<Path>,
619 ) -> Result<()> {
620 let video_id =
621 self.extract_video_id_from_file_paths(video_path.as_ref(), audio_path.as_ref());
622
623 if let Some(video_id) = video_id
624 && let Some(video) = self.get_video_by_id(&video_id).await
625 {
626 #[cfg(feature = "tracing")]
627 tracing::debug!("Adding metadata to combined file");
628
629 cfg_if::cfg_if! {
630 if #[cfg(feature = "cache")] {
631 let video_format = self.find_cached_format(video_path.as_ref()).await;
632 let audio_format = self.find_cached_format(audio_path.as_ref()).await;
633
634 // Add metadata (including chapters) to the combined file with full format information
635 if let Err(_e) = metadata::MetadataManager::add_metadata_with_chapters(
636 output_path.as_ref(),
637 &video,
638 video_format.as_ref(),
639 audio_format.as_ref(),
640 )
641 .await
642 {
643 #[cfg(feature = "tracing")]
644 tracing::warn!("Failed to add metadata to combined file: {}", _e);
645 } else {
646 #[cfg(feature = "tracing")]
647 tracing::debug!("Successfully added metadata (including chapters) to combined file");
648 }
649 } else {
650 // Without cache, we don't have format details, add basic metadata only
651 if let Err(e) = metadata::MetadataManager::add_metadata(
652 output_path.as_ref(),
653 &video,
654 )
655 .await
656 {
657 #[cfg(feature = "tracing")]
658 tracing::warn!("Failed to add basic metadata to combined file: {}", e);
659 } else {
660 #[cfg(feature = "tracing")]
661 tracing::debug!("Successfully added basic metadata to combined file");
662 }
663 }
664 }
665 }
666
667 Ok(())
668 }
669
670 /// Extracts the video ID from audio and video file paths
671 fn extract_video_id_from_file_paths(
672 &self,
673 video_path: impl AsRef<Path>,
674 audio_path: impl AsRef<Path>,
675 ) -> Option<String> {
676 let video_filename = video_path.as_ref().file_name()?.to_str()?;
677
678 if let Some(id) = utils::fs::extract_video_id(video_filename) {
679 return Some(id);
680 }
681
682 let audio_filename = audio_path.as_ref().file_name()?.to_str()?;
683 utils::fs::extract_video_id(audio_filename)
684 }
685
686 /// Finds the format of a file in the cache if it exists
687 #[cfg(feature = "cache")]
688 async fn find_cached_format(
689 &self,
690 file_path: impl AsRef<Path>,
691 ) -> Option<model::format::Format> {
692 if let Some(download_cache) = &self.download_cache {
693 let file_hash = match DownloadCache::calculate_file_hash(file_path.as_ref()).await {
694 Ok(hash) => hash,
695 Err(_) => return None,
696 };
697
698 if let Some((cached_file, _)) = download_cache.get_by_hash(&file_hash).await
699 && let Some(ref format_json) = cached_file.format_json
700 && let Ok(format) = serde_json::from_str(format_json)
701 {
702 return Some(format);
703 }
704 }
705
706 None
707 }
708
709 /// Enables caching of video metadata.
710 ///
711 /// # Arguments
712 ///
713 /// * `cache_dir` - The directory where to store the cache.
714 /// * `ttl` - The time-to-live for cache entries in seconds (default: 24 hours).
715 ///
716 /// # Errors
717 ///
718 /// This function will return an error if the cache directory could not be created.
719 ///
720 /// # Examples
721 ///
722 /// ```rust, no_run
723 /// # use yt_dlp::Youtube;
724 /// # use std::path::PathBuf;
725 /// # use yt_dlp::client::deps::Libraries;
726 /// # #[tokio::main]
727 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
728 /// # let libraries_dir = PathBuf::from("libs");
729 /// # let output_dir = PathBuf::from("output");
730 /// # let youtube = libraries_dir.join("yt-dlp");
731 /// # let ffmpeg = libraries_dir.join("ffmpeg");
732 /// # let libraries = Libraries::new(youtube, ffmpeg);
733 /// let mut fetcher = Youtube::new(libraries, output_dir)?;
734 ///
735 /// // Enable video metadata caching
736 /// fetcher.with_cache(PathBuf::from("cache"), None)?;
737 /// # Ok(())
738 /// # }
739 /// ```
740 #[cfg(feature = "cache")]
741 pub async fn with_cache(
742 &mut self,
743 cache_dir: impl AsRef<Path> + std::fmt::Debug,
744 ttl: Option<u64>,
745 ) -> Result<&mut Self> {
746 #[cfg(feature = "tracing")]
747 tracing::debug!("Enabling video metadata cache");
748
749 let cache = VideoCache::new(cache_dir.as_ref(), ttl).await?;
750 self.cache = Some(Arc::new(cache));
751 Ok(self)
752 }
753
754 /// Enables caching of downloaded files.
755 ///
756 /// # Arguments
757 ///
758 /// * `cache_dir` - The directory where to store the cache.
759 /// * `ttl` - The time-to-live for cache entries in seconds (default: 7 days).
760 ///
761 /// # Errors
762 ///
763 /// This function will return an error if the cache directory could not be created.
764 ///
765 /// # Examples
766 ///
767 /// ```rust, no_run
768 /// # use yt_dlp::Youtube;
769 /// # use std::path::PathBuf;
770 /// # use yt_dlp::client::deps::Libraries;
771 /// # #[tokio::main]
772 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
773 /// # let libraries_dir = PathBuf::from("libs");
774 /// # let output_dir = PathBuf::from("output");
775 /// # let youtube = libraries_dir.join("yt-dlp");
776 /// # let ffmpeg = libraries_dir.join("ffmpeg");
777 /// # let libraries = Libraries::new(youtube, ffmpeg);
778 /// let mut fetcher = Youtube::new(libraries, output_dir)?;
779 ///
780 /// // Enable downloaded files caching
781 /// fetcher.with_download_cache(PathBuf::from("cache"), None)?;
782 /// # Ok(())
783 /// # }
784 /// ```
785 #[cfg(feature = "cache")]
786 pub async fn with_download_cache(
787 &mut self,
788 cache_dir: impl AsRef<Path> + std::fmt::Debug,
789 ttl: Option<u64>,
790 ) -> Result<&mut Self> {
791 #[cfg(feature = "tracing")]
792 tracing::debug!("Enabling downloaded files cache");
793
794 let download_cache = DownloadCache::new(cache_dir.as_ref(), ttl).await?;
795 self.download_cache = Some(Arc::new(download_cache));
796 Ok(self)
797 }
798
799 /// Enables caching of playlist metadata.
800 ///
801 /// # Arguments
802 ///
803 /// * `cache_dir` - The directory where to store the cache.
804 /// * `ttl` - The time-to-live for cache entries in seconds (default: 6 hours).
805 ///
806 /// # Errors
807 ///
808 /// This function will return an error if the cache directory could not be created.
809 ///
810 /// # Examples
811 ///
812 /// ```rust, no_run
813 /// # use yt_dlp::Youtube;
814 /// # use std::path::PathBuf;
815 /// # use yt_dlp::client::deps::Libraries;
816 /// # #[tokio::main]
817 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
818 /// # let libraries_dir = PathBuf::from("libs");
819 /// # let output_dir = PathBuf::from("output");
820 /// # let youtube = libraries_dir.join("yt-dlp");
821 /// # let ffmpeg = libraries_dir.join("ffmpeg");
822 /// # let libraries = Libraries::new(youtube, ffmpeg);
823 /// let mut fetcher = Youtube::new(libraries, output_dir)?;
824 ///
825 /// // Enable playlist metadata caching
826 /// fetcher.with_playlist_cache(PathBuf::from("cache"), None)?;
827 /// # Ok(())
828 /// # }
829 /// ```
830 #[cfg(feature = "cache")]
831 pub async fn with_playlist_cache(
832 &mut self,
833 cache_dir: impl AsRef<Path> + std::fmt::Debug,
834 ttl: Option<i64>,
835 ) -> Result<&mut Self> {
836 #[cfg(feature = "tracing")]
837 tracing::debug!("Enabling playlist metadata cache");
838
839 let db_path = cache_dir.as_ref().join("playlists.db");
840 let playlist_cache = if let Some(ttl_seconds) = ttl {
841 PlaylistCache::with_ttl(db_path, ttl_seconds).await?
842 } else {
843 PlaylistCache::new(db_path).await?
844 };
845 self.playlist_cache = Some(Arc::new(playlist_cache));
846 Ok(self)
847 }
848
849 /// Download a video using the download manager with priority.
850 ///
851 /// This method adds the video download to the download queue with the specified priority.
852 /// The download will be processed according to its priority and the current load.
853 ///
854 /// # Arguments
855 ///
856 /// * `video` - The video to download.
857 /// * `output` - The name of the file to save the video to.
858 /// * `priority` - The download priority (optional).
859 ///
860 /// # Returns
861 ///
862 /// The download ID that can be used to track the download status.
863 ///
864 /// # Errors
865 ///
866 /// This function will return an error if the video information could not be retrieved.
867 pub async fn download_video_with_priority(
868 &self,
869 video: &model::Video,
870 output: impl AsRef<str> + std::fmt::Debug,
871 priority: Option<download::manager::DownloadPriority>,
872 ) -> Result<u64> {
873 #[cfg(feature = "tracing")]
874 tracing::debug!("Downloading video with priority: {}", video.id);
875
876 // Get the best format with video and audio
877 let format = video
878 .formats
879 .iter()
880 .find(|f| f.format_type().is_audio_and_video())
881 .ok_or_else(|| Error::FormatNotAvailable {
882 video_id: video.id.clone(),
883 format_type: "audio+video".to_string(),
884 available_formats: video.formats.iter().map(|f| f.format_id.clone()).collect(),
885 })?;
886
887 // Get the URL
888 let url = format
889 .download_info
890 .url
891 .as_ref()
892 .ok_or_else(|| Error::FormatNoUrl {
893 video_id: video.id.clone(),
894 format_id: format.format_id.clone(),
895 })?;
896
897 // Create the output path
898 let output_path = self.output_dir.join(output.as_ref());
899
900 // Add to download queue
901 let download_id = self
902 .download_manager
903 .enqueue(url, output_path, priority)
904 .await;
905
906 Ok(download_id)
907 }
908
909 /// Download a video using the download manager with progress tracking.
910 ///
911 /// This method adds the video download to the download queue and provides progress updates.
912 ///
913 /// # Arguments
914 ///
915 /// * `video` - The video to download.
916 /// * `output` - The name of the file to save the video to.
917 /// * `progress_callback` - A function that will be called with progress updates.
918 ///
919 /// # Returns
920 ///
921 /// The download ID that can be used to track the download status.
922 ///
923 /// # Errors
924 ///
925 /// This function will return an error if the video information could not be retrieved.
926 pub async fn download_video_with_progress<F>(
927 &self,
928 video: &model::Video,
929 output: impl AsRef<str> + std::fmt::Debug,
930 progress_callback: F,
931 ) -> Result<u64>
932 where
933 F: Fn(u64, u64) + Send + Sync + 'static,
934 {
935 #[cfg(feature = "tracing")]
936 tracing::debug!("Downloading video with progress tracking: {}", video.id);
937
938 // Get the best format with video and audio
939 let format = video
940 .formats
941 .iter()
942 .find(|f| f.format_type().is_audio_and_video())
943 .ok_or_else(|| Error::FormatNotAvailable {
944 video_id: video.id.clone(),
945 format_type: "audio+video".to_string(),
946 available_formats: video.formats.iter().map(|f| f.format_id.clone()).collect(),
947 })?;
948
949 // Get the URL
950 let url = format
951 .download_info
952 .url
953 .as_ref()
954 .ok_or_else(|| Error::FormatNoUrl {
955 video_id: video.id.clone(),
956 format_id: format.format_id.clone(),
957 })?;
958
959 // Create the output path
960 let output_path = self.output_dir.join(output.as_ref());
961
962 // Add to download queue with progress callback
963 let download_id = self
964 .download_manager
965 .enqueue_with_progress(
966 url,
967 output_path,
968 Some(download::manager::DownloadPriority::Normal),
969 progress_callback,
970 )
971 .await;
972
973 Ok(download_id)
974 }
975
976 /// Get the status of a download.
977 ///
978 /// # Arguments
979 ///
980 /// * `download_id` - The ID of the download to check.
981 ///
982 /// # Returns
983 ///
984 /// The download status, or None if the download ID is not found.
985 pub async fn get_download_status(
986 &self,
987 download_id: u64,
988 ) -> Option<download::manager::DownloadStatus> {
989 self.download_manager.get_status(download_id).await
990 }
991
992 /// Cancel a download.
993 ///
994 /// # Arguments
995 ///
996 /// * `download_id` - The ID of the download to cancel.
997 ///
998 /// # Returns
999 ///
1000 /// true if the download was canceled, false if it was not found or already completed.
1001 pub async fn cancel_download(&self, download_id: u64) -> bool {
1002 self.download_manager.cancel(download_id).await
1003 }
1004
1005 /// Wait for a download to complete.
1006 ///
1007 /// # Arguments
1008 ///
1009 /// * `download_id` - The ID of the download to wait for.
1010 ///
1011 /// # Returns
1012 ///
1013 /// The final download status, or None if the download ID is not found.
1014 pub async fn wait_for_download(
1015 &self,
1016 download_id: u64,
1017 ) -> Option<download::manager::DownloadStatus> {
1018 self.download_manager.wait_for_completion(download_id).await
1019 }
1020
1021 /// Downloads a video with the specified video and audio quality preferences.
1022 ///
1023 /// # Arguments
1024 ///
1025 /// * `url` - The URL of the video to download
1026 /// * `output` - The name of the output file
1027 /// * `video_quality` - The desired video quality
1028 /// * `video_codec` - The preferred video codec
1029 /// * `audio_quality` - The desired audio quality
1030 /// * `audio_codec` - The preferred audio codec
1031 ///
1032 /// # Returns
1033 ///
1034 /// The path to the downloaded video file
1035 ///
1036 /// # Example
1037 ///
1038 /// ```rust, no_run
1039 /// # use yt_dlp::Youtube;
1040 /// # use std::path::PathBuf;
1041 /// # use yt_dlp::client::deps::Libraries;
1042 /// # use yt_dlp::model::{VideoQuality, VideoCodecPreference, AudioQuality, AudioCodecPreference};
1043 /// # #[tokio::main]
1044 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
1045 /// # let libraries_dir = PathBuf::from("libs");
1046 /// # let output_dir = PathBuf::from("output");
1047 /// # let youtube = libraries_dir.join("yt-dlp");
1048 /// # let ffmpeg = libraries_dir.join("ffmpeg");
1049 /// # let libraries = Libraries::new(youtube, ffmpeg);
1050 /// # let fetcher = Youtube::new(libraries, output_dir)?;
1051 /// let url = String::from("https://www.youtube.com/watch?v=dQw4w9WgXcQ");
1052 ///
1053 /// // Download a high quality video with VP9 codec and high quality audio with Opus codec
1054 /// let video_path = fetcher.download_video_with_quality(
1055 /// url,
1056 /// "my-video.mp4",
1057 /// VideoQuality::High,
1058 /// VideoCodecPreference::VP9,
1059 /// AudioQuality::High,
1060 /// AudioCodecPreference::Opus
1061 /// ).await?;
1062 /// # Ok(())
1063 /// # }
1064 /// ```
1065 pub async fn download_video_with_quality(
1066 &self,
1067 url: impl AsRef<str> + std::fmt::Debug + Display,
1068 output: impl AsRef<str> + std::fmt::Debug + Display,
1069 video_quality: model::selector::VideoQuality,
1070 video_codec: model::selector::VideoCodecPreference,
1071 audio_quality: model::selector::AudioQuality,
1072 audio_codec: model::selector::AudioCodecPreference,
1073 ) -> Result<PathBuf> {
1074 let video = self.fetch_video_infos(url.to_string()).await?;
1075
1076 // Select video format based on quality and codec preferences
1077 let video_format = video
1078 .select_video_format(video_quality, video_codec.clone())
1079 .ok_or_else(|| Error::FormatNotAvailable {
1080 video_id: video.id.clone(),
1081 format_type: "video".to_string(),
1082 available_formats: video.formats.iter().map(|f| f.format_id.clone()).collect(),
1083 })?;
1084
1085 // Select audio format based on quality and codec preferences
1086 let audio_format = video
1087 .select_audio_format(audio_quality, audio_codec.clone())
1088 .ok_or_else(|| Error::FormatNotAvailable {
1089 video_id: video.id.clone(),
1090 format_type: "audio".to_string(),
1091 available_formats: video.formats.iter().map(|f| f.format_id.clone()).collect(),
1092 })?;
1093
1094 // Download video format with preferences
1095 let video_ext = format!("{:?}", video_format.download_info.ext);
1096 let video_filename = format!("temp_video_{}.{}", utils::fs::random_filename(8), video_ext);
1097
1098 cfg_if::cfg_if! {
1099 if #[cfg(feature = "cache")] {
1100 let video_path = self
1101 .download_format_with_preferences(
1102 video_format,
1103 &video_filename,
1104 Some(video_quality),
1105 None,
1106 Some(video_codec),
1107 None,
1108 )
1109 .await?;
1110 } else {
1111 let video_path = self
1112 .download_format(video_format, &video_filename)
1113 .await?;
1114 }
1115 }
1116
1117 // Download audio format with preferences
1118 let audio_ext = format!("{:?}", audio_format.download_info.ext);
1119 let audio_filename = format!("temp_audio_{}.{}", utils::fs::random_filename(8), audio_ext);
1120 cfg_if::cfg_if! {
1121 if #[cfg(feature = "cache")] {
1122 let audio_path = self
1123 .download_format_with_preferences(
1124 audio_format,
1125 &audio_filename,
1126 None,
1127 Some(audio_quality),
1128 None,
1129 Some(audio_codec),
1130 )
1131 .await?;
1132 } else {
1133 let audio_path = self
1134 .download_format(audio_format, &audio_filename)
1135 .await?;
1136 }
1137 }
1138
1139 // Combine audio and video
1140 let output_path = self
1141 .combine_audio_and_video(&audio_filename, &video_filename, output)
1142 .await?;
1143
1144 // Clean up temporary files
1145 if let Err(_e) = tokio::fs::remove_file(&video_path).await {
1146 #[cfg(feature = "tracing")]
1147 tracing::warn!("Failed to remove temporary video file: {}", _e);
1148 }
1149
1150 if let Err(_e) = tokio::fs::remove_file(&audio_path).await {
1151 #[cfg(feature = "tracing")]
1152 tracing::warn!("Failed to remove temporary audio file: {}", _e);
1153 }
1154
1155 Ok(output_path)
1156 }
1157
1158 /// Downloads a video stream with the specified quality preferences.
1159 ///
1160 /// # Arguments
1161 ///
1162 /// * `url` - The URL of the video to download
1163 /// * `output` - The name of the output file
1164 /// * `quality` - The desired video quality
1165 /// * `codec` - The preferred video codec
1166 ///
1167 /// # Returns
1168 ///
1169 /// The path to the downloaded video file
1170 ///
1171 /// # Example
1172 ///
1173 /// ```rust, no_run
1174 /// # use yt_dlp::Youtube;
1175 /// # use std::path::PathBuf;
1176 /// # use yt_dlp::client::deps::Libraries;
1177 /// # use yt_dlp::model::{VideoQuality, VideoCodecPreference};
1178 /// # #[tokio::main]
1179 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
1180 /// # let libraries_dir = PathBuf::from("libs");
1181 /// # let output_dir = PathBuf::from("output");
1182 /// # let youtube = libraries_dir.join("yt-dlp");
1183 /// # let ffmpeg = libraries_dir.join("ffmpeg");
1184 /// # let libraries = Libraries::new(youtube, ffmpeg);
1185 /// # let fetcher = Youtube::new(libraries, output_dir)?;
1186 /// let url = String::from("https://www.youtube.com/watch?v=dQw4w9WgXcQ");
1187 ///
1188 /// // Download a medium quality video with AVC1 codec
1189 /// let video_path = fetcher.download_video_stream_with_quality(
1190 /// url,
1191 /// "video-only.mp4",
1192 /// VideoQuality::Medium,
1193 /// VideoCodecPreference::AVC1
1194 /// ).await?;
1195 /// # Ok(())
1196 /// # }
1197 /// ```
1198 pub async fn download_video_stream_with_quality(
1199 &self,
1200 url: impl AsRef<str> + std::fmt::Debug + Display,
1201 output: impl AsRef<str> + std::fmt::Debug + Display,
1202 quality: model::selector::VideoQuality,
1203 codec: model::selector::VideoCodecPreference,
1204 ) -> Result<PathBuf> {
1205 let video = self.fetch_video_infos(url.to_string()).await?;
1206
1207 // Select video format based on quality and codec preferences
1208 let video_format = video
1209 .select_video_format(quality, codec.clone())
1210 .ok_or_else(|| Error::FormatNotAvailable {
1211 video_id: video.id.clone(),
1212 format_type: "video".to_string(),
1213 available_formats: video.formats.iter().map(|f| f.format_id.clone()).collect(),
1214 })?;
1215
1216 // Download video format with preferences
1217 cfg_if::cfg_if! {
1218 if #[cfg(feature = "cache")] {
1219 self.download_format_with_preferences(
1220 video_format,
1221 output,
1222 Some(quality),
1223 None,
1224 Some(codec),
1225 None,
1226 )
1227 .await
1228 } else {
1229 self.download_format(video_format, output)
1230 .await
1231 }
1232 }
1233 }
1234
1235 /// Downloads an audio stream with the specified quality preferences.
1236 ///
1237 /// # Arguments
1238 ///
1239 /// * `url` - The URL of the video to download
1240 /// * `output` - The name of the output file
1241 /// * `quality` - The desired audio quality
1242 /// * `codec` - The preferred audio codec
1243 ///
1244 /// # Returns
1245 ///
1246 /// The path to the downloaded audio file
1247 ///
1248 /// # Example
1249 ///
1250 /// ```rust, no_run
1251 /// # use yt_dlp::Youtube;
1252 /// # use std::path::PathBuf;
1253 /// # use yt_dlp::client::deps::Libraries;
1254 /// # use yt_dlp::model::{AudioQuality, AudioCodecPreference};
1255 /// # #[tokio::main]
1256 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
1257 /// # let libraries_dir = PathBuf::from("libs");
1258 /// # let output_dir = PathBuf::from("output");
1259 /// # let youtube = libraries_dir.join("yt-dlp");
1260 /// # let ffmpeg = libraries_dir.join("ffmpeg");
1261 /// # let libraries = Libraries::new(youtube, ffmpeg);
1262 /// # let fetcher = Youtube::new(libraries, output_dir)?;
1263 /// let url = String::from("https://www.youtube.com/watch?v=dQw4w9WgXcQ");
1264 ///
1265 /// // Download a high quality audio with Opus codec
1266 /// let audio_path = fetcher.download_audio_stream_with_quality(
1267 /// url,
1268 /// "audio-only.mp3",
1269 /// AudioQuality::High,
1270 /// AudioCodecPreference::Opus
1271 /// ).await?;
1272 /// # Ok(())
1273 /// # }
1274 /// ```
1275 pub async fn download_audio_stream_with_quality(
1276 &self,
1277 url: impl AsRef<str> + std::fmt::Debug + Display,
1278 output: impl AsRef<str> + std::fmt::Debug + Display,
1279 quality: model::selector::AudioQuality,
1280 codec: model::selector::AudioCodecPreference,
1281 ) -> Result<PathBuf> {
1282 let video = self.fetch_video_infos(url.to_string()).await?;
1283
1284 // Select audio format based on quality and codec preferences
1285 let audio_format = video
1286 .select_audio_format(quality, codec.clone())
1287 .ok_or_else(|| Error::FormatNotAvailable {
1288 video_id: video.id.clone(),
1289 format_type: "audio".to_string(),
1290 available_formats: video.formats.iter().map(|f| f.format_id.clone()).collect(),
1291 })?;
1292
1293 // Download audio format with preferences
1294 cfg_if::cfg_if! {
1295 if #[cfg(feature = "cache")] {
1296 self.download_format_with_preferences(
1297 audio_format,
1298 output,
1299 None,
1300 Some(quality),
1301 None,
1302 Some(codec),
1303 )
1304 .await
1305 } else {
1306 self.download_format(audio_format, output)
1307 .await
1308 }
1309 }
1310 }
1311
1312 /// Initiates a graceful shutdown of all ongoing operations.
1313 ///
1314 /// This method triggers the cancellation token, signaling all ongoing
1315 /// downloads and operations to stop gracefully. It does not wait for
1316 /// operations to complete.
1317 ///
1318 /// # Examples
1319 ///
1320 /// ```rust,no_run
1321 /// # use yt_dlp::Youtube;
1322 /// # use yt_dlp::client::deps::Libraries;
1323 /// # use std::path::PathBuf;
1324 /// # #[tokio::main]
1325 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
1326 /// # let libs = Libraries::new(PathBuf::from("yt-dlp"), PathBuf::from("ffmpeg"));
1327 /// let youtube = Youtube::new(libs, "output").await?;
1328 ///
1329 /// // Start some downloads...
1330 ///
1331 /// // Initiate graceful shutdown
1332 /// youtube.shutdown();
1333 /// # Ok(())
1334 /// # }
1335 /// ```
1336 pub fn shutdown(&self) {
1337 #[cfg(feature = "tracing")]
1338 tracing::info!("Initiating graceful shutdown");
1339
1340 self.cancellation_token.cancel();
1341 }
1342
1343 /// Checks if a shutdown has been requested.
1344 ///
1345 /// # Returns
1346 ///
1347 /// Returns `true` if shutdown has been initiated, `false` otherwise.
1348 pub fn is_shutdown_requested(&self) -> bool {
1349 self.cancellation_token.is_cancelled()
1350 }
1351
1352 // ==================== Fluent API Methods ====================
1353
1354 /// Fluent method to fetch video info and return self for chaining.
1355 ///
1356 /// This is useful for building operation pipelines.
1357 ///
1358 /// # Arguments
1359 ///
1360 /// * `url` - The YouTube video URL
1361 ///
1362 /// # Returns
1363 ///
1364 /// A tuple of (self, video) for method chaining
1365 ///
1366 /// # Examples
1367 ///
1368 /// ```rust,no_run
1369 /// # use yt_dlp::Youtube;
1370 /// # use yt_dlp::client::deps::Libraries;
1371 /// # #[tokio::main]
1372 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
1373 /// # let libs = Libraries::new("yt-dlp", "ffmpeg");
1374 /// let (youtube, video) = Youtube::builder(libs, "output")
1375 /// .build()
1376 /// .await?
1377 /// .fetch("https://youtube.com/watch?v=dQw4w9WgXcQ")
1378 /// .await?;
1379 ///
1380 /// println!("Title: {}", video.title);
1381 /// # Ok(())
1382 /// # }
1383 /// ```
1384 pub async fn fetch(self, url: impl Into<String>) -> Result<(Self, model::Video)> {
1385 let video = self.fetch_video_infos(url.into()).await?;
1386 Ok((self, video))
1387 }
1388
1389 /// Fluent method to download a video and return self for chaining.
1390 ///
1391 /// # Arguments
1392 ///
1393 /// * `video` - The video to download
1394 /// * `output` - The output filename
1395 ///
1396 /// # Examples
1397 ///
1398 /// ```rust,no_run
1399 /// # use yt_dlp::Youtube;
1400 /// # use yt_dlp::client::deps::Libraries;
1401 /// # #[tokio::main]
1402 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
1403 /// # let libs = Libraries::new("yt-dlp", "ffmpeg");
1404 /// let youtube = Youtube::builder(libs, "output")
1405 /// .build()
1406 /// .await?
1407 /// .fetch("https://youtube.com/watch?v=dQw4w9WgXcQ")
1408 /// .await?
1409 /// .0
1410 /// .download_and_continue(&video, "output.mp4")
1411 /// .await?;
1412 /// # Ok(())
1413 /// # }
1414 /// ```
1415 pub async fn download_and_continue(
1416 self,
1417 video: &model::Video,
1418 output: impl AsRef<str> + std::fmt::Debug + Display,
1419 ) -> Result<Self> {
1420 self.download_video(video, output).await?;
1421 Ok(self)
1422 }
1423
1424 /// Chain multiple operations in a pipeline.
1425 ///
1426 /// This method allows you to chain fetch -> download -> metadata operations.
1427 ///
1428 /// # Examples
1429 ///
1430 /// ```rust,no_run
1431 /// # use yt_dlp::Youtube;
1432 /// # use yt_dlp::client::deps::Libraries;
1433 /// # #[tokio::main]
1434 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
1435 /// # let libs = Libraries::new("yt-dlp", "ffmpeg");
1436 /// Youtube::builder(libs, "output")
1437 /// .build()
1438 /// .await?
1439 /// .pipeline("https://youtube.com/watch?v=dQw4w9WgXcQ", |yt, video| async move {
1440 /// yt.download_video(&video, "video.mp4").await?;
1441 /// Ok(yt)
1442 /// })
1443 /// .await?;
1444 /// # Ok(())
1445 /// # }
1446 /// ```
1447 pub async fn pipeline<F, Fut>(self, url: impl Into<String>, operation: F) -> Result<Self>
1448 where
1449 F: FnOnce(Self, model::Video) -> Fut,
1450 Fut: std::future::Future<Output = Result<Self>>,
1451 {
1452 let video = self.fetch_video_infos(url.into()).await?;
1453 operation(self, video).await
1454 }
1455
1456 /// Applies post-processing to a video file using FFmpeg.
1457 ///
1458 /// This method allows you to apply various post-processing operations such as:
1459 /// - Codec conversion (H.264, H.265, VP9, AV1)
1460 /// - Bitrate adjustment
1461 /// - Resolution scaling
1462 /// - Video filters (crop, rotate, brightness, contrast, etc.)
1463 ///
1464 /// # Arguments
1465 ///
1466 /// * `input_path` - Path to the input video file
1467 /// * `output` - The output filename
1468 /// * `config` - Post-processing configuration
1469 ///
1470 /// # Errors
1471 ///
1472 /// Returns an error if FFmpeg execution fails
1473 ///
1474 /// # Returns
1475 ///
1476 /// The path to the processed video file
1477 ///
1478 /// # Examples
1479 ///
1480 /// ```rust,no_run
1481 /// # use yt_dlp::Youtube;
1482 /// # use yt_dlp::download::postprocess::{PostProcessConfig, VideoCodec, AudioCodec, Resolution};
1483 /// # use std::path::PathBuf;
1484 /// # use yt_dlp::client::deps::Libraries;
1485 /// # #[tokio::main]
1486 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
1487 /// # let libraries = Libraries::new("libs/yt-dlp", "libs/ffmpeg");
1488 /// # let youtube = Youtube::builder(libraries, "output").build().await?;
1489 /// let config = PostProcessConfig::new()
1490 /// .with_video_codec(VideoCodec::H264)
1491 /// .with_audio_codec(AudioCodec::AAC)
1492 /// .with_video_bitrate("2M")
1493 /// .with_resolution(Resolution::HD);
1494 ///
1495 /// let processed = youtube.postprocess_video("input.mp4", "output.mp4", config).await?;
1496 /// # Ok(())
1497 /// # }
1498 /// ```
1499 pub async fn postprocess_video(
1500 &self,
1501 input_path: impl AsRef<std::path::Path>,
1502 output: impl AsRef<str>,
1503 config: download::postprocess::PostProcessConfig,
1504 ) -> Result<PathBuf> {
1505 let output_path = self.output_dir.join(output.as_ref());
1506
1507 metadata::postprocess::apply_postprocess(
1508 input_path,
1509 &output_path,
1510 &config,
1511 &self.libraries,
1512 self.timeout,
1513 )
1514 .await
1515 }
1516}