1use crate::spec::{Quality, Rung};
10
11const STANDARD_SHORT_SIDES: &[u32] = &[2160, 1440, 1080, 720, 480, 360, 240];
14
15pub const DEFAULT_MAX_SHORT_SIDE: u32 = 1080;
18
19const MIN_DIMENSION: u32 = 200;
21
22pub fn standard_ladder(src_width: u32, src_height: u32, max_short_side: Option<u32>) -> Vec<Rung> {
27 dims_for(src_width, src_height, max_short_side)
28 .into_iter()
29 .map(|(w, h)| Rung::new(w, h))
30 .collect()
31}
32
33pub fn standard_ladder_with_quality(
35 src_width: u32,
36 src_height: u32,
37 max_short_side: Option<u32>,
38 quality: Quality,
39) -> Vec<Rung> {
40 dims_for(src_width, src_height, max_short_side)
41 .into_iter()
42 .map(|(w, h)| Rung::new(w, h).with_quality(quality.clone()))
43 .collect()
44}
45
46fn dims_for(src_width: u32, src_height: u32, max_short_side: Option<u32>) -> Vec<(u32, u32)> {
48 let cap = max_short_side.unwrap_or(DEFAULT_MAX_SHORT_SIDE);
49 if src_width == 0 || src_height == 0 {
50 return Vec::new();
51 }
52
53 let is_landscape = src_width >= src_height;
54 let src_short = src_width.min(src_height);
55 let src_long = src_width.max(src_height);
56 let src_aspect = src_long as f64 / src_short as f64;
57
58 let (top_short, top_long) = if src_short > cap {
61 let s = cap;
62 let l = ((s as f64 * src_aspect).round() as u32) & !1;
63 (s & !1, l)
64 } else {
65 (src_short & !1, src_long & !1)
66 };
67 let (top_w, top_h) = if is_landscape {
68 (top_long, top_short)
69 } else {
70 (top_short, top_long)
71 };
72
73 let mut out: Vec<(u32, u32)> = vec![(top_w, top_h)];
74
75 for &short in STANDARD_SHORT_SIDES {
76 if short >= top_short || short > cap {
77 continue;
78 }
79 let long = ((short as f64 * src_aspect).round() as u32) & !1;
80 let short_even = short & !1;
81 if short_even < MIN_DIMENSION || long < MIN_DIMENSION {
82 continue;
83 }
84 let (w, h) = if is_landscape {
85 (long, short_even)
86 } else {
87 (short_even, long)
88 };
89 if !out.iter().any(|&(pw, ph)| pw == w && ph == h) {
90 out.push((w, h));
91 }
92 }
93
94 out
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 fn dims(rungs: &[Rung]) -> Vec<(u32, u32)> {
102 rungs.iter().map(|r| (r.width, r.height)).collect()
103 }
104
105 #[test]
106 fn ladder_16_9_1080p_source() {
107 let v = standard_ladder(1920, 1080, None);
108 assert_eq!(
109 dims(&v),
110 vec![(1920, 1080), (1280, 720), (852, 480), (640, 360), (426, 240)]
111 );
112 }
113
114 #[test]
115 fn ladder_4k_clamps_to_1080p_by_default() {
116 let v = standard_ladder(3840, 2160, None);
117 assert_eq!(v.first().map(|r| (r.width, r.height)), Some((1920, 1080)));
118 }
119
120 #[test]
121 fn ladder_4k_with_2160_cap_keeps_full_quality() {
122 let v = standard_ladder(3840, 2160, Some(2160));
123 assert_eq!(v.first().map(|r| (r.width, r.height)), Some((3840, 2160)));
124 assert_eq!(v.len(), 7);
125 }
126
127 #[test]
128 fn ladder_portrait_short_side_labels() {
129 let v = standard_ladder(1080, 1920, None);
130 assert_eq!(dims(&v), vec![(1080, 1920), (720, 1280), (480, 852), (360, 640), (240, 426)]);
131 assert_eq!(v[0].label, "1080p");
132 assert_eq!(v[1].label, "720p");
133 }
134
135 #[test]
136 fn ladder_below_floor_keeps_only_source() {
137 assert_eq!(dims(&standard_ladder(320, 240, None)), vec![(320, 240)]);
138 }
139
140 #[test]
141 fn ladder_zero_dims_empty() {
142 assert!(standard_ladder(0, 1080, None).is_empty());
143 }
144
145 #[test]
146 fn every_rung_is_even() {
147 for r in standard_ladder(1921, 1081, None) {
148 assert_eq!(r.width % 2, 0);
149 assert_eq!(r.height % 2, 0);
150 }
151 }
152}