Skip to main content

yt_dlp/model/
selector.rs

1//! Format selector enumerations for audio and video formats.
2
3use std::fmt;
4
5use serde::{Deserialize, Serialize};
6
7/// Represents video quality preferences for format selection.
8#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
9pub enum VideoQuality {
10    /// Best available video quality (highest resolution, fps, and bitrate)
11    #[default]
12    Best,
13    /// High quality video (1080p or better if available)
14    High,
15    /// Medium quality video (720p if available)
16    Medium,
17    /// Low quality video (480p or lower)
18    Low,
19    /// Worst available video quality (lowest resolution, fps, and bitrate)
20    Worst,
21    /// Custom resolution with preference for specified height
22    CustomHeight(u32),
23    /// Custom resolution with preference for specified width
24    CustomWidth(u32),
25}
26
27impl fmt::Display for VideoQuality {
28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        match self {
30            VideoQuality::Best => f.write_str("Best"),
31            VideoQuality::High => f.write_str("High"),
32            VideoQuality::Medium => f.write_str("Medium"),
33            VideoQuality::Low => f.write_str("Low"),
34            VideoQuality::Worst => f.write_str("Worst"),
35            VideoQuality::CustomHeight(h) => write!(f, "CustomHeight(height={h})"),
36            VideoQuality::CustomWidth(w) => write!(f, "CustomWidth(width={w})"),
37        }
38    }
39}
40
41/// Represents audio quality preferences for format selection.
42#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
43pub enum AudioQuality {
44    /// Best available audio quality (highest bitrate and sample rate)
45    #[default]
46    Best,
47    /// High quality audio (192kbps or better if available)
48    High,
49    /// Medium quality audio (128kbps if available)
50    Medium,
51    /// Low quality audio (96kbps or lower)
52    Low,
53    /// Worst available audio quality (lowest bitrate and sample rate)
54    Worst,
55    /// Custom audio with preference for specified bitrate in kbps
56    CustomBitrate(u32),
57}
58
59impl fmt::Display for AudioQuality {
60    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61        match self {
62            AudioQuality::Best => f.write_str("Best"),
63            AudioQuality::High => f.write_str("High"),
64            AudioQuality::Medium => f.write_str("Medium"),
65            AudioQuality::Low => f.write_str("Low"),
66            AudioQuality::Worst => f.write_str("Worst"),
67            AudioQuality::CustomBitrate(b) => write!(f, "CustomBitrate(bitrate={b})"),
68        }
69    }
70}
71
72/// Represents codec preferences for video format selection.
73#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
74pub enum VideoCodecPreference {
75    /// Prefer VP9 codec
76    VP9,
77    /// Prefer AVC1/H.264 codec
78    AVC1,
79    /// Prefer AV01/AV1 codec
80    AV1,
81    /// Custom codec preference
82    Custom(String),
83    /// No specific codec preference
84    #[default]
85    Any,
86}
87
88impl fmt::Display for VideoCodecPreference {
89    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90        match self {
91            VideoCodecPreference::VP9 => f.write_str("VP9"),
92            VideoCodecPreference::AVC1 => f.write_str("AVC1"),
93            VideoCodecPreference::AV1 => f.write_str("AV1"),
94            VideoCodecPreference::Custom(c) => write!(f, "Custom(codec={c})"),
95            VideoCodecPreference::Any => f.write_str("Any"),
96        }
97    }
98}
99
100/// Represents codec preferences for audio format selection.
101#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
102pub enum AudioCodecPreference {
103    /// Prefer Opus codec
104    Opus,
105    /// Prefer AAC codec
106    AAC,
107    /// Prefer MP3 codec
108    MP3,
109    /// Custom codec preference
110    Custom(String),
111    /// No specific codec preference
112    #[default]
113    Any,
114}
115
116impl fmt::Display for AudioCodecPreference {
117    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118        match self {
119            AudioCodecPreference::Opus => f.write_str("Opus"),
120            AudioCodecPreference::AAC => f.write_str("AAC"),
121            AudioCodecPreference::MP3 => f.write_str("MP3"),
122            AudioCodecPreference::Custom(c) => write!(f, "Custom(codec={c})"),
123            AudioCodecPreference::Any => f.write_str("Any"),
124        }
125    }
126}
127
128/// Represents quality preferences for storyboard format selection.
129///
130/// A storyboard is a grid of video preview images embedded in MHTML fragments.
131/// Higher quality storyboards have more fragments and larger per-frame resolution.
132#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
133pub enum StoryboardQuality {
134    /// Best available storyboard (highest resolution, most fragments).
135    #[default]
136    Best,
137    /// Worst available storyboard (lowest resolution, fewest fragments).
138    Worst,
139}
140
141impl fmt::Display for StoryboardQuality {
142    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143        match self {
144            StoryboardQuality::Best => f.write_str("Best"),
145            StoryboardQuality::Worst => f.write_str("Worst"),
146        }
147    }
148}
149
150/// Represents quality preferences for thumbnail format selection.
151#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
152pub enum ThumbnailQuality {
153    /// Best available thumbnail (highest resolution)
154    #[default]
155    Best,
156    /// Minimum resolution preference (minimum width, minimum height)
157    MinimumResolution(u32, u32),
158    /// Worst available thumbnail (lowest resolution)
159    Worst,
160}
161
162impl fmt::Display for ThumbnailQuality {
163    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164        match self {
165            ThumbnailQuality::Best => f.write_str("Best"),
166            ThumbnailQuality::MinimumResolution(w, h) => {
167                write!(f, "MinimumResolution(width={w}, height={h})")
168            }
169            ThumbnailQuality::Worst => f.write_str("Worst"),
170        }
171    }
172}
173
174/// Case-insensitive substring check without allocation.
175fn contains_ignore_ascii_case(haystack: &str, needle: &str) -> bool {
176    if needle.len() > haystack.len() {
177        return false;
178    }
179    haystack
180        .as_bytes()
181        .windows(needle.len())
182        .any(|w| w.eq_ignore_ascii_case(needle.as_bytes()))
183}
184
185/// Checks whether a video codec string matches the given codec preference.
186///
187/// The comparison is case-insensitive and checks for substring containment,
188/// so `"vp9.0"` will match [`VideoCodecPreference::VP9`]. The `Any` preference
189/// always returns `true`.
190///
191/// # Arguments
192///
193/// * `codec` - The codec identifier string to check (e.g. `"vp9"`, `"avc1.64001f"`).
194/// * `preference` - The desired codec preference to match against.
195///
196/// # Returns
197///
198/// `true` if the codec matches the preference, or if the preference is `Any`.
199pub fn matches_video_codec(codec: &str, preference: &VideoCodecPreference) -> bool {
200    match preference {
201        VideoCodecPreference::VP9 => contains_ignore_ascii_case(codec, "vp9"),
202        VideoCodecPreference::AVC1 => {
203            contains_ignore_ascii_case(codec, "avc1")
204                || contains_ignore_ascii_case(codec, "h264")
205                || contains_ignore_ascii_case(codec, "h.264")
206        }
207        VideoCodecPreference::AV1 => {
208            contains_ignore_ascii_case(codec, "av1") || contains_ignore_ascii_case(codec, "av01")
209        }
210        VideoCodecPreference::Custom(custom) => contains_ignore_ascii_case(codec, custom),
211        VideoCodecPreference::Any => true,
212    }
213}
214
215/// Checks whether an audio codec string matches the given codec preference.
216///
217/// The comparison is case-insensitive and checks for substring containment,
218/// so `"mp4a.40.2"` will match [`AudioCodecPreference::AAC`]. The `Any` preference
219/// always returns `true`.
220///
221/// # Arguments
222///
223/// * `codec` - The codec identifier string to check (e.g. `"opus"`, `"mp4a.40.2"`).
224/// * `preference` - The desired codec preference to match against.
225///
226/// # Returns
227///
228/// `true` if the codec matches the preference, or if the preference is `Any`.
229pub fn matches_audio_codec(codec: &str, preference: &AudioCodecPreference) -> bool {
230    match preference {
231        AudioCodecPreference::Opus => contains_ignore_ascii_case(codec, "opus"),
232        AudioCodecPreference::AAC => {
233            contains_ignore_ascii_case(codec, "aac") || contains_ignore_ascii_case(codec, "mp4a")
234        }
235        AudioCodecPreference::MP3 => contains_ignore_ascii_case(codec, "mp3"),
236        AudioCodecPreference::Custom(custom) => contains_ignore_ascii_case(codec, custom),
237        AudioCodecPreference::Any => true,
238    }
239}
240
241/// Bundles video/audio quality and codec preferences for format cache lookups.
242///
243/// Used to pass download preferences through cache layers without repeating
244/// four separate `Option` parameters everywhere.
245#[derive(Debug, Default, Clone, PartialEq, Eq)]
246pub struct FormatPreferences {
247    /// Preferred video quality.
248    pub video_quality: Option<VideoQuality>,
249    /// Preferred audio quality.
250    pub audio_quality: Option<AudioQuality>,
251    /// Preferred video codec.
252    pub video_codec: Option<VideoCodecPreference>,
253    /// Preferred audio codec.
254    pub audio_codec: Option<AudioCodecPreference>,
255}
256
257impl FormatPreferences {
258    /// Returns `true` if at least one preference is set.
259    pub fn has_any(&self) -> bool {
260        self.video_quality.is_some()
261            || self.audio_quality.is_some()
262            || self.video_codec.is_some()
263            || self.audio_codec.is_some()
264    }
265}
266
267impl fmt::Display for FormatPreferences {
268    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
269        write!(
270            f,
271            "FormatPreferences(video_quality={}, audio_quality={}, video_codec={}, audio_codec={})",
272            self.video_quality
273                .as_ref()
274                .map_or("none".to_string(), |q| q.to_string()),
275            self.audio_quality
276                .as_ref()
277                .map_or("none".to_string(), |q| q.to_string()),
278            self.video_codec.as_ref().map_or("none".to_string(), |c| c.to_string()),
279            self.audio_codec.as_ref().map_or("none".to_string(), |c| c.to_string()),
280        )
281    }
282}