Skip to main content

yt_dlp/events/
types.rs

1use std::fmt;
2use std::path::PathBuf;
3use std::time::Duration;
4
5use crate::download::DownloadPriority;
6use crate::model::Video;
7use crate::model::chapter::Chapter;
8use crate::model::format::Format;
9use crate::model::playlist::Playlist;
10
11/// The method used for live recording.
12#[cfg(feature = "live-recording")]
13#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
14pub enum RecordingMethod {
15    /// Pure Rust recording via reqwest HLS segment fetching
16    Native,
17    /// FFmpeg-based recording (fallback)
18    Fallback,
19}
20
21/// Represents all possible events that can occur during download operations
22#[derive(Debug, Clone, serde::Serialize)]
23#[allow(clippy::large_enum_variant)]
24pub enum DownloadEvent {
25    /// Video metadata has been fetched from the URL
26    VideoFetched {
27        url: String,
28        video: Box<Video>,
29        duration: Duration,
30    },
31
32    /// Video metadata fetch failed
33    VideoFetchFailed {
34        url: String,
35        error: String,
36        duration: Duration,
37    },
38
39    /// Download has been queued in the download manager
40    DownloadQueued {
41        download_id: u64,
42        url: String,
43        priority: DownloadPriority,
44        output_path: PathBuf,
45    },
46
47    /// Download has started processing
48    DownloadStarted {
49        download_id: u64,
50        url: String,
51        total_bytes: u64,
52        format_id: Option<String>,
53    },
54
55    /// Download progress update
56    DownloadProgress {
57        download_id: u64,
58        downloaded_bytes: u64,
59        total_bytes: u64,
60        speed_bytes_per_sec: f64,
61        eta_seconds: Option<u64>,
62    },
63
64    /// Download has been paused
65    DownloadPaused { download_id: u64, reason: String },
66
67    /// Download has been resumed
68    DownloadResumed { download_id: u64 },
69
70    /// Download completed successfully
71    DownloadCompleted {
72        download_id: u64,
73        url: String,
74        output_path: PathBuf,
75        duration: Duration,
76        total_bytes: u64,
77    },
78
79    /// Download failed with error
80    DownloadFailed {
81        download_id: u64,
82        url: String,
83        error: String,
84        retry_count: u32,
85    },
86
87    /// Download was canceled
88    DownloadCanceled { download_id: u64, reason: String },
89
90    /// Format has been selected for download
91    FormatSelected {
92        video_id: String,
93        format: Format,
94        quality: String,
95    },
96
97    /// Metadata has been applied to a file
98    MetadataApplied { path: PathBuf, metadata_type: MetadataType },
99
100    /// Chapters have been embedded into a file
101    ChaptersEmbedded { path: PathBuf, chapters: Vec<Chapter> },
102
103    /// Post-processing has started
104    PostProcessStarted {
105        input_path: PathBuf,
106        operation: PostProcessOperation,
107    },
108
109    /// Post-processing completed successfully
110    PostProcessCompleted {
111        input_path: PathBuf,
112        output_path: PathBuf,
113        operation: PostProcessOperation,
114        duration: Duration,
115    },
116
117    /// Post-processing failed
118    PostProcessFailed {
119        input_path: PathBuf,
120        operation: PostProcessOperation,
121        error: String,
122    },
123
124    /// Playlist metadata has been fetched
125    PlaylistFetched {
126        url: String,
127        playlist: Playlist,
128        duration: Duration,
129    },
130
131    /// Playlist metadata fetch failed
132    PlaylistFetchFailed {
133        url: String,
134        error: String,
135        duration: Duration,
136    },
137
138    /// Playlist item download has started
139    PlaylistItemStarted {
140        playlist_id: String,
141        index: usize,
142        total: usize,
143        video_id: String,
144    },
145
146    /// Playlist item download completed
147    PlaylistItemCompleted {
148        playlist_id: String,
149        index: usize,
150        total: usize,
151        video_id: String,
152        output_path: PathBuf,
153    },
154
155    /// Playlist item download failed
156    PlaylistItemFailed {
157        playlist_id: String,
158        index: usize,
159        total: usize,
160        video_id: String,
161        error: String,
162    },
163
164    /// Entire playlist download completed
165    PlaylistCompleted {
166        playlist_id: String,
167        total_items: usize,
168        successful: usize,
169        failed: usize,
170        duration: Duration,
171    },
172
173    /// Segment download started (for parallel downloads)
174    SegmentStarted {
175        download_id: u64,
176        segment_index: usize,
177        total_segments: usize,
178    },
179
180    /// Segment download completed
181    SegmentCompleted {
182        download_id: u64,
183        segment_index: usize,
184        total_segments: usize,
185        bytes: u64,
186    },
187
188    /// Live recording has started
189    #[cfg(feature = "live-recording")]
190    LiveRecordingStarted {
191        video_id: String,
192        url: String,
193        quality: String,
194        method: RecordingMethod,
195    },
196
197    /// Live recording progress update
198    #[cfg(feature = "live-recording")]
199    LiveRecordingProgress {
200        video_id: String,
201        elapsed: Duration,
202        bytes_written: u64,
203        segments: u64,
204        bitrate_bps: f64,
205    },
206
207    /// Live recording stopped (graceful)
208    #[cfg(feature = "live-recording")]
209    LiveRecordingStopped {
210        video_id: String,
211        reason: String,
212        output_path: PathBuf,
213        total_bytes: u64,
214        total_duration: Duration,
215    },
216
217    /// Live recording failed
218    #[cfg(feature = "live-recording")]
219    LiveRecordingFailed { video_id: String, error: String },
220
221    /// Live fragment streaming started
222    #[cfg(feature = "live-streaming")]
223    LiveStreamStarted {
224        video_id: String,
225        url: String,
226        quality: String,
227    },
228
229    /// Live fragment streaming progress update
230    #[cfg(feature = "live-streaming")]
231    LiveStreamProgress {
232        video_id: String,
233        elapsed: Duration,
234        bytes_received: u64,
235        segments: u64,
236        bitrate_bps: f64,
237    },
238
239    /// Live fragment streaming stopped
240    #[cfg(feature = "live-streaming")]
241    LiveStreamStopped {
242        video_id: String,
243        reason: String,
244        total_bytes: u64,
245        total_duration: Duration,
246    },
247
248    /// Live fragment streaming failed
249    #[cfg(feature = "live-streaming")]
250    LiveStreamFailed { video_id: String, error: String },
251}
252
253/// Types of metadata that can be applied
254#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, serde::Serialize)]
255pub enum MetadataType {
256    /// MP3 ID3 tags
257    Mp3,
258    /// MP4/M4A metadata
259    Mp4,
260    /// FFmpeg metadata
261    Ffmpeg,
262}
263
264impl fmt::Display for MetadataType {
265    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
266        match self {
267            Self::Mp3 => f.write_str("Mp3"),
268            Self::Mp4 => f.write_str("Mp4"),
269            Self::Ffmpeg => f.write_str("Ffmpeg"),
270        }
271    }
272}
273
274/// Post-processing operations
275#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
276pub enum PostProcessOperation {
277    /// Combining audio and video streams
278    CombineStreams { audio_path: PathBuf, video_path: PathBuf },
279    /// Converting audio format
280    ConvertAudio { target_format: String },
281    /// Embedding subtitles
282    EmbedSubtitles { subtitle_path: PathBuf },
283    /// Embedding thumbnail
284    EmbedThumbnail { thumbnail_path: PathBuf },
285    /// Custom FFmpeg operation
286    Custom { description: String },
287    /// Splitting a video into individual chapter files
288    SplitChapters { source_path: PathBuf, chapter_count: usize },
289}
290
291impl fmt::Display for PostProcessOperation {
292    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
293        match self {
294            Self::CombineStreams { .. } => f.write_str("CombineStreams"),
295            Self::ConvertAudio { target_format } => {
296                write!(f, "ConvertAudio(format={})", target_format)
297            }
298            Self::EmbedSubtitles { .. } => f.write_str("EmbedSubtitles"),
299            Self::EmbedThumbnail { .. } => f.write_str("EmbedThumbnail"),
300            Self::Custom { description } => write!(f, "Custom(description={})", description),
301            Self::SplitChapters { chapter_count, .. } => write!(f, "SplitChapters(chapters={})", chapter_count),
302        }
303    }
304}
305
306#[cfg(feature = "live-recording")]
307impl fmt::Display for RecordingMethod {
308    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
309        match self {
310            Self::Native => f.write_str("Native"),
311            Self::Fallback => f.write_str("Fallback"),
312        }
313    }
314}
315
316impl DownloadEvent {
317    /// Returns the download ID if this event is associated with a specific download
318    pub fn download_id(&self) -> Option<u64> {
319        match self {
320            Self::DownloadQueued { download_id, .. }
321            | Self::DownloadStarted { download_id, .. }
322            | Self::DownloadProgress { download_id, .. }
323            | Self::DownloadPaused { download_id, .. }
324            | Self::DownloadResumed { download_id, .. }
325            | Self::DownloadCompleted { download_id, .. }
326            | Self::DownloadFailed { download_id, .. }
327            | Self::DownloadCanceled { download_id, .. }
328            | Self::SegmentStarted { download_id, .. }
329            | Self::SegmentCompleted { download_id, .. } => Some(*download_id),
330            _ => None,
331        }
332    }
333
334    /// Returns true if this is a terminal event (download completed, failed, or canceled)
335    pub fn is_terminal(&self) -> bool {
336        matches!(
337            self,
338            Self::DownloadCompleted { .. } | Self::DownloadFailed { .. } | Self::DownloadCanceled { .. }
339        )
340    }
341
342    /// Returns true if this is a progress event
343    pub fn is_progress(&self) -> bool {
344        match self {
345            Self::DownloadProgress { .. } => true,
346            #[cfg(feature = "live-recording")]
347            Self::LiveRecordingProgress { .. } => true,
348            #[cfg(feature = "live-streaming")]
349            Self::LiveStreamProgress { .. } => true,
350            _ => false,
351        }
352    }
353
354    /// Returns a human-readable event type name
355    pub fn event_type(&self) -> &'static str {
356        match self {
357            Self::VideoFetched { .. } => "video_fetched",
358            Self::VideoFetchFailed { .. } => "video_fetch_failed",
359            Self::DownloadQueued { .. } => "download_queued",
360            Self::DownloadStarted { .. } => "download_started",
361            Self::DownloadProgress { .. } => "download_progress",
362            Self::DownloadPaused { .. } => "download_paused",
363            Self::DownloadResumed { .. } => "download_resumed",
364            Self::DownloadCompleted { .. } => "download_completed",
365            Self::DownloadFailed { .. } => "download_failed",
366            Self::DownloadCanceled { .. } => "download_canceled",
367            Self::FormatSelected { .. } => "format_selected",
368            Self::MetadataApplied { .. } => "metadata_applied",
369            Self::ChaptersEmbedded { .. } => "chapters_embedded",
370            Self::PostProcessStarted { .. } => "post_process_started",
371            Self::PostProcessCompleted { .. } => "post_process_completed",
372            Self::PostProcessFailed { .. } => "post_process_failed",
373            Self::PlaylistFetched { .. } => "playlist_fetched",
374            Self::PlaylistFetchFailed { .. } => "playlist_fetch_failed",
375            Self::PlaylistItemStarted { .. } => "playlist_item_started",
376            Self::PlaylistItemCompleted { .. } => "playlist_item_completed",
377            Self::PlaylistItemFailed { .. } => "playlist_item_failed",
378            Self::PlaylistCompleted { .. } => "playlist_completed",
379            Self::SegmentStarted { .. } => "segment_started",
380            Self::SegmentCompleted { .. } => "segment_completed",
381            #[cfg(feature = "live-recording")]
382            Self::LiveRecordingStarted { .. } => "live_recording_started",
383            #[cfg(feature = "live-recording")]
384            Self::LiveRecordingProgress { .. } => "live_recording_progress",
385            #[cfg(feature = "live-recording")]
386            Self::LiveRecordingStopped { .. } => "live_recording_stopped",
387            #[cfg(feature = "live-recording")]
388            Self::LiveRecordingFailed { .. } => "live_recording_failed",
389            #[cfg(feature = "live-streaming")]
390            Self::LiveStreamStarted { .. } => "live_stream_started",
391            #[cfg(feature = "live-streaming")]
392            Self::LiveStreamProgress { .. } => "live_stream_progress",
393            #[cfg(feature = "live-streaming")]
394            Self::LiveStreamStopped { .. } => "live_stream_stopped",
395            #[cfg(feature = "live-streaming")]
396            Self::LiveStreamFailed { .. } => "live_stream_failed",
397        }
398    }
399}
400
401impl fmt::Display for DownloadEvent {
402    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
403        match self {
404            Self::DownloadProgress {
405                download_id,
406                downloaded_bytes,
407                total_bytes,
408                ..
409            } => {
410                write!(
411                    f,
412                    "DownloadProgress(id={}, downloaded={}, total={})",
413                    download_id, downloaded_bytes, total_bytes
414                )
415            }
416            _ => {
417                let event_type = self.event_type();
418                if let Some(id) = self.download_id() {
419                    write!(f, "{}(id={})", event_type, id)
420                } else {
421                    f.write_str(event_type)
422                }
423            }
424        }
425    }
426}