1#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8#[non_exhaustive]
9pub struct Rung {
10 pub height: u32,
13
14 pub bitrate: u64,
17}
18
19impl Rung {
20 pub fn new(height: u32, bitrate: u64) -> Self {
22 Self { height, bitrate }
23 }
24}
25
26#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
28#[non_exhaustive]
29pub enum Error {
30 #[error("rung {height}p at {bitrate} bps encodes nothing")]
33 Empty {
34 height: u32,
36 bitrate: u64,
38 },
39
40 #[error("rungs {first}p and {second}p share a maximum of {bitrate} bps")]
43 DuplicateBitrate {
44 bitrate: u64,
46 first: u32,
48 second: u32,
50 },
51
52 #[error("rung {height}p at {bitrate} bps does not rise above {below_height}p at {below_bitrate} bps")]
56 Unordered {
57 height: u32,
59 bitrate: u64,
61 below_height: u32,
63 below_bitrate: u64,
65 },
66}
67
68#[derive(Clone, Debug, PartialEq, Eq)]
75pub struct Ladder {
76 rungs: Vec<Rung>,
78}
79
80impl Ladder {
81 pub fn new(rungs: impl IntoIterator<Item = Rung>) -> Result<Self, Error> {
89 let mut rungs: Vec<Rung> = rungs.into_iter().collect();
90 for rung in &mut rungs {
91 if rung.height < 2 || rung.bitrate == 0 {
92 return Err(Error::Empty {
93 height: rung.height,
94 bitrate: rung.bitrate,
95 });
96 }
97 rung.height &= !1;
99 }
100
101 rungs.sort_by_key(|rung| (rung.bitrate, rung.height));
102
103 for pair in rungs.windows(2) {
104 let (below, rung) = (pair[0], pair[1]);
105 if below.bitrate == rung.bitrate {
106 return Err(Error::DuplicateBitrate {
107 bitrate: rung.bitrate,
108 first: below.height,
109 second: rung.height,
110 });
111 }
112 if rung.height <= below.height {
113 return Err(Error::Unordered {
114 height: rung.height,
115 bitrate: rung.bitrate,
116 below_height: below.height,
117 below_bitrate: below.bitrate,
118 });
119 }
120 }
121
122 Ok(Self { rungs })
123 }
124
125 pub fn rungs(&self) -> &[Rung] {
127 &self.rungs
128 }
129}
130
131impl Default for Ladder {
132 fn default() -> Self {
135 Self::new([
136 Rung::new(240, 350_000),
137 Rung::new(360, 600_000),
138 Rung::new(480, 1_200_000),
139 Rung::new(720, 2_500_000),
140 Rung::new(1080, 5_000_000),
141 ])
142 .expect("the default ladder is ordered")
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 fn heights(ladder: &Ladder) -> Vec<u32> {
151 ladder.rungs().iter().map(|rung| rung.height).collect()
152 }
153
154 #[test]
155 fn default_ladder_is_ordered() {
156 let ladder = Ladder::default();
157 assert_eq!(heights(&ladder), [240, 360, 480, 720, 1080]);
158 }
159
160 #[test]
163 fn custom_ladder_out_of_order() {
164 let ladder = Ladder::new([
165 Rung::new(720, 2_500_000),
166 Rung::new(240, 350_000),
167 Rung::new(480, 1_200_000),
168 ])
169 .unwrap();
170 assert_eq!(heights(&ladder), [240, 480, 720]);
171 assert_eq!(ladder.rungs()[1].bitrate, 1_200_000);
174 assert_eq!(ladder.rungs()[0].bitrate, 350_000);
175 }
176
177 #[test]
179 fn duplicate_ceiling_is_refused() {
180 let err = Ladder::new([Rung::new(720, 2_500_000), Rung::new(480, 2_500_000)]).unwrap_err();
181 assert_eq!(
182 err,
183 Error::DuplicateBitrate {
184 bitrate: 2_500_000,
185 first: 480,
186 second: 720,
187 }
188 );
189 }
190
191 #[test]
195 fn duplicate_height_is_refused() {
196 let err = Ladder::new([Rung::new(721, 2_500_000), Rung::new(720, 1_200_000)]).unwrap_err();
197 assert_eq!(
198 err,
199 Error::Unordered {
200 height: 720,
201 bitrate: 2_500_000,
202 below_height: 720,
203 below_bitrate: 1_200_000,
204 }
205 );
206 }
207
208 #[test]
211 fn resolution_inversion_is_refused() {
212 let err = Ladder::new([Rung::new(1080, 1_000_000), Rung::new(360, 3_000_000)]).unwrap_err();
213 assert_eq!(
214 err,
215 Error::Unordered {
216 height: 360,
217 bitrate: 3_000_000,
218 below_height: 1080,
219 below_bitrate: 1_000_000,
220 }
221 );
222 }
223
224 #[test]
225 fn rung_without_a_rendition_is_refused() {
226 assert_eq!(
227 Ladder::new([Rung::new(1, 350_000)]).unwrap_err(),
228 Error::Empty {
229 height: 1,
230 bitrate: 350_000
231 }
232 );
233 assert_eq!(
234 Ladder::new([Rung::new(240, 0)]).unwrap_err(),
235 Error::Empty {
236 height: 240,
237 bitrate: 0
238 }
239 );
240 }
241
242 #[test]
243 fn empty_ladder_is_allowed() {
244 assert!(Ladder::new([]).unwrap().rungs().is_empty());
245 }
246}