use std::fmt;
use serde::{Deserialize, Serialize};
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum VideoQuality {
#[default]
Best,
High,
Medium,
Low,
Worst,
CustomHeight(u32),
CustomWidth(u32),
}
impl fmt::Display for VideoQuality {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
VideoQuality::Best => f.write_str("Best"),
VideoQuality::High => f.write_str("High"),
VideoQuality::Medium => f.write_str("Medium"),
VideoQuality::Low => f.write_str("Low"),
VideoQuality::Worst => f.write_str("Worst"),
VideoQuality::CustomHeight(h) => write!(f, "CustomHeight(height={h})"),
VideoQuality::CustomWidth(w) => write!(f, "CustomWidth(width={w})"),
}
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum AudioQuality {
#[default]
Best,
High,
Medium,
Low,
Worst,
CustomBitrate(u32),
}
impl fmt::Display for AudioQuality {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AudioQuality::Best => f.write_str("Best"),
AudioQuality::High => f.write_str("High"),
AudioQuality::Medium => f.write_str("Medium"),
AudioQuality::Low => f.write_str("Low"),
AudioQuality::Worst => f.write_str("Worst"),
AudioQuality::CustomBitrate(b) => write!(f, "CustomBitrate(bitrate={b})"),
}
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum VideoCodecPreference {
VP9,
AVC1,
AV1,
Custom(String),
#[default]
Any,
}
impl fmt::Display for VideoCodecPreference {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
VideoCodecPreference::VP9 => f.write_str("VP9"),
VideoCodecPreference::AVC1 => f.write_str("AVC1"),
VideoCodecPreference::AV1 => f.write_str("AV1"),
VideoCodecPreference::Custom(c) => write!(f, "Custom(codec={c})"),
VideoCodecPreference::Any => f.write_str("Any"),
}
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum AudioCodecPreference {
Opus,
AAC,
MP3,
Custom(String),
#[default]
Any,
}
impl fmt::Display for AudioCodecPreference {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AudioCodecPreference::Opus => f.write_str("Opus"),
AudioCodecPreference::AAC => f.write_str("AAC"),
AudioCodecPreference::MP3 => f.write_str("MP3"),
AudioCodecPreference::Custom(c) => write!(f, "Custom(codec={c})"),
AudioCodecPreference::Any => f.write_str("Any"),
}
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum StoryboardQuality {
#[default]
Best,
Worst,
}
impl fmt::Display for StoryboardQuality {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
StoryboardQuality::Best => f.write_str("Best"),
StoryboardQuality::Worst => f.write_str("Worst"),
}
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ThumbnailQuality {
#[default]
Best,
MinimumResolution(u32, u32),
Worst,
}
impl fmt::Display for ThumbnailQuality {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ThumbnailQuality::Best => f.write_str("Best"),
ThumbnailQuality::MinimumResolution(w, h) => {
write!(f, "MinimumResolution(width={w}, height={h})")
}
ThumbnailQuality::Worst => f.write_str("Worst"),
}
}
}
fn contains_ignore_ascii_case(haystack: &str, needle: &str) -> bool {
if needle.len() > haystack.len() {
return false;
}
haystack
.as_bytes()
.windows(needle.len())
.any(|w| w.eq_ignore_ascii_case(needle.as_bytes()))
}
pub fn matches_video_codec(codec: &str, preference: &VideoCodecPreference) -> bool {
match preference {
VideoCodecPreference::VP9 => contains_ignore_ascii_case(codec, "vp9"),
VideoCodecPreference::AVC1 => {
contains_ignore_ascii_case(codec, "avc1")
|| contains_ignore_ascii_case(codec, "h264")
|| contains_ignore_ascii_case(codec, "h.264")
}
VideoCodecPreference::AV1 => {
contains_ignore_ascii_case(codec, "av1") || contains_ignore_ascii_case(codec, "av01")
}
VideoCodecPreference::Custom(custom) => contains_ignore_ascii_case(codec, custom),
VideoCodecPreference::Any => true,
}
}
pub fn matches_audio_codec(codec: &str, preference: &AudioCodecPreference) -> bool {
match preference {
AudioCodecPreference::Opus => contains_ignore_ascii_case(codec, "opus"),
AudioCodecPreference::AAC => {
contains_ignore_ascii_case(codec, "aac") || contains_ignore_ascii_case(codec, "mp4a")
}
AudioCodecPreference::MP3 => contains_ignore_ascii_case(codec, "mp3"),
AudioCodecPreference::Custom(custom) => contains_ignore_ascii_case(codec, custom),
AudioCodecPreference::Any => true,
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct FormatPreferences {
pub video_quality: Option<VideoQuality>,
pub audio_quality: Option<AudioQuality>,
pub video_codec: Option<VideoCodecPreference>,
pub audio_codec: Option<AudioCodecPreference>,
}
impl FormatPreferences {
pub fn has_any(&self) -> bool {
self.video_quality.is_some()
|| self.audio_quality.is_some()
|| self.video_codec.is_some()
|| self.audio_codec.is_some()
}
}
impl fmt::Display for FormatPreferences {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"FormatPreferences(video_quality={}, audio_quality={}, video_codec={}, audio_codec={})",
self.video_quality
.as_ref()
.map_or("none".to_string(), |q| q.to_string()),
self.audio_quality
.as_ref()
.map_or("none".to_string(), |q| q.to_string()),
self.video_codec.as_ref().map_or("none".to_string(), |c| c.to_string()),
self.audio_codec.as_ref().map_or("none".to_string(), |c| c.to_string()),
)
}
}