1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub struct DeviceFormatInfo {
11 pub format_id: i32,
13 pub description: &'static str,
15}
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub struct DeviceProfile {
20 pub name: &'static str,
22 pub formats: &'static [DeviceFormatInfo],
24}
25
26macro_rules! formats {
31 ($($id:expr => $desc:expr),+ $(,)?) => {
32 &[$(DeviceFormatInfo { format_id: $id, description: $desc }),+]
33 };
34}
35
36static CLASSIC_5G: &[DeviceFormatInfo] = formats![
37 1019 => "720×480 YUV422 interlaced full-screen",
38 1024 => "320×240 RGB565 photo",
39 1027 => "100×100 RGB565 cover art",
40 1028 => "100×100 RGB565",
41 1029 => "200×200 RGB565",
42 1031 => "42×42 RGB565",
43 1032 => "42×37 RGB565",
44];
45
46static CLASSIC_5_5G: &[DeviceFormatInfo] = formats![
47 1019 => "720×480 YUV422 interlaced full-screen",
48 1024 => "320×240 RGB565 photo",
49 1027 => "100×100 RGB565 cover art",
50 1028 => "100×100 RGB565",
51 1029 => "200×200 RGB565",
52 1031 => "42×42 RGB565",
53 1032 => "42×37 RGB565",
54 1055 => "128×128 RGB565 cover art medium",
55 1056 => "80×80 RGB565",
56];
57
58static CLASSIC_6G: &[DeviceFormatInfo] = formats![
59 1024 => "320×240 RGB565 photo",
60 1055 => "128×128 RGB565 cover art",
61 1060 => "320×320 RGB565 cover art large",
62 1061 => "56×56 RGB565 cover art small",
63 1066 => "64×64 RGB565 photo",
64 1067 => "720×480 YCbCr420 padded full-screen",
65 1068 => "128×128 RGB565 cover art",
66];
67
68static NANO_1G: &[DeviceFormatInfo] = formats![
69 1024 => "320×240 RGB565",
70 1027 => "100×100 RGB565",
71];
72
73static NANO_2G: &[DeviceFormatInfo] = formats![
74 1019 => "720×480 YUV422",
75 1027 => "100×100",
76 1028 => "100×100",
77 1029 => "200×200",
78 1032 => "42×37",
79];
80
81static NANO_3G: &[DeviceFormatInfo] = formats![
82 1066 => "64×64 RGB565",
83 1067 => "720×480 YCbCr420 padded",
84 1068 => "128×128",
85 1071 => "240×240",
86 1073 => "240×240",
87 1074 => "50×50",
88];
89
90static NANO_4G: &[DeviceFormatInfo] = formats![
91 1071 => "240×240",
92 1073 => "240×240",
93 1074 => "50×50",
94 1078 => "80×80",
95 1079 => "80×80",
96 1083 => "240×320",
97 1084 => "240×240",
98 1085 => "88×88",
99 1087 => "384×384",
100 1089 => "58×58",
101 1092 => "80×80",
102 1093 => "512×512",
103];
104
105static NANO_5G: &[DeviceFormatInfo] = formats![
106 1087 => "384×384 RGB565",
107 1092 => "80×80",
108 1093 => "512×512",
109];
110
111static NANO_6G: &[DeviceFormatInfo] = formats![
112 1084 => "240×240",
113 1092 => "80×80",
114 1093 => "512×512",
115];
116
117static NANO_7G: &[DeviceFormatInfo] = formats![
118 1007 => "480×864 RGB565 full-res",
119 1010 => "240×240 RGB565 cover art",
120];
121
122static MINI_1G_2G: &[DeviceFormatInfo] = formats![
124 1024 => "320×240 RGB565",
125 1027 => "100×100",
126];
127
128static PHOTO_4G: &[DeviceFormatInfo] = formats![
129 1013 => "220×176 RGB565 big-endian",
130 1015 => "130×88 RGB565",
131 1016 => "140×140",
132 1019 => "720×480 YUV422",
133];
134
135static TOUCH: &[DeviceFormatInfo] = formats![
137 3001 => "256×256 RGB555",
138 3002 => "128×128 RGB555",
139 3003 => "64×64 RGB555",
140 3004 => "56×55 RGB555",
141 3005 => "320×320 RGB555",
142 3008 => "640×480 RGB555",
143 3009 => "160×120 RGB555",
144 3011 => "80×79 RGB555",
145];
146
147static IPHONE: &[DeviceFormatInfo] = formats![
149 3001 => "256×256 RGB555",
150 3002 => "128×128 RGB555",
151 3003 => "64×64 RGB555",
152 3004 => "56×55 RGB555",
153 3005 => "320×320 RGB555",
154 3008 => "640×480 RGB555",
155 3009 => "160×120 RGB555",
156 3011 => "80×79 RGB555",
157];
158
159static ROKR_E1: &[DeviceFormatInfo] = formats![
160 2002 => "50×50 RGB565 big-endian",
161 2003 => "150×150 RGB565 big-endian",
162];
163
164pub static DEVICE_PROFILES: &[DeviceProfile] = &[
170 DeviceProfile {
171 name: "iPod Classic 5G (Video)",
172 formats: CLASSIC_5G,
173 },
174 DeviceProfile {
175 name: "iPod Classic 5.5G (Enhanced)",
176 formats: CLASSIC_5_5G,
177 },
178 DeviceProfile {
179 name: "iPod Classic 6G (Thin)",
180 formats: CLASSIC_6G,
181 },
182 DeviceProfile {
183 name: "iPod Nano 1G",
184 formats: NANO_1G,
185 },
186 DeviceProfile {
187 name: "iPod Nano 2G",
188 formats: NANO_2G,
189 },
190 DeviceProfile {
191 name: "iPod Nano 3G",
192 formats: NANO_3G,
193 },
194 DeviceProfile {
195 name: "iPod Nano 4G",
196 formats: NANO_4G,
197 },
198 DeviceProfile {
199 name: "iPod Nano 5G",
200 formats: NANO_5G,
201 },
202 DeviceProfile {
203 name: "iPod Nano 6G",
204 formats: NANO_6G,
205 },
206 DeviceProfile {
207 name: "iPod Nano 7G",
208 formats: NANO_7G,
209 },
210 DeviceProfile {
211 name: "iPod Video 5G",
212 formats: CLASSIC_5G,
213 },
214 DeviceProfile {
215 name: "iPod Mini 1G/2G",
216 formats: MINI_1G_2G,
217 },
218 DeviceProfile {
219 name: "iPod Photo 4G",
220 formats: PHOTO_4G,
221 },
222 DeviceProfile {
223 name: "iPod Touch 1G/2G",
224 formats: TOUCH,
225 },
226 DeviceProfile {
227 name: "iPod Touch 3G/4G",
228 formats: TOUCH,
229 },
230 DeviceProfile {
231 name: "iPhone 1G/2G",
232 formats: IPHONE,
233 },
234 DeviceProfile {
235 name: "iPhone 3G/3GS",
236 formats: IPHONE,
237 },
238 DeviceProfile {
239 name: "Motorola ROKR E1",
240 formats: ROKR_E1,
241 },
242];
243
244#[must_use]
250pub fn find_device(name: &str) -> Option<&'static DeviceProfile> {
251 let lower = name.to_ascii_lowercase();
252 DEVICE_PROFILES
253 .iter()
254 .find(|p| p.name.to_ascii_lowercase().contains(&lower))
255}
256
257#[must_use]
259pub fn find_formats_by_id(format_id: i32) -> Vec<&'static DeviceFormatInfo> {
260 let mut results: Vec<&'static DeviceFormatInfo> = Vec::new();
261 for profile in DEVICE_PROFILES {
262 if let Some(info) = profile.formats.iter().find(|f| f.format_id == format_id) {
263 results.push(info);
264 }
265 }
266 results
267}
268
269#[must_use]
271pub fn all_device_profiles() -> &'static [DeviceProfile] {
272 DEVICE_PROFILES
273}
274
275#[cfg(test)]
280#[allow(clippy::unwrap_used)]
281mod tests {
282 use super::*;
283
284 #[test]
285 fn has_18_device_profiles() {
286 assert_eq!(DEVICE_PROFILES.len(), 18);
287 }
288
289 #[test]
290 fn find_classic_5g_by_name() {
291 let device = find_device("iPod Classic 5G").expect("should find Classic 5G");
292 assert_eq!(device.name, "iPod Classic 5G (Video)");
293 assert_eq!(device.formats.len(), 7);
294 }
295
296 #[test]
297 fn find_case_insensitive() {
298 let device = find_device("IPOD CLASSIC 5G").expect("case-insensitive");
299 assert_eq!(device.name, "iPod Classic 5G (Video)");
300 }
301
302 #[test]
303 fn nano_7g_has_two_formats() {
304 let device = find_device("iPod Nano 7G").expect("Nano 7G");
305 assert_eq!(device.formats.len(), 2);
306 assert!(device.formats.iter().any(|f| f.format_id == 1007));
307 }
308
309 #[test]
310 fn touch_3g_reuses_touch_array() {
311 let t3 = find_device("iPod Touch 3G").expect("Touch 3G/4G");
312 let t1 = find_device("iPod Touch 1G").expect("Touch 1G/2G");
313 assert_eq!(t3.formats.len(), 8);
314 assert!(
315 std::ptr::eq(t3.formats, t1.formats),
316 "Touch devices must share format table"
317 );
318 }
319
320 #[test]
321 fn iphone_3g_reuses_iphone_array() {
322 let i1 = find_device("iPhone 1G").expect("iPhone 1G/2G");
323 let i3 = find_device("iPhone 3G").expect("iPhone 3G/3GS");
324 assert!(
325 std::ptr::eq(i1.formats, i3.formats),
326 "iPhone devices must share format table"
327 );
328 }
329
330 #[test]
331 fn video_5g_reuses_classic_5g_array() {
332 let c5 = find_device("iPod Classic 5G").expect("Classic 5G");
333 let v5 = find_device("iPod Video 5G").expect("Video 5G");
334 assert!(
335 std::ptr::eq(c5.formats, v5.formats),
336 "Video 5G must share Classic 5G table"
337 );
338 }
339
340 #[test]
341 fn rokr_e1_has_two_formats() {
342 let device = find_device("Motorola ROKR E1").expect("ROKR E1");
343 assert_eq!(device.formats.len(), 2);
344 assert!(device.formats.iter().any(|f| f.format_id == 2003));
345 }
346
347 #[test]
348 fn nonexistent_device_returns_none() {
349 assert!(find_device("iPod Shuffle").is_none());
350 }
351
352 #[test]
353 fn find_1019_across_devices() {
354 let results = find_formats_by_id(1019);
355 assert!(!results.is_empty());
356 assert!(results.iter().all(|f| f.format_id == 1019));
357 }
358
359 #[test]
360 fn find_3001_in_four_devices() {
361 let results = find_formats_by_id(3001);
362 assert_eq!(results.len(), 4);
363 }
364
365 #[test]
366 fn nonexistent_format_returns_empty() {
367 assert!(find_formats_by_id(9999).is_empty());
368 }
369
370 #[test]
371 fn all_device_profiles_is_stable() {
372 assert!(std::ptr::eq(all_device_profiles(), DEVICE_PROFILES));
373 }
374
375 #[test]
376 fn nano_4g_has_most_formats() {
377 let device = find_device("iPod Nano 4G").expect("Nano 4G");
378 assert_eq!(device.formats.len(), 12);
379 }
380
381 #[test]
382 fn every_device_has_formats() {
383 for p in DEVICE_PROFILES {
384 assert!(!p.formats.is_empty(), "{}", p.name);
385 }
386 }
387
388 #[test]
389 fn every_format_has_description() {
390 for p in DEVICE_PROFILES {
391 for f in p.formats {
392 assert!(!f.description.is_empty());
393 }
394 }
395 }
396}