Skip to main content

yt_dlp/metadata/
api.rs

1//! Public API methods for metadata management.
2//!
3//! This module provides the high-level public API for adding metadata
4//! and thumbnails to downloaded files.
5
6use std::path::PathBuf;
7use std::str::FromStr;
8
9use super::MetadataManager;
10use crate::error::Result;
11use crate::model::Video;
12use crate::model::format::{Extension, Format};
13
14impl MetadataManager {
15    /// Add metadata to a file based on its format.
16    ///
17    /// This method automatically detects the file format and applies appropriate metadata.
18    /// Use this for standalone files when you don't have format details.
19    ///
20    /// # Arguments
21    ///
22    /// * `file_path` - Path to the file to add metadata to
23    /// * `video` - Video metadata to apply
24    ///
25    /// # Errors
26    ///
27    /// Returns an error if the file format is unsupported or if metadata writing fails
28    ///
29    /// # Examples
30    ///
31    /// ```rust,no_run
32    /// # use yt_dlp::metadata::MetadataManager;
33    /// # use yt_dlp::model::Video;
34    /// # use std::path::PathBuf;
35    /// # #[tokio::main]
36    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
37    /// let manager = MetadataManager::new();
38    /// # let video: Video = todo!();
39    /// // video obtained from a fetch_video_infos call
40    /// manager.add_metadata("video.mp4", &video).await?;
41    /// # Ok(())
42    /// # }
43    /// ```
44    pub async fn add_metadata(&self, file_path: impl Into<PathBuf>, video: &Video) -> Result<()> {
45        let file_path: std::path::PathBuf = file_path.into();
46
47        tracing::debug!(
48            file_path = ?file_path,
49            video_id = %video.id,
50            title = %video.title,
51            "🏷️ Adding metadata to file"
52        );
53
54        let file_format = Self::get_file_extension(&file_path)?;
55
56        let extension = Extension::from_str(&file_format).unwrap_or(Extension::Unknown);
57
58        tracing::debug!(
59            file_path = ?file_path,
60            file_format = %file_format,
61            extension = ?extension,
62            "⚙️ Detected file format and extension"
63        );
64
65        let result = match extension {
66            Extension::Mp3 => Self::add_metadata_to_mp3(&file_path, video, None, None).await,
67            Extension::M4A | Extension::Mp4 => Self::add_metadata_to_m4a(&file_path, video, None, None, None).await,
68            Extension::Webm => self.add_metadata_to_webm(&file_path, video, None, None, None).await,
69            Extension::Flac | Extension::Ogg | Extension::Wav | Extension::Aac | Extension::Aiff => {
70                Self::add_metadata_with_lofty(&file_path, video, None, None, &file_format).await
71            }
72            _ => {
73                self.add_ffmpeg_metadata(&file_path, video, &file_format, None, None, None)
74                    .await
75            }
76        };
77
78        match &result {
79            Ok(()) => tracing::debug!(
80                file_path = ?file_path,
81                video_id = %video.id,
82                "✅ Metadata added successfully"
83            ),
84            Err(e) => tracing::warn!(
85                file_path = ?file_path,
86                video_id = %video.id,
87                error = %e,
88                "Failed to add metadata"
89            ),
90        }
91
92        result
93    }
94
95    /// Add metadata to a file with format details for audio and video.
96    ///
97    /// This method should be used when you have detailed format information,
98    /// typically for combined audio+video files. Technical metadata (resolution,
99    /// codecs, bitrates) will be included for MP4 and WebM formats.
100    ///
101    /// # Arguments
102    ///
103    /// * `file_path` - Path to the file to add metadata to
104    /// * `video` - Video metadata to apply
105    /// * `video_format` - Optional video format details (for technical metadata)
106    /// * `audio_format` - Optional audio format details (for technical metadata)
107    ///
108    /// # Errors
109    ///
110    /// Returns an error if the file format is unsupported or if metadata writing fails
111    pub async fn add_metadata_with_format(
112        &self,
113        file_path: impl Into<PathBuf>,
114        video: &Video,
115        video_format: Option<&Format>,
116        audio_format: Option<&Format>,
117    ) -> Result<()> {
118        let file_path: PathBuf = file_path.into();
119
120        tracing::debug!(
121            file_path = ?file_path,
122            video_id = %video.id,
123            title = %video.title,
124            has_video_format = video_format.is_some(),
125            has_audio_format = audio_format.is_some(),
126            "🏷️ Adding metadata with format details to file"
127        );
128
129        let file_format = Self::get_file_extension(&file_path)?;
130
131        let extension = Extension::from_str(&file_format).unwrap_or(Extension::Unknown);
132
133        tracing::debug!(
134            file_path = ?file_path,
135            file_format = %file_format,
136            extension = ?extension,
137            "⚙️ Detected file format and extension"
138        );
139
140        let result = match extension {
141            Extension::Mp3 => Self::add_metadata_to_mp3(&file_path, video, audio_format, None).await,
142            Extension::M4A | Extension::Mp4 => {
143                Self::add_metadata_to_m4a(&file_path, video, audio_format, video_format, None).await
144            }
145            Extension::Webm => {
146                self.add_metadata_to_webm(&file_path, video, video_format, audio_format, None)
147                    .await
148            }
149            Extension::Flac | Extension::Ogg | Extension::Wav | Extension::Aac | Extension::Aiff => {
150                Self::add_metadata_with_lofty(&file_path, video, audio_format, None, &file_format).await
151            }
152            _ => {
153                self.add_ffmpeg_metadata(&file_path, video, &file_format, video_format, audio_format, None)
154                    .await
155            }
156        };
157
158        match &result {
159            Ok(()) => tracing::debug!(
160                file_path = ?file_path,
161                video_id = %video.id,
162                "✅ Metadata with format added successfully"
163            ),
164            Err(e) => tracing::warn!(
165                file_path = ?file_path,
166                video_id = %video.id,
167                error = %e,
168                "Failed to add metadata with format"
169            ),
170        }
171
172        result
173    }
174
175    /// Add a thumbnail to a file based on its format.
176    ///
177    /// Thumbnails are embedded in the file metadata. Supported formats: MP3, M4A, MP4, WebM, MKV
178    ///
179    /// # Arguments
180    ///
181    /// * `file_path` - Path to the file to add thumbnail to
182    /// * `thumbnail_path` - Path to the thumbnail image file
183    ///
184    /// # Errors
185    ///
186    /// Returns an error if the file format doesn't support thumbnails or if embedding fails
187    ///
188    /// # Examples
189    ///
190    /// ```rust,no_run
191    /// # use yt_dlp::metadata::MetadataManager;
192    /// # use std::path::PathBuf;
193    /// # #[tokio::main]
194    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
195    /// let manager = MetadataManager::new();
196    /// manager
197    ///     .add_thumbnail_to_file("video.mp3", "cover.jpg")
198    ///     .await?;
199    /// # Ok(())
200    /// # }
201    /// ```
202    pub async fn add_thumbnail_to_file(
203        &self,
204        file_path: impl Into<PathBuf>,
205        thumbnail_path: impl Into<PathBuf>,
206    ) -> Result<()> {
207        let file_path: PathBuf = file_path.into();
208        let thumbnail_path: PathBuf = thumbnail_path.into();
209
210        tracing::debug!(
211            file_path = ?file_path,
212            thumbnail_path = ?thumbnail_path,
213            "🏷️ Adding thumbnail to file"
214        );
215
216        let file_format = Self::get_file_extension(&file_path)?;
217
218        let extension = Extension::from_str(&file_format).unwrap_or(Extension::Unknown);
219
220        tracing::debug!(
221            file_path = ?file_path,
222            file_format = %file_format,
223            extension = ?extension,
224            "⚙️ Detected file format for thumbnail"
225        );
226
227        let result = match extension {
228            Extension::Mp3 => Self::add_thumbnail_to_mp3(&file_path, &thumbnail_path).await,
229            Extension::M4A | Extension::Mp4 => Self::add_thumbnail_to_m4a(&file_path, &thumbnail_path).await,
230            Extension::Webm => self.add_thumbnail_to_webm(&file_path, &thumbnail_path).await,
231            Extension::Flac | Extension::Ogg | Extension::Wav | Extension::Aac | Extension::Aiff => {
232                Self::add_thumbnail_with_lofty(&file_path, &thumbnail_path, &file_format).await
233            }
234            _ => {
235                tracing::debug!(
236                    file_format = %file_format,
237                    "⚙️ Thumbnails not supported for file format"
238                );
239                Ok(())
240            }
241        };
242
243        match &result {
244            Ok(()) => tracing::debug!(
245                file_path = ?file_path,
246                "✅ Thumbnail added successfully"
247            ),
248            Err(e) => tracing::warn!(
249                file_path = ?file_path,
250                error = %e,
251                "Failed to add thumbnail"
252            ),
253        }
254
255        result
256    }
257}