use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct QualityProfile {
pub bitrate: u32,
pub sample_rate: u32,
pub channels: u8,
pub codec: String,
pub duration: f64,
}
impl QualityProfile {
pub fn new(bitrate: u32, sample_rate: u32, channels: u8, codec: String, duration: f64) -> anyhow::Result<Self> {
if bitrate == 0 {
anyhow::bail!("Bitrate must be positive, got {}", bitrate);
}
if sample_rate == 0 {
anyhow::bail!("Sample rate must be positive, got {}", sample_rate);
}
if channels != 1 && channels != 2 {
anyhow::bail!("Channels must be 1 or 2, got {}", channels);
}
Ok(Self {
bitrate,
sample_rate,
channels,
codec,
duration,
})
}
pub fn is_better_than(&self, other: &QualityProfile, prefer_stereo: bool) -> bool {
if self.bitrate != other.bitrate {
return self.bitrate > other.bitrate;
}
if self.sample_rate != other.sample_rate {
return self.sample_rate > other.sample_rate;
}
if self.channels != other.channels {
if prefer_stereo {
return self.channels > other.channels;
} else {
return self.channels < other.channels;
}
}
let codec_priority = |codec: &str| match codec.to_lowercase().as_str() {
"aac" => 2,
"mp3" => 1,
_ => 0,
};
codec_priority(&self.codec) > codec_priority(&other.codec)
}
pub fn is_compatible_for_concat(&self, other: &QualityProfile) -> bool {
self.bitrate == other.bitrate
&& self.sample_rate == other.sample_rate
&& self.channels == other.channels
&& self.codec.to_lowercase() == other.codec.to_lowercase()
}
pub fn to_aac_equivalent(&self) -> QualityProfile {
let aac_bitrate = self.bitrate.max(128);
QualityProfile {
bitrate: aac_bitrate,
sample_rate: self.sample_rate,
channels: self.channels,
codec: "aac".to_string(),
duration: self.duration,
}
}
pub fn from_preset(preset: &str, source: &QualityProfile) -> Option<QualityProfile> {
match preset.to_lowercase().as_str() {
"low" => Some(QualityProfile {
bitrate: 64,
sample_rate: 22050,
channels: 1, codec: "aac".to_string(),
duration: source.duration,
}),
"medium" => Some(QualityProfile {
bitrate: 96,
sample_rate: 44100,
channels: 2, codec: "aac".to_string(),
duration: source.duration,
}),
"high" => Some(QualityProfile {
bitrate: 128,
sample_rate: 48000,
channels: 2, codec: "aac".to_string(),
duration: source.duration,
}),
"ultra" => Some(QualityProfile {
bitrate: 192,
sample_rate: 48000,
channels: 2, codec: "aac".to_string(),
duration: source.duration,
}),
"maximum" => Some(QualityProfile {
bitrate: 256,
sample_rate: 48000,
channels: 2, codec: "aac".to_string(),
duration: source.duration,
}),
"source" | _ => None, }
}
pub fn apply_preset(&self, preset: Option<&str>) -> QualityProfile {
preset
.and_then(|p| Self::from_preset(p, self))
.unwrap_or_else(|| self.clone())
}
}
impl fmt::Display for QualityProfile {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}kbps, {}Hz, {}ch, {}, {:.1}s",
self.bitrate, self.sample_rate, self.channels, self.codec, self.duration
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_quality_creation() {
let profile = QualityProfile::new(128, 44100, 2, "aac".to_string(), 3600.0).unwrap();
assert_eq!(profile.bitrate, 128);
assert_eq!(profile.sample_rate, 44100);
assert_eq!(profile.channels, 2);
assert_eq!(profile.codec, "aac");
}
#[test]
fn test_quality_validation() {
assert!(QualityProfile::new(0, 44100, 2, "aac".to_string(), 3600.0).is_err());
assert!(QualityProfile::new(128, 0, 2, "aac".to_string(), 3600.0).is_err());
assert!(QualityProfile::new(128, 44100, 3, "aac".to_string(), 3600.0).is_err());
}
#[test]
fn test_is_better_than() {
let high = QualityProfile::new(256, 44100, 2, "aac".to_string(), 3600.0).unwrap();
let low = QualityProfile::new(128, 44100, 2, "aac".to_string(), 3600.0).unwrap();
assert!(high.is_better_than(&low, true));
assert!(!low.is_better_than(&high, true));
}
#[test]
fn test_compatibility() {
let profile1 = QualityProfile::new(128, 44100, 2, "aac".to_string(), 3600.0).unwrap();
let profile2 = QualityProfile::new(128, 44100, 2, "aac".to_string(), 1800.0).unwrap();
let profile3 = QualityProfile::new(256, 44100, 2, "aac".to_string(), 3600.0).unwrap();
assert!(profile1.is_compatible_for_concat(&profile2));
assert!(!profile1.is_compatible_for_concat(&profile3));
}
}