1use serde::{Deserialize, Serialize};
4use std::fmt;
5
6#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
8pub struct QualityProfile {
9 pub bitrate: u32,
11 pub sample_rate: u32,
13 pub channels: u8,
15 pub codec: String,
17 pub duration: f64,
19}
20
21impl QualityProfile {
22 pub fn new(bitrate: u32, sample_rate: u32, channels: u8, codec: String, duration: f64) -> anyhow::Result<Self> {
24 if bitrate == 0 {
25 anyhow::bail!("Bitrate must be positive, got {}", bitrate);
26 }
27 if sample_rate == 0 {
28 anyhow::bail!("Sample rate must be positive, got {}", sample_rate);
29 }
30 if channels != 1 && channels != 2 {
31 anyhow::bail!("Channels must be 1 or 2, got {}", channels);
32 }
33
34 Ok(Self {
35 bitrate,
36 sample_rate,
37 channels,
38 codec,
39 duration,
40 })
41 }
42
43 pub fn is_better_than(&self, other: &QualityProfile, prefer_stereo: bool) -> bool {
45 if self.bitrate != other.bitrate {
48 return self.bitrate > other.bitrate;
49 }
50
51 if self.sample_rate != other.sample_rate {
53 return self.sample_rate > other.sample_rate;
54 }
55
56 if self.channels != other.channels {
58 if prefer_stereo {
59 return self.channels > other.channels;
60 } else {
61 return self.channels < other.channels;
62 }
63 }
64
65 let codec_priority = |codec: &str| match codec.to_lowercase().as_str() {
67 "aac" => 2,
68 "mp3" => 1,
69 _ => 0,
70 };
71
72 codec_priority(&self.codec) > codec_priority(&other.codec)
73 }
74
75 pub fn is_compatible_for_concat(&self, other: &QualityProfile) -> bool {
77 self.bitrate == other.bitrate
78 && self.sample_rate == other.sample_rate
79 && self.channels == other.channels
80 && self.codec.to_lowercase() == other.codec.to_lowercase()
81 }
82
83 pub fn to_aac_equivalent(&self) -> QualityProfile {
85 let aac_bitrate = self.bitrate.max(128); QualityProfile {
89 bitrate: aac_bitrate,
90 sample_rate: self.sample_rate,
91 channels: self.channels,
92 codec: "aac".to_string(),
93 duration: self.duration,
94 }
95 }
96}
97
98impl fmt::Display for QualityProfile {
99 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100 write!(
101 f,
102 "{}kbps, {}Hz, {}ch, {}, {:.1}s",
103 self.bitrate, self.sample_rate, self.channels, self.codec, self.duration
104 )
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn test_quality_creation() {
114 let profile = QualityProfile::new(128, 44100, 2, "aac".to_string(), 3600.0).unwrap();
115 assert_eq!(profile.bitrate, 128);
116 assert_eq!(profile.sample_rate, 44100);
117 assert_eq!(profile.channels, 2);
118 assert_eq!(profile.codec, "aac");
119 }
120
121 #[test]
122 fn test_quality_validation() {
123 assert!(QualityProfile::new(0, 44100, 2, "aac".to_string(), 3600.0).is_err());
124 assert!(QualityProfile::new(128, 0, 2, "aac".to_string(), 3600.0).is_err());
125 assert!(QualityProfile::new(128, 44100, 3, "aac".to_string(), 3600.0).is_err());
126 }
127
128 #[test]
129 fn test_is_better_than() {
130 let high = QualityProfile::new(256, 44100, 2, "aac".to_string(), 3600.0).unwrap();
131 let low = QualityProfile::new(128, 44100, 2, "aac".to_string(), 3600.0).unwrap();
132
133 assert!(high.is_better_than(&low, true));
134 assert!(!low.is_better_than(&high, true));
135 }
136
137 #[test]
138 fn test_compatibility() {
139 let profile1 = QualityProfile::new(128, 44100, 2, "aac".to_string(), 3600.0).unwrap();
140 let profile2 = QualityProfile::new(128, 44100, 2, "aac".to_string(), 1800.0).unwrap();
141 let profile3 = QualityProfile::new(256, 44100, 2, "aac".to_string(), 3600.0).unwrap();
142
143 assert!(profile1.is_compatible_for_concat(&profile2));
144 assert!(!profile1.is_compatible_for_concat(&profile3));
145 }
146}